PageSourceSearch

https://disclosure.gse.com.gh:8443/share/res/js/yui-common_5518510ec77ced541930f488625ed9c0.js

js gse.com.gh collected 2026-10-02 10:06:49 UTC 3,064,636 bytes, 90,774 lines download raw bytes

1/**
2 * Copyright 2009 Tim Down.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18if(!Array.prototype.push){Array.prototype.push=function(){for(var i=0,len=arguments.length;i<len;i++){this[this.length]=arguments[i];}
19return this.length;};}
20if(!Array.prototype.shift){Array.prototype.shift=function(){if(this.length>0){var firstItem=this[0];for(var i=0,len=this.length-1;i<len;i++){this[i]=this[i+1];}
21this.length=this.length-1;return firstItem;}};}
22if(!Array.prototype.splice){Array.prototype.splice=function(startIndex,deleteCount){var itemsAfterDeleted=this.slice(startIndex+deleteCount);var itemsDeleted=this.slice(startIndex,startIndex+deleteCount);this.length=startIndex;var argumentsArray=[];for(var i=0,len=arguments.length;i<len;i++){argumentsArray[i]=arguments[i];}
23var itemsToAppend=(argumentsArray.length>2)?itemsAfterDeleted=argumentsArray.slice(2).concat(itemsAfterDeleted):itemsAfterDeleted;for(i=0,len=itemsToAppend.length;i<len;i++){this.push(itemsToAppend[i]);}
24return itemsDeleted;};}
25var log4javascript;(function(){function isUndefined(obj){return typeof obj=="undefined";}
26function EventSupport(){}
27EventSupport.prototype={eventTypes:[],eventListeners:{},setEventTypes:function(eventTypesParam){if(eventTypesParam instanceof Array){this.eventTypes=eventTypesParam;this.eventListeners={};for(var i=0,len=this.eventTypes.length;i<len;i++){this.eventListeners[this.eventTypes[i]]=[];}}else{handleError("log4javascript.EventSupport ["+this+"]: setEventTypes: eventTypes parameter must be an Array");}},addEventListener:function(eventType,listener){if(typeof listener=="function"){if(!array_contains(this.eventTypes,eventType)){handleError("log4javascript.EventSupport ["+this+"]: addEventListener: no event called '"+eventType+"'");}
28this.eventListeners[eventType].push(listener);}else{handleError("log4javascript.EventSupport ["+this+"]: addEventListener: listener must be a function");}},removeEventListener:function(eventType,listener){if(typeof listener=="function"){if(!array_contains(this.eventTypes,eventType)){handleError("log4javascript.EventSupport ["+this+"]: removeEventListener: no event called '"+eventType+"'");}
29array_remove(this.eventListeners[eventType],listener);}else{handleError("log4javascript.EventSupport ["+this+"]: removeEventListener: listener must be a function");}},dispatchEvent:function(eventType,eventArgs){if(array_contains(this.eventTypes,eventType)){var listeners=this.eventListeners[eventType];for(var i=0,len=listeners.length;i<len;i++){listeners[i](this,eventType,eventArgs);}}else{handleError("log4javascript.EventSupport ["+this+"]: dispatchEvent: no event called '"+eventType+"'");}}};var applicationStartDate=new Date();var uniqueId="log4javascript_"+applicationStartDate.getTime()+"_"+
30Math.floor(Math.random()*100000000);var emptyFunction=function(){};var newLine="\r\n";var pageLoaded=false;function Log4JavaScript(){}
31Log4JavaScript.prototype=new EventSupport();log4javascript=new Log4JavaScript();log4javascript.version="1.4.1";log4javascript.edition="log4javascript";function toStr(obj){if(obj&&obj.toString){return obj.toString();}else{return String(obj);}}
32function getExceptionMessage(ex){if(ex.message){return ex.message;}else if(ex.description){return ex.description;}else{return toStr(ex);}}
33function getUrlFileName(url){var lastSlashIndex=Math.max(url.lastIndexOf("/"),url.lastIndexOf("\\"));return url.substr(lastSlashIndex+1);}
34function getExceptionStringRep(ex){if(ex){var exStr="Exception: "+getExceptionMessage(ex);try{if(ex.lineNumber){exStr+=" on line number "+ex.lineNumber;}
35if(ex.fileName){exStr+=" in file "+getUrlFileName(ex.fileName);}}catch(localEx){logLog.warn("Unable to obtain file and line information for error");}
36if(showStackTraces&&ex.stack){exStr+=newLine+"Stack trace:"+newLine+ex.stack;}
37return exStr;}
38return null;}
39function bool(obj){return Boolean(obj);}
40function trim(str){return str.replace(/^\s+/,"").replace(/\s+$/,"");}
41function splitIntoLines(text){var text2=text.replace(/\r\n/g,"\n").replace(/\r/g,"\n");return text2.split("\n");}
42function urlEncode(str){return escape(str).replace(/\+/g,"%2B").replace(/"/g,"%22").replace(/'/g,"%27").replace(/\//g,"%2F").replace(/=/g,"%3D");}
43function urlDecode(str){return unescape(str).replace(/%2B/g,"+").replace(/%22/g,"\"").replace(/%27/g,"'").replace(/%2F/g,"/").replace(/%3D/g,"=");}
44function array_remove(arr,val){var index=-1;for(var i=0,len=arr.length;i<len;i++){if(arr[i]===val){index=i;break;}}
45if(index>=0){arr.splice(index,1);return true;}else{return false;}}
46function array_contains(arr,val){for(var i=0,len=arr.length;i<len;i++){if(arr[i]==val){return true;}}
47return false;}
48function extractBooleanFromParam(param,defaultValue){if(isUndefined(param)){return defaultValue;}else{return bool(param);}}
49function extractStringFromParam(param,defaultValue){if(isUndefined(param)){return defaultValue;}else{return String(param);}}
50function extractIntFromParam(param,defaultValue){if(isUndefined(param)){return defaultValue;}else{try{var value=parseInt(param,10);return isNaN(value)?defaultValue:value;}catch(ex){logLog.warn("Invalid int param "+param,ex);return defaultValue;}}}
51function extractFunctionFromParam(param,defaultValue){if(typeof param=="function"){return param;}else{return defaultValue;}}
52function isError(err){return(err instanceof Error);}
53if(!Function.prototype.apply){Function.prototype.apply=function(obj,args){var methodName="__apply__";if(typeof obj[methodName]!="undefined"){methodName+=String(Math.random()).substr(2);}
54obj[methodName]=this;var argsStrings=[];for(var i=0,len=args.length;i<len;i++){argsStrings[i]="args["+i+"]";}
55var script="obj."+methodName+"("+argsStrings.join(",")+")";var returnValue=eval(script);delete obj[methodName];return returnValue;};}
56if(!Function.prototype.call){Function.prototype.call=function(obj){var args=[];for(var i=1,len=arguments.length;i<len;i++){args[i-1]=arguments[i];}
57return this.apply(obj,args);};}
58function getListenersPropertyName(eventName){return"__log4javascript_listeners__"+eventName;}
59function addEvent(node,eventName,listener,useCapture,win){win=win?win:window;if(node.addEventListener){node.addEventListener(eventName,listener,useCapture);}else if(node.attachEvent){node.attachEvent("on"+eventName,listener);}else{var propertyName=getListenersPropertyName(eventName);if(!node[propertyName]){node[propertyName]=[];node["on"+eventName]=function(evt){evt=getEvent(evt,win);var listenersPropertyName=getListenersPropertyName(eventName);var listeners=this[listenersPropertyName].concat([]);var currentListener;while((currentListener=listeners.shift())){currentListener.call(this,evt);}};}
60node[propertyName].push(listener);}}
61function removeEvent(node,eventName,listener,useCapture){if(node.removeEventListener){node.removeEventListener(eventName,listener,useCapture);}else if(node.detachEvent){node.detachEvent("on"+eventName,listener);}else{var propertyName=getListenersPropertyName(eventName);if(node[propertyName]){array_remove(node[propertyName],listener);}}}
62function getEvent(evt,win){win=win?win:window;return evt?evt:win.event;}
63function stopEventPropagation(evt){if(evt.stopPropagation){evt.stopPropagation();}else if(typeof evt.cancelBubble!="undefined"){evt.cancelBubble=true;}
64evt.returnValue=false;}
65var logLog={quietMode:false,debugMessages:[],setQuietMode:function(quietMode){this.quietMode=bool(quietMode);},numberOfErrors:0,alertAllErrors:false,setAlertAllErrors:function(alertAllErrors){this.alertAllErrors=alertAllErrors;},debug:function(message){this.debugMessages.push(message);},displayDebug:function(){alert(this.debugMessages.join(newLine));},warn:function(message,exception){},error:function(message,exception){if(++this.numberOfErrors==1||this.alertAllErrors){if(!this.quietMode){var alertMessage="log4javascript error: "+message;if(exception){alertMessage+=newLine+newLine+"Original error: "+getExceptionStringRep(exception);}
66alert(alertMessage);}}}};log4javascript.logLog=logLog;log4javascript.setEventTypes(["load","error"]);function handleError(message,exception){logLog.error(message,exception);log4javascript.dispatchEvent("error",{"message":message,"exception":exception});}
67log4javascript.handleError=handleError;var enabled=!((typeof log4javascript_disabled!="undefined")&&log4javascript_disabled);log4javascript.setEnabled=function(enable){enabled=bool(enable);};log4javascript.isEnabled=function(){return enabled;};var useTimeStampsInMilliseconds=true;log4javascript.setTimeStampsInMilliseconds=function(timeStampsInMilliseconds){useTimeStampsInMilliseconds=bool(timeStampsInMilliseconds);};log4javascript.isTimeStampsInMilliseconds=function(){return useTimeStampsInMilliseconds;};log4javascript.evalInScope=function(expr){return eval(expr);};var showStackTraces=false;log4javascript.setShowStackTraces=function(show){showStackTraces=bool(show);};var Level=function(level,name){this.level=level;this.name=name;};Level.prototype={toString:function(){return this.name;},equals:function(level){return this.level==level.level;},isGreaterOrEqual:function(level){return this.level>=level.level;}};Level.ALL=new Level(Number.MIN_VALUE,"ALL");Level.TRACE=new Level(10000,"TRACE");Level.DEBUG=new Level(20000,"DEBUG");Level.INFO=new Level(30000,"INFO");Level.WARN=new Level(40000,"WARN");Level.ERROR=new Level(50000,"ERROR");Level.FATAL=new Level(60000,"FATAL");Level.OFF=new Level(Number.MAX_VALUE,"OFF");log4javascript.Level=Level;function Timer(name,level){this.name=name;this.level=isUndefined(level)?Level.INFO:level;this.start=new Date();}
68Timer.prototype.getElapsedTime=function(){return new Date().getTime()-this.start.getTime();};var anonymousLoggerName="[anonymous]";var defaultLoggerName="[default]";var nullLoggerName="[null]";var rootLoggerName="root";function Logger(name){this.name=name;this.parent=null;this.children=[];var appenders=[];var loggerLevel=null;
68var isRoot=(this.name===rootLoggerName);var isNull=(this.name===nullLoggerName);var appenderCache=null;var appenderCacheInvalidated=false;this.addChild=function(childLogger){this.children.push(childLogger);childLogger.parent=this;childLogger.invalidateAppenderCache();};var additive=true;this.getAdditivity=function(){return additive;};this.setAdditivity=function(additivity){var valueChanged=(additive!=additivity);additive=additivity;if(valueChanged){this.invalidateAppenderCache();}};this.addAppender=function(appender){if(isNull){handleError("Logger.addAppender: you may not add an appender to the null logger");}else{if(appender instanceof log4javascript.Appender){if(!array_contains(appenders,appender)){appenders.push(appender);appender.setAddedToLogger(this);this.invalidateAppenderCache();}}else{handleError("Logger.addAppender: appender supplied ('"+
69toStr(appender)+"') is not a subclass of Appender");}}};this.removeAppender=function(appender){array_remove(appenders,appender);appender.setRemovedFromLogger(this);this.invalidateAppenderCache();};this.removeAllAppenders=function(){var appenderCount=appenders.length;if(appenderCount>0){for(var i=0;i<appenderCount;i++){appenders[i].setRemovedFromLogger(this);}
70appenders.length=0;this.invalidateAppenderCache();}};this.getEffectiveAppenders=function(){if(appenderCache===null||appenderCacheInvalidated){var parentEffectiveAppenders=(isRoot||!this.getAdditivity())?[]:this.parent.getEffectiveAppenders();appenderCache=parentEffectiveAppenders.concat(appenders);appenderCacheInvalidated=false;}
71return appenderCache;};this.invalidateAppenderCache=function(){appenderCacheInvalidated=true;for(var i=0,len=this.children.length;i<len;i++){this.children[i].invalidateAppenderCache();}};this.log=function(level,params){if(level.isGreaterOrEqual(this.getEffectiveLevel())){var exception;var finalParamIndex=params.length-1;var lastParam=params[params.length-1];if(params.length>1&&isError(lastParam)){exception=lastParam;finalParamIndex--;}
72var messages=[];for(var i=0;i<=finalParamIndex;i++){messages[i]=params[i];}
73var loggingEvent=new LoggingEvent(this,new Date(),level,messages,exception);this.callAppenders(loggingEvent);}};this.callAppenders=function(loggingEvent){var effectiveAppenders=this.getEffectiveAppenders();for(var i=0,len=effectiveAppenders.length;i<len;i++){effectiveAppenders[i].doAppend(loggingEvent);}};this.setLevel=function(level){if(isRoot&&level===null){handleError("Logger.setLevel: you cannot set the level of the root logger to null");}else if(level instanceof Level){loggerLevel=level;}else{handleError("Logger.setLevel: level supplied to logger "+
74this.name+" is not an instance of log4javascript.Level");}};this.getLevel=function(){return loggerLevel;};this.getEffectiveLevel=function(){for(var logger=this;logger!==null;logger=logger.parent){var level=logger.getLevel();if(level!==null){return level;}}};this.group=function(name,initiallyExpanded){var effectiveAppenders=this.getEffectiveAppenders();for(var i=0,len=effectiveAppenders.length;i<len;i++){effectiveAppenders[i].group(name,initiallyExpanded);}};this.groupEnd=function(name){var effectiveAppenders=this.getEffectiveAppenders();for(var i=0,len=effectiveAppenders.length;i<len;i++){effectiveAppenders[i].groupEnd();}};var timers={};this.time=function(name,level){if(isUndefined(name)){handleError("Logger.time: a name for the timer must be supplied");}else if(level&&!(level instanceof Level)){handleError("Logger.time: level supplied to timer "+
75name+" is not an instance of log4javascript.Level");}else{timers[name]=new Timer(name,level);}};this.timeEnd=function(name){if(isUndefined(name)){handleError("Logger.timeEnd: a name for the timer must be supplied");}else if(timers[name]){var timer=timers[name];var milliseconds=timer.getElapsedTime();this.log(timer.level,["Timer "+toStr(name)+" completed in "+milliseconds+"ms"]);delete timers[name];}else{logLog.warn("Logger.timeEnd: no timer found with name "+name);}};this.assert=function(expr){if(!expr){var args=[];for(var i=1,len=arguments.length;i<len;i++){args.push(arguments[i]);}
76args=(args.length>0)?args:["Assertion Failure"];args.push(newLine);args.push(expr);this.log(Level.ERROR,args);}};this.toString=function(){return"Logger["+this.name+"]";};}
77Logger.prototype={trace:function(){this.log(Level.TRACE,arguments);},debug:function(){this.log(Level.DEBUG,arguments);},info:function(){this.log(Level.INFO,arguments);},warn:function(){this.log(Level.WARN,arguments);},error:function(){this.log(Level.ERROR,arguments);},fatal:function(){this.log(Level.FATAL,arguments);},isEnabledFor:function(level){return level.isGreaterOrEqual(this.getEffectiveLevel());},isTraceEnabled:function(){return this.isEnabledFor(Level.TRACE);},isDebugEnabled:function(){return this.isEnabledFor(Level.DEBUG);},isInfoEnabled:function(){return this.isEnabledFor(Level.INFO);},isWarnEnabled:function(){return this.isEnabledFor(Level.WARN);},isErrorEnabled:function(){return this.isEnabledFor(Level.ERROR);},isFatalEnabled:function(){return this.isEnabledFor(Level.FATAL);}};Logger.prototype.trace.isEntryPoint=true;Logger.prototype.debug.isEntryPoint=true;Logger.prototype.info.isEntryPoint=true;Logger.prototype.warn.isEntryPoint=true;Logger.prototype.error.isEntryPoint=true;Logger.prototype.fatal.isEntryPoint=true;var loggers={};var loggerNames=[];var ROOT_LOGGER_DEFAULT_LEVEL=Level.DEBUG;var rootLogger=new Logger(rootLoggerName);rootLogger.setLevel(ROOT_LOGGER_DEFAULT_LEVEL);log4javascript.getRootLogger=function(){return rootLogger;};log4javascript.getLogger=function(loggerName){if(!(typeof loggerName=="string")){loggerName=anonymousLoggerName;logLog.warn("log4javascript.getLogger: non-string logger name "+
78toStr(loggerName)+" supplied, returning anonymous logger");}
79if(loggerName==rootLoggerName){handleError("log4javascript.getLogger: root logger may not be obtained by name");}
80if(!loggers[loggerName]){var logger=new Logger(loggerName);loggers[loggerName]=logger;loggerNames.push(loggerName);var lastDotIndex=loggerName.lastIndexOf(".");var parentLogger;if(lastDotIndex>-1){var parentLoggerName=loggerName.substring(0,lastDotIndex);parentLogger=log4javascript.getLogger(parentLoggerName);}else{parentLogger=rootLogger;}
81parentLogger.addChild(logger);}
82return loggers[loggerName];};var defaultLogger=null;log4javascript.getDefaultLogger=function(){if(!defaultLogger){defaultLogger=log4javascript.getLogger(defaultLoggerName);var a=new log4javascript.PopUpAppender();defaultLogger.addAppender(a);}
83return defaultLogger;};var nullLogger=null;log4javascript.getNullLogger=function(){if(!nullLogger){nullLogger=new Logger(nullLoggerName);nullLogger.setLevel(Level.OFF);}
84return nullLogger;};log4javascript.resetConfiguration=function(){rootLogger.setLevel(ROOT_LOGGER_DEFAULT_LEVEL);loggers={};};var LoggingEvent=function(logger,timeStamp,level,messages,exception){this.logger=logger;this.timeStamp=timeStamp;this.timeStampInMilliseconds=timeStamp.getTime();this.timeStampInSeconds=Math.floor(this.timeStampInMilliseconds/1000);this.milliseconds=this.timeStamp.getMilliseconds();this.level=level;this.messages=messages;this.exception=exception;};LoggingEvent.prototype={getThrowableStrRep:function(){return this.exception?getExceptionStringRep(this.exception):"";},getCombinedMessages:function(){return(this.messages.length==1)?this.messages[0]:this.messages.join(newLine);},toString:function(){return"LoggingEvent["+this.level+"]";}};log4javascript.LoggingEvent=LoggingEvent;var Layout=function(){};Layout.prototype={defaults:{loggerKey:"logger",timeStampKey:"timestamp",millisecondsKey:"milliseconds",levelKey:"level",messageKey:"message",exceptionKey:"exception",urlKey:"url"},loggerKey:"logger",timeStampKey:"timestamp",millisecondsKey:"milliseconds",levelKey:"level",messageKey:"message",exceptionKey:"exception",urlKey:"url",batchHeader:"",batchFooter:"",batchSeparator:"",returnsPostData:false,overrideTimeStampsSetting:false,useTimeStampsInMilliseconds:null,format:function(loggingEvent){handleError("Layout.format: layout supplied has no format() method");},ignoresThrowable:function(){handleError("Layout.ignoresThrowable: layout supplied has no ignoresThrowable() method");},getContentType:function(){return"text/plain";},allowBatching:function(){return true;},setTimeStampsInMilliseconds:function(timeStampsInMilliseconds){this.overrideTimeStampsSetting=true;this.useTimeStampsInMilliseconds=bool(timeStampsInMilliseconds);},isTimeStampsInMilliseconds:function(){return this.overrideTimeStampsSetting?this.useTimeStampsInMilliseconds:useTimeStampsInMilliseconds;},getTimeStampValue:function(loggingEvent){return this.isTimeStampsInMilliseconds()?loggingEvent.timeStampInMilliseconds:loggingEvent.timeStampInSeconds;},getDataValues:function(loggingEvent,combineMessages){var dataValues=[[this.loggerKey,loggingEvent.logger.name],[this.timeStampKey,this.getTimeStampValue(loggingEvent)],[this.levelKey,loggingEvent.level.name],[this.urlKey,window.location.href],[this.messageKey,combineMessages?loggingEvent.getCombinedMessages():loggingEvent.messages]];if(!this.isTimeStampsInMilliseconds()){dataValues.push([this.millisecondsKey,loggingEvent.milliseconds]);}
85if(loggingEvent.exception){dataValues.push([this.exceptionKey,getExceptionStringRep(loggingEvent.exception)]);}
86if(this.hasCustomFields()){for(var i=0,len=this.customFields.length;i<len;i++){var val=this.customFields[i].value;if(typeof val==="function"){val=val(this,loggingEvent);}
87dataValues.push([this.customFields[i].name,val]);}}
88return dataValues;},setKeys:function(loggerKey,timeStampKey,levelKey,messageKey,exceptionKey,urlKey,millisecondsKey){this.loggerKey=extractStringFromParam(loggerKey,this.defaults.loggerKey);this.timeStampKey=extractStringFromParam(timeStampKey,this.defaults.timeStampKey);this.levelKey=extractStringFromParam(levelKey,this.defaults.levelKey);this.messageKey=extractStringFromParam(messageKey,this.defaults.messageKey);this.exceptionKey=extractStringFromParam(exceptionKey,this.defaults.exceptionKey);this.urlKey=extractStringFromParam(urlKey,this.defaults.urlKey);this.millisecondsKey=extractStringFromParam(millisecondsKey,this.defaults.millisecondsKey);},setCustomField:function(name,value){var fieldUpdated=false;for(var i=0,len=this.customFields.length;i<len;i++){if(this.customFields[i].name===name){this.customFields[i].value=value;fieldUpdated=true;}}
89if(!fieldUpdated){this.customFields.push({"name":name,"value":value});}},hasCustomFields:function(){return(this.customFields.length>0);},toString:function(){handleError("Layout.toString: all layouts must override this method");}};log4javascript.Layout=Layout;var Appender=function(){};Appender.prototype=new EventSupport();Appender.prototype.layout=new PatternLayout();Appender.prototype.threshold=Level.ALL;Appender.prototype.loggers=[];Appender.prototype.doAppend=function(loggingEvent){if(enabled&&loggingEvent.level.level>=this.threshold.level){this.append(loggingEvent);}};Appender.prototype.append=function(loggingEvent){};Appender.prototype.setLayout=function(layout){if(layout instanceof Layout){this.layout=layout;}else{handleError("Appender.setLayout: layout supplied to "+
90this.toString()+" is not a subclass of Layout");}};Appender.prototype.getLayout=function(){return this.layout;};Appender.prototype.setThreshold=function(threshold){if(threshold instanceof Level){this.threshold=threshold;}else{handleError("Appender.setThreshold: threshold supplied to "+
91this.toString()+" is not a subclass of Level");}};Appender.prototype.getThreshold=function(){return this.threshold;};Appender.prototype.setAddedToLogger=function(logger){this.loggers.push(logger);};Appender.prototype.setRemovedFromLogger=function(logger){array_remove(this.loggers,logger);};Appender.prototype.group=emptyFunction;Appender.prototype.groupEnd=emptyFunction;Appender.prototype.toString=function(){handleError("Appender.toString: all appenders must override this method");};log4javascript.Appender=Appender;function SimpleLayout(){this.customFields=[];}
92SimpleLayout.prototype=new Layout();SimpleLayout.prototype.format=function(loggingEvent){return loggingEvent.level.name+" - "+loggingEvent.getCombinedMessages();};SimpleLayout.prototype.ignoresThrowable=function(){return true;};SimpleLayout.prototype.toString=function(){return"SimpleLayout";};log4javascript.SimpleLayout=SimpleLayout;function NullLayout(){this.customFields=[];}
93NullLayout.prototype=new Layout();NullLayout.prototype.format=function(loggingEvent){return loggingEvent.messages;};NullLayout.prototype.ignoresThrowable=function(){return true;};NullLayout.prototype.toString=function(){return"NullLayout";};log4javascript.NullLayout=NullLayout;function XmlLayout(combineMessages){this.combineMessages=extractBooleanFromParam(combineMessages,true);this.customFields=[];}
94XmlLayout.prototype=new Layout();XmlLayout.prototype.isCombinedMessages=function(){return this.combineMessages;};XmlLayout.prototype.getContentType=function(){return"text/xml";};XmlLayout.prototype.escapeCdata=function(str){return str.replace(/\]\]>/,"]]>]]&gt;<![CDATA[");};XmlLayout.prototype.format=function(loggingEvent){var layout=this;var i,len;function formatMessage(message){message=(typeof message==="string")?message:toStr(message);return"<log4javascript:message><![CDATA["+
95layout.escapeCdata(message)+"]]></log4javascript:message>";}
96var str="<log4javascript:event logger=\""+loggingEvent.logger.name+"\" timestamp=\""+this.getTimeStampValue(loggingEvent)+"\"";if(!this.isTimeStampsInMilliseconds()){str+=" milliseconds=\""+loggingEvent.milliseconds+"\"";}
97str+=" level=\""+loggingEvent.level.name+"\">"+newLine;if(this.combineMessages){str+=formatMessage(loggingEvent.getCombinedMessages());}else{str+="<log4javascript:messages>"+newLine;for(i=0,len=loggingEvent.messages.length;i<len;i++){str+=formatMessage(loggingEvent.messages[i])+newLine;}
98str+="</log4javascript:messages>"+newLine;}
99if(this.hasCustomFields()){for(i=0,len=this.customFields.length;i<len;i++){str+="<log4javascript:customfield name=\""+
100this.customFields[i].name+"\"><![CDATA["+
101this.customFields[i].value.toString()+"]]></log4javascript:customfield>"+newLine;}}
102if(loggingEvent.exception){str+="<log4javascript:exception><![CDATA["+
103getExceptionStringRep(loggingEvent.exception)+"]]></log4javascript:exception>"+newLine;}
104str+="</log4javascript:event>"+newLine+newLine;return str;};XmlLayout.prototype.ignoresThrowable=function(){return false;};XmlLayout.prototype.toString=function(){return"XmlLayout";};log4javascript.XmlLayout=XmlLayout;function escapeNewLines(str){return str.replace(/\r\n|\r|\n/g,"\\r\\n");}
105function JsonLayout(readable,combineMessages){this.readable=extractBooleanFromParam(readable,false);this.combineMessages=extractBooleanFromParam(combineMessages,true);this.batchHeader=this.readable?"["+newLine:"[";this.batchFooter=this.readable?"]"+newLine:"]";this.batchSeparator=this.readable?","+newLine:",";this.setKeys();this.colon=this.readable?": ":":";this.tab=this.readable?"\t":"";this.lineBreak=this.readable?newLine:"";this.customFields=[];}
106JsonLayout.prototype=new Layout();JsonLayout.prototype.isReadable=function(){return this.readable;};JsonLayout.prototype.isCombinedMessages=function(){return this.combineMessages;};JsonLayout.prototype.format=function(loggingEvent){var layout=this;var dataValues=this.getDataValues(loggingEvent,this.combineMessages);var str="{"+this.lineBreak;var i;function formatValue(val,prefix,expand){var formattedValue;var valType=typeof val;if(val instanceof Date){formattedValue=String(val.getTime());}else if(expand&&(val instanceof Array)){formattedValue="["+layout.lineBreak;for(i=0,len=val.length;i<len;i++){var childPrefix=prefix+layout.tab;formattedValue+=childPrefix+formatValue(val[i],childPrefix,false);
106if(i<val.length-1){formattedValue+=",";}
107formattedValue+=layout.lineBreak;}
108formattedValue+=prefix+"]";}else if(valType!=="number"&&valType!=="boolean"){formattedValue="\""+escapeNewLines(toStr(val).replace(/\"/g,"\\\""))+"\"";}else{formattedValue=val;}
109return formattedValue;}
110for(i=0,len=dataValues.length;i<len;i++){str+=this.tab+"\""+dataValues[i][0]+"\""+this.colon+formatValue(dataValues[i][1],this.tab,true);if(i<dataValues.length-1){str+=",";}
111str+=this.lineBreak;}
112str+="}"+this.lineBreak;return str;};JsonLayout.prototype.ignoresThrowable=function(){return false;};JsonLayout.prototype.toString=function(){return"JsonLayout";};JsonLayout.prototype.getContentType=function(){return"application/json";};log4javascript.JsonLayout=JsonLayout;function HttpPostDataLayout(){this.setKeys();this.customFields=[];this.returnsPostData=true;}
113HttpPostDataLayout.prototype=new Layout();HttpPostDataLayout.prototype.allowBatching=function(){return false;};HttpPostDataLayout.prototype.format=function(loggingEvent){var dataValues=this.getDataValues(loggingEvent);var queryBits=[];for(var i=0,len=dataValues.length;i<len;i++){var val=(dataValues[i][1]instanceof Date)?String(dataValues[i][1].getTime()):dataValues[i][1];queryBits.push(urlEncode(dataValues[i][0])+"="+urlEncode(val));}
114return queryBits.join("&");};HttpPostDataLayout.prototype.ignoresThrowable=function(loggingEvent){return false;};HttpPostDataLayout.prototype.toString=function(){return"HttpPostDataLayout";};log4javascript.HttpPostDataLayout=HttpPostDataLayout;function formatObjectExpansion(obj,depth,indentation){var objectsExpanded=[];function doFormat(obj,depth,indentation){var i,j,len,childDepth,childIndentation,childLines,expansion,childExpansion;if(!indentation){indentation="";}
115function formatString(text){var lines=splitIntoLines(text);for(var j=1,jLen=lines.length;j<jLen;j++){lines[j]=indentation+lines[j];}
116return lines.join(newLine);}
117if(obj===null){return"null";}else if(typeof obj=="undefined"){return"undefined";}else if(typeof obj=="string"){return formatString(obj);}else if(typeof obj=="object"&&array_contains(objectsExpanded,obj)){try{expansion=toStr(obj);}catch(ex){expansion="Error formatting property. Details: "+getExceptionStringRep(ex);}
118return expansion+" [already expanded]";}else if((obj instanceof Array)&&depth>0){objectsExpanded.push(obj);expansion="["+newLine;childDepth=depth-1;childIndentation=indentation+"  ";childLines=[];for(i=0,len=obj.length;i<len;i++){try{childExpansion=doFormat(obj[i],childDepth,childIndentation);childLines.push(childIndentation+childExpansion);}catch(ex){childLines.push(childIndentation+"Error formatting array member. Details: "+
119getExceptionStringRep(ex)+"");}}
120expansion+=childLines.join(","+newLine)+newLine+indentation+"]";return expansion;}else if(typeof obj=="object"&&depth>0){objectsExpanded.push(obj);expansion="{"+newLine;childDepth=depth-1;childIndentation=indentation+"  ";childLines=[];for(i in obj){try{childExpansion=doFormat(obj[i],childDepth,childIndentation);childLines.push(childIndentation+i+": "+childExpansion);}catch(ex){childLines.push(childIndentation+i+": Error formatting property. Details: "+
121getExceptionStringRep(ex));}}
122expansion+=childLines.join(","+newLine)+newLine+indentation+"}";return expansion;}else{return formatString(toStr(obj));}}
123return doFormat(obj,depth,indentation);}
124var SimpleDateFormat;(function(){var regex=/('[^']*')|(G+|y+|M+|w+|W+|D+|d+|F+|E+|a+|H+|k+|K+|h+|m+|s+|S+|Z+)|([a-zA-Z]+)|([^a-zA-Z']+)/;var monthNames=["January","February","March","April","May","June","July","August","September","October","November","December"];var dayNames=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var TEXT2=0,TEXT3=1,NUMBER=2,YEAR=3,MONTH=4,TIMEZONE=5;var types={G:TEXT2,y:YEAR,M:MONTH,w:NUMBER,W:NUMBER,D:NUMBER,d:NUMBER,F:NUMBER,E:TEXT3,a:TEXT2,H:NUMBER,k:NUMBER,K:NUMBER,h:NUMBER,m:NUMBER,s:NUMBER,S:NUMBER,Z:TIMEZONE};var ONE_DAY=24*60*60*1000;var ONE_WEEK=7*ONE_DAY;var DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK=1;var newDateAtMidnight=function(year,month,day){var d=new Date(year,month,day,0,0,0);d.setMilliseconds(0);return d;};Date.prototype.getDifference=function(date){return this.getTime()-date.getTime();};Date.prototype.isBefore=function(d){return this.getTime()<d.getTime();};Date.prototype.getUTCTime=function(){return Date.UTC(this.getFullYear(),this.getMonth(),this.getDate(),this.getHours(),this.getMinutes(),this.get
124Seconds(),this.getMilliseconds());};Date.prototype.getTimeSince=function(d){return this.getUTCTime()-d.getUTCTime();};Date.prototype.getPreviousSunday=function(){var midday=new Date(this.getFullYear(),this.getMonth(),this.getDate(),12,0,0);var previousSunday=new Date(midday.getTime()-this.getDay()*ONE_DAY);return newDateAtMidnight(previousSunday.getFullYear(),previousSunday.getMonth(),previousSunday.getDate());};Date.prototype.getWeekInYear=function(minimalDaysInFirstWeek){if(isUndefined(this.minimalDaysInFirstWeek)){minimalDaysInFirstWeek=DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;}
125var previousSunday=this.getPreviousSunday();var startOfYear=newDateAtMidnight(this.getFullYear(),0,1);var numberOfSundays=previousSunday.isBefore(startOfYear)?0:1+Math.floor(previousSunday.getTimeSince(startOfYear)/ONE_WEEK);var numberOfDaysInFirstWeek=7-startOfYear.getDay();var weekInYear=numberOfSundays;if(numberOfDaysInFirstWeek<minimalDaysInFirstWeek){weekInYear--;}
126return weekInYear;};Date.prototype.getWeekInMonth=function(minimalDaysInFirstWeek){if(isUndefined(this.minimalDaysInFirstWeek)){minimalDaysInFirstWeek=DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;}
127var previousSunday=this.getPreviousSunday();var startOfMonth=newDateAtMidnight(this.getFullYear(),this.getMonth(),1);var numberOfSundays=previousSunday.isBefore(startOfMonth)?0:1+Math.floor(previousSunday.getTimeSince(startOfMonth)/ONE_WEEK);var numberOfDaysInFirstWeek=7-startOfMonth.getDay();var weekInMonth=numberOfSundays;if(numberOfDaysInFirstWeek>=minimalDaysInFirstWeek){weekInMonth++;}
128return weekInMonth;};Date.prototype.getDayInYear=function(){var startOfYear=newDateAtMidnight(this.getFullYear(),0,1);return 1+Math.floor(this.getTimeSince(startOfYear)/ONE_DAY);};SimpleDateFormat=function(formatString){this.formatString=formatString;};SimpleDateFormat.prototype.setMinimalDaysInFirstWeek=function(days){this.minimalDaysInFirstWeek=days;};SimpleDateFormat.prototype.getMinimalDaysInFirstWeek=function(){return isUndefined(this.minimalDaysInFirstWeek)?DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK:this.minimalDaysInFirstWeek;};var padWithZeroes=function(str,len){while(str.length<len){str="0"+str;}
129return str;};var formatText=function(data,numberOfLetters,minLength){return(numberOfLetters>=4)?data:data.substr(0,Math.max(minLength,numberOfLetters));};var formatNumber=function(data,numberOfLetters){var dataString=""+data;return padWithZeroes(dataString,numberOfLetters);};SimpleDateFormat.prototype.format=function(date){var formattedString="";var result;var searchString=this.formatString;while((result=regex.exec(searchString))){var quotedString=result[1];var patternLetters=result[2];var otherLetters=result[3];var otherCharacters=result[4];if(quotedString){if(quotedString=="''"){formattedString+="'";}else{formattedString+=quotedString.substring(1,quotedString.length-1);}}else if(otherLetters){}else if(otherCharacters){formattedString+=otherCharacters;}else if(patternLetters){var patternLetter=patternLetters.charAt(0);var numberOfLetters=patternLetters.length;var rawData="";switch(patternLetter){case"G":rawData="AD";break;case"y":rawData=date.getFullYear();break;case"M":rawData=date.getMonth();break;case"w":rawData=date.getWeekInYear(this.getMinimalDaysInFirstWeek());break;case"W":rawData=date.getWeekInMonth(this.getMinimalDaysInFirstWeek());break;case"D":rawData=date.getDayInYear();break;case"d":rawData=date.getDate();break;case"F":rawData=1+Math.floor((date.getDate()-1)/7);break;case"E":rawData=dayNames[date.getDay()];break;case"a":rawData=(date.getHours()>=12)?"PM":"AM";break;case"H":rawData=date.getHours();break;case"k":rawData=date.getHours()||24;break;case"K":rawData=date.getHours()%12;break;case"h":rawData=(date.getHours()%12)||12;break;case"m":rawData=date.getMinutes();break;case"s":rawData=date.getSeconds();break;case"S":rawData=date.getMilliseconds();break;case"Z":rawData=date.getTimezoneOffset();break;}
130switch(types[patternLetter]){case TEXT2:formattedString+=formatText(rawData,numberOfLetters,2);break;case TEXT3:formattedString+=formatText(rawData,numberOfLetters,3);break;case NUMBER:formattedString+=formatNumber(rawData,numberOfLetters);break;case YEAR:if(numberOfLetters<=3){var dataString=""+rawData;formattedString+=dataString.substr(2,2);}else{formattedString+=formatNumber(rawData,numberOfLetters);}
131break;case MONTH:if(numberOfLetters>=3){formattedString+=formatText(monthNames[rawData],numberOfLetters,numberOfLetters);}else{formattedString+=formatNumber(rawData+1,numberOfLetters);}
132break;case TIMEZONE:var isPositive=(rawData>0);var prefix=isPositive?"-":"+";var absData=Math.abs(rawData);var hours=""+Math.floor(absData/60);hours=padWithZeroes(hours,2);var minutes=""+(absData%60);minutes=padWithZeroes(minutes,2);formattedString+=prefix+hours+minutes;break;}}
133searchString=searchString.substr(result.index+result[0].length);}
134return formattedString;};})();log4javascript.SimpleDateFormat=SimpleDateFormat;function PatternLayout(pattern){if(pattern){this.pattern=pattern;}else{this.pattern=PatternLayout.DEFAULT_CONVERSION_PATTERN;}
135this.customFields=[];}
136PatternLayout.TTCC_CONVERSION_PATTERN="%r %p %c - %m%n";PatternLayout.DEFAULT_CONVERSION_PATTERN="%m%n";PatternLayout.ISO8601_DATEFORMAT="yyyy-MM-dd HH:mm:ss,SSS";PatternLayout.DATETIME_DATEFORMAT="dd MMM yyyy HH:mm:ss,SSS";PatternLayout.ABSOLUTETIME_DATEFORMAT="HH:mm:ss,SSS";PatternLayout.prototype=new Layout();PatternLayout.prototype.format=function(loggingEvent){var regex=/%(-?[0-9]+)?(\.?[0-9]+)?([acdfmMnpr%])(\{([^\}]+)\})?|([^%]+)/;var formattedString="";var result;var searchString=this.pattern;while((result=regex.exec(searchString))){var matchedString=result[0];var padding=result[1];var truncation=result[2];var conversionCharacter=result[3];var specifier=result[5];var text=result[6];if(text){formattedString+=""+text;}else{var replacement="";switch(conversionCharacter){case"a":case"m":var depth=0;
136if(specifier){depth=parseInt(specifier,10);if(isNaN(depth)){handleError("PatternLayout.format: invalid specifier '"+
137specifier+"' for conversion character '"+conversionCharacter+"' - should be a number");depth=0;}}
138var messages=(conversionCharacter==="a")?loggingEvent.messages[0]:loggingEvent.messages;for(var i=0,len=messages.length;i<len;i++){if(i>0&&(replacement.charAt(replacement.length-1)!==" ")){replacement+=" ";}
139if(depth===0){replacement+=messages[i];}else{replacement+=formatObjectExpansion(messages[i],depth);}}
140break;case"c":var loggerName=loggingEvent.logger.name;if(specifier){var precision=parseInt(specifier,10);var loggerNameBits=loggingEvent.logger.name.split(".");if(precision>=loggerNameBits.length){replacement=loggerName;}else{replacement=loggerNameBits.slice(loggerNameBits.length-precision).join(".");}}else{replacement=loggerName;}
141break;case"d":var dateFormat=PatternLayout.ISO8601_DATEFORMAT;if(specifier){dateFormat=specifier;if(dateFormat=="ISO8601"){dateFormat=PatternLayout.ISO8601_DATEFORMAT;}else if(dateFormat=="ABSOLUTE"){dateFormat=PatternLayout.ABSOLUTETIME_DATEFORMAT;}else if(dateFormat=="DATE"){dateFormat=PatternLayout.DATETIME_DATEFORMAT;}}
142replacement=(new SimpleDateFormat(dateFormat)).format(loggingEvent.timeStamp);break;case"f":if(this.hasCustomFields()){var fieldIndex=0;if(specifier){fieldIndex=parseInt(specifier,10);if(isNaN(fieldIndex)){handleError("PatternLayout.format: invalid specifier '"+
143specifier+"' for conversion character 'f' - should be a number");}else if(fieldIndex===0){handleError("PatternLayout.format: invalid specifier '"+
144specifier+"' for conversion character 'f' - must be greater than zero");}else if(fieldIndex>this.customFields.length){handleError("PatternLayout.format: invalid specifier '"+
145specifier+"' for conversion character 'f' - there aren't that many custom fields");}else{fieldIndex=fieldIndex-1;}}
146replacement=this.customFields[fieldIndex].value;}
147break;case"n":replacement=newLine;break;case"p":replacement=loggingEvent.level.name;break;case"r":replacement=""+loggingEvent.timeStamp.getDifference(applicationStartDate);break;case"%":replacement="%";break;default:replacement=matchedString;break;}
148var l;if(truncation){l=parseInt(truncation.substr(1),10);var strLen=replacement.length;if(l<strLen){replacement=replacement.substring(strLen-l,strLen);}}
149if(padding){if(padding.charAt(0)=="-"){l=parseInt(padding.substr(1),10);while(replacement.length<l){replacement+=" ";}}else{l=parseInt(padding,10);while(replacement.length<l){replacement=" "+replacement;}}}
150formattedString+=replacement;}
151searchString=searchString.substr(result.index+result[0].length);}
152return formattedString;};PatternLayout.prototype.ignoresThrowable=function(){return true;};PatternLayout.prototype.toString=function(){return"PatternLayout";};log4javascript.PatternLayout=PatternLayout;function AlertAppender(){}
153AlertAppender.prototype=new Appender();AlertAppender.prototype.layout=new SimpleLayout();AlertAppender.prototype.append=function(loggingEvent){var formattedMessage=this.getLayout().format(loggingEvent);if(this.getLayout().ignoresThrowable()){formattedMessage+=loggingEvent.getThrowableStrRep();}
154alert(formattedMessage);};AlertAppender.prototype.toString=function(){return"AlertAppender";};log4javascript.AlertAppender=AlertAppender;function BrowserConsoleAppender(){}
155BrowserConsoleAppender.prototype=new log4javascript.Appender();BrowserConsoleAppender.prototype.layout=new NullLayout();BrowserConsoleAppender.prototype.threshold=Level.DEBUG;BrowserConsoleAppender.prototype.append=function(loggingEvent){var appender=this;var getFormattedMessage=function(){var layout=appender.getLayout();var formattedMessage=layout.format(loggingEvent);if(layout.ignoresThrowable()&&loggingEvent.exception){formattedMessage+=loggingEvent.getThrowableStrRep();}
156return formattedMessage;};if((typeof opera!="undefined")&&opera.postError){opera.postError(getFormattedMessage());}else if(window.console&&window.console.log){var formattedMesage=getFormattedMessage();
156if(window.console.debug&&Level.DEBUG.isGreaterOrEqual(loggingEvent.level)){window.console.debug(formattedMesage);}else if(window.console.info&&Level.INFO.equals(loggingEvent.level)){window.console.info(formattedMesage);}else if(window.console.warn&&Level.WARN.equals(loggingEvent.level)){window.console.warn(formattedMesage);}else if(window.console.error&&loggingEvent.level.isGreaterOrEqual(Level.ERROR)){window.console.error(formattedMesage);}else{window.console.log(formattedMesage);}}};BrowserConsoleAppender.prototype.group=function(name){if(window.console&&window.console.group){window.console.group(name);}};BrowserConsoleAppender.prototype.groupEnd=function(){if(window.console&&window.console.groupEnd){window.console.groupEnd();}};BrowserConsoleAppender.prototype.toString=function(){return"BrowserConsoleAppender";};log4javascript.BrowserConsoleAppender=BrowserConsoleAppender;function getXmlHttp(errorHandler){var xmlHttp=null;if(typeof XMLHttpRequest=="object"||typeof XMLHttpRequest=="function"){xmlHttp=new XMLHttpRequest();}else{try{xmlHttp=new ActiveXObject("Msxml2.XMLHTTP");}catch(e1){try{xmlHttp=new ActiveXObject("Microsoft.XMLHTTP");}catch(e2){if(errorHandler){errorHandler();}else{handleError("getXmlHttp: unable to obtain XMLHttpRequest object");}}}}
157return xmlHttp;}
158function isHttpRequestSuccessful(xmlHttp){return(isUndefined(xmlHttp.status)||xmlHttp.status===0||(xmlHttp.status>=200&&xmlHttp.status<300));}
159function AjaxAppender(url){var appender=this;var isSupported=true;if(!url){handleError("AjaxAppender: URL must be specified in constructor");isSupported=false;}
160var timed=this.defaults.timed;var waitForResponse=this.defaults.waitForResponse;var batchSize=this.defaults.batchSize;var timerInterval=this.defaults.timerInterval;var requestSuccessCallback=this.defaults.requestSuccessCallback;var failCallback=this.defaults.failCallback;var postVarName=this.defaults.postVarName;var sendAllOnUnload=this.defaults.sendAllOnUnload;var sessionId=null;var queuedLoggingEvents=[];var queuedRequests=[];var sending=false;var initialized=false;function checkCanConfigure(configOptionName){if(initialized){handleError("AjaxAppender: configuration option '"+
161configOptionName+"' may not be set after the appender has been initialized");return false;}
162return true;}
163this.getSessionId=function(){return sessionId;};this.setSessionId=function(sessionIdParam){sessionId=extractStringFromParam(sessionIdParam,null);this.layout.setCustomField("sessionid",sessionId);};this.setLayout=function(layoutParam){if(checkCanConfigure("layout")){this.layout=layoutParam;if(sessionId!==null){this.setSessionId(sessionId);}}};this.isTimed=function(){return timed;};this.setTimed=function(timedParam){if(checkCanConfigure("timed")){timed=bool(timedParam);}};this.getTimerInterval=function(){return timerInterval;};this.setTimerInterval=function(timerIntervalParam){if(checkCanConfigure("timerInterval")){timerInterval=extractIntFromParam(timerIntervalParam,timerInterval);}};this.isWaitForResponse=function(){return waitForResponse;};this.setWaitForResponse=function(waitForResponseParam){if(checkCanConfigure("waitForResponse")){waitForResponse=bool(waitForResponseParam);}};this.getBatchSize=function(){return batchSize;};this.setBatchSize=function(batchSizeParam){if(checkCanConfigure("batchSize")){batchSize=extractIntFromParam(batchSizeParam,batchSize);}};this.isSendAllOnUnload=function(){return sendAllOnUnload;};this.setSendAllOnUnload=function(sendAllOnUnloadParam){if(checkCanConfigure("sendAllOnUnload")){sendAllOnUnload=extractIntFromParam(sendAllOnUnloadParam,sendAllOnUnload);}};this.setRequestSuccessCallback=function(requestSuccessCallbackParam){requestSuccessCallback=extractFunctionFromParam(requestSuccessCallbackParam,requestSuccessCallback);};this.setFailCallback=function(failCallbackParam){failCallback=extractFunctionFromParam(failCallbackParam,failCallback);};this.getPostVarName=function(){return postVarName;};this.setPostVarName=function(postVarNameParam){if(checkCanConfigure("postVarName")){postVarName=extractStringFromParam(postVarNameParam,postVarName);}};function sendAll(){if(isSupported&&enabled){sending=true;var currentRequestBatch;if(waitForResponse){if(queuedRequests.length>0){currentRequestBatch=queuedRequests.shift();sendRequest(preparePostData(currentRequestBatch),sendAll);}else{sending=false;if(timed){scheduleSending();}}}else{while((currentRequestBatch=queuedRequests.shift())){sendRequest(preparePostData(currentRequestBatch));}
164sending=false;if(timed){scheduleSending();}}}}
165this.sendAll=sendAll;function sendAllRemaining(){if(isSupported&&enabled){var actualBatchSize=appender.getLayout().allowBatching()?batchSize:1;var currentLoggingEvent;var postData="";var batchedLoggingEvents=[];while((currentLoggingEvent=queuedLoggingEvents.shift())){batchedLoggingEvents.push(currentLoggingEvent);if(queuedLoggingEvents.length>=actualBatchSize){queuedRequests.push(batchedLoggingEvents);batchedLoggingEvents=[];}}
166if(batchedLoggingEvents.length>0){queuedRequests.push(batchedLoggingEvents);}
167waitForResponse=false;timed=false;sendAll();}}
168function preparePostData(batchedLoggingEvents){var formattedMessages=[];var currentLoggingEvent;var postData="";while((currentLoggingEvent=batchedLoggingEvents.shift())){var currentFormattedMessage=appender.getLayout().format(currentLoggingEvent);if(appender.getLayout().ignoresThrowable()){currentFormattedMessage+=loggingEvent.getThrowableStrRep();}
169formattedMessages.push(currentFormattedMessage);}
170if(batchedLoggingEvents.length==1){postData=formattedMessages.join("");}else{postData=appender.getLayout().batchHeader+
171formattedMessages.join(appender.getLayout().batchSeparator)+
172appender.getLayout().batchFooter;}
173postData=appender.getLayout().returnsPostData?postData:urlEncode(postVarName)+"="+urlEncode(postData);if(postData.length>0){postData+="&";}
174return postData+"layout="+urlEncode(appender.getLayout().toString());}
175function scheduleSending(){setTimeout(sendAll,timerInterval);}
176function xmlHttpErrorHandler(){var msg="AjaxAppender: could not create XMLHttpRequest object. AjaxAppender disabled";handleError(msg);isSupported=false;
176if(failCallback){failCallback(msg);}}
177function sendRequest(postData,successCallback){try{var xmlHttp=getXmlHttp(xmlHttpErrorHandler);if(isSupported){if(xmlHttp.overrideMimeType){xmlHttp.overrideMimeType(appender.getLayout().getContentType());}
178xmlHttp.onreadystatechange=function(){if(xmlHttp.readyState==4){if(isHttpRequestSuccessful(xmlHttp)){if(requestSuccessCallback){requestSuccessCallback(xmlHttp);}
179if(successCallback){successCallback(xmlHttp);}}else{var msg="AjaxAppender.append: XMLHttpRequest request to URL "+
180url+" returned status code "+xmlHttp.status;handleError(msg);if(failCallback){failCallback(msg);}}
181xmlHttp.onreadystatechange=emptyFunction;xmlHttp=null;}};xmlHttp.open("POST",url,true);try{xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded");}catch(headerEx){var msg="AjaxAppender.append: your browser's XMLHttpRequest implementation"+" does not support setRequestHeader, therefore cannot post data. AjaxAppender disabled";handleError(msg);isSupported=false;if(failCallback){failCallback(msg);}
182return;}
183xmlHttp.send(postData);}}catch(ex){var errMsg="AjaxAppender.append: error sending log message to "+url;handleError(errMsg,ex);isSupported=false;if(failCallback){failCallback(errMsg+". Details: "+getExceptionStringRep(ex));}}}
184this.append=function(loggingEvent){if(isSupported){if(!initialized){init();}
185queuedLoggingEvents.push(loggingEvent);var actualBatchSize=this.getLayout().allowBatching()?batchSize:1;if(queuedLoggingEvents.length>=actualBatchSize){var currentLoggingEvent;var postData="";var batchedLoggingEvents=[];while((currentLoggingEvent=queuedLoggingEvents.shift())){batchedLoggingEvents.push(currentLoggingEvent);}
186queuedRequests.push(batchedLoggingEvents);if(!timed){if(!waitForResponse||(waitForResponse&&!sending)){sendAll();}}}}};function init(){initialized=true;if(sendAllOnUnload){addEvent(window,"unload",sendAllRemaining);}
187if(timed){scheduleSending();}}}
188AjaxAppender.prototype=new Appender();AjaxAppender.prototype.defaults={waitForResponse:false,timed:false,timerInterval:1000,batchSize:1,sendAllOnUnload:true,requestSuccessCallback:null,failCallback:null,postVarName:"data"};AjaxAppender.prototype.layout=new HttpPostDataLayout();AjaxAppender.prototype.toString=function(){return"AjaxAppender";};log4javascript.AjaxAppender=AjaxAppender;function setCookie(name,value,days,path){var expires;path=path?"; path="+path:"";if(days){var date=new Date();date.setTime(date.getTime()+(days*24*60*60*1000));expires="; expires="+date.toGMTString();}else{expires="";}
189document.cookie=escape(name)+"="+escape(value)+expires+path;}
190function getCookie(name){var nameEquals=escape(name)+"=";var ca=document.cookie.split(";");for(var i=0,len=ca.length;i<len;i++){var c=ca[i];while(c.charAt(0)===" "){c=c.substring(1,c.length);}
191if(c.indexOf(nameEquals)===0){return unescape(c.substring(nameEquals.length,c.length));}}
192return null;}
193function getBaseUrl(){var scripts=document.getElementsByTagName("script");for(var i=0,len=scripts.length;i<len;++i){if(scripts[i].src.indexOf("log4javascript")!=-1){var lastSlash=scripts[i].src.lastIndexOf("/");return(lastSlash==-1)?"":scripts[i].src.substr(0,lastSlash+1);}}
194return null;}
195function isLoaded(win){try{return bool(win.loaded);}catch(ex){return false;}}
196var ConsoleAppender;(function(){var getConsoleHtmlLines=function(){return['<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">','<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">','<head>','<title>log4javascript</title>','<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />','<!-- Make IE8 behave like IE7, having gone to all the trouble of making IE work -->','<meta http-equiv="X-UA-Compatible" content="IE=7" />','<script type="text/javascript">var isIe = false, isIePre7 = false;</script>','<!--[if IE]><script type="text/javascript">isIe = true</script><![endif]-->','<!--[if lt IE 7]><script type="text/javascript">isIePre7 = true</script><![endif]-->','<script type="text/javascript">','//<![CDATA[','var loggingEnabled=true;var logQueuedEventsTimer=null;var logEntries=[];var logEntriesAndSeparators=[];var logItems=[];var renderDelay=100;var unrenderedLogItemsExist=false;var rootGroup,currentGroup=null;var loaded=false;var currentLogItem=null;var logMainContainer;function copyProperties(obj,props){for(var i in props){obj[i]=props[i];}}','function LogItem(){}','LogItem.prototype={mainContainer:null,wrappedContainer:null,unwrappedContainer:null,group:null,appendToLog:function(){for(var i=0,len=this.elementContainers.length;i<len;i++){this.elementContainers[i].appendToLog();}','this.group.update();},doRemove:function(doUpdate,removeFromGroup){if(this.rendered){for(var i=0,len=this.elementContainers.length;i<len;i++){this.elementContainers[i].remove();}','this.unwrappedElementContainer=null;this.wrappedElementContainer=null;this.mainElementContainer=null;}','if(this.group&&removeFromGroup){this.group.removeChild(this,doUpdate);}','if(this===currentLogItem){currentLogItem=null;}},remove:function(doUpdate,removeFromGroup){this.doRemove(doUpdate,removeFromGroup);},render:function(){},accept:function(visitor){visitor.visit(this);},getUnwrappedDomContainer:function(){return this.group.unwrappedElementContainer.contentDiv;},getWrappedDomContainer:function(){return this.group.wrappedElementContainer.contentDiv;},getMainDomContainer:function(){return this.group.mainElementContainer.contentDiv;}};LogItem.serializedItemKeys={LOG_ENTRY:0,GROUP_START:1,GROUP_END:2};function LogItemContainerElement(){}','LogItemContainerElement.prototype={appendToLog:function(){var insertBeforeFirst=(newestAtTop&&this.containerDomNode.hasChildNodes());if(insertBeforeFirst){this.containerDomNode.insertBefore(this.mainDiv,this.containerDomNode.firstChild);}else{this.containerDomNode.appendChild(this.mainDiv);}}};function SeparatorElementContainer(containerDomNode){this.containerDomNode=containerDomNode;this.mainDiv=document.createElement("div");this.mainDiv.className="separator";this.mainDiv.innerHTML="&nbsp;";}','SeparatorElementContainer.prototype=new LogItemContainerElement();SeparatorElementContainer.prototype.remove=function(){this.mainDiv.parentNode.removeChild(this.mainDiv);this.mainDiv=null;};function Separator(){this.rendered=false;}','Separator.prototype=new LogItem();copyProperties(Separator.prototype,{render:function(){var containerDomNode=this.group.contentDiv;if(isIe){this.unwrappedElementContainer=new SeparatorElementContainer(this.getUnwrappedDomContainer());this.wrappedElementContainer=new SeparatorElementContainer(this.getWrappedDomContainer());this.elementContainers=[this.unwrappedElementContainer,this.wrappedElementContainer];}else{this.mainElementContainer=new SeparatorElementContainer(this.getMainDomContainer());this.elementContainers=[this.mainElementContainer];}','this.content=this.formattedMessage;this.rendered=true;}});function GroupElementContainer(group,containerDomNode,isRoot,isWrapped){this.group=group;this.containerDomNode=containerDomNode;this.isRoot=isRoot;this.isWrapped=isWrapped;this.expandable=false;if(this.isRoot){if(isIe){this.contentDiv=logMainContainer.appendChild(document.createElement("div"));this.contentDiv.id=this.isWrapped?"log_wrapped":"log_unwrapped";}else{this.contentDiv=logMainContainer;}}else{var groupElementContainer=this;this.mainDiv=document.createElement("div");this.mainDiv.className="group";this.headingDiv=this.mainDiv.appendChild(document.createElement("div"));this.headingDiv.className="groupheading";this.expander=this.headingDiv.appendChild(document.createElement("span"));this.expander.className="expander unselectable greyedout";this.expander.unselectable=true;var expanderText=this.group.expanded?"-":"+";this.expanderTextNode=this.expander.appendChild(document.createTextNode(expanderText));this.headingDiv.appendChild(document.createTextNode(" "+this.group.name));this.contentDiv=this.mainDiv.appendChild(document.createElement("div"));var contentCssClass=this.group.expanded?"expanded":"collapsed";this.contentDiv.className="groupcontent "+contentCssClass;this.expander.onclick=function(){if(groupElementContainer.group.expandable){groupElementContainer.group.toggleExpanded();}};}}','GroupElementContainer.prototype=new LogItemContainerElement();copyProperties(GroupElementContainer.prototype,{toggleExpanded:function(){if(!this.isRoot){var oldCssClass,newCssClass,exp
196anderText;if(this.group.expanded){newCssClass="expanded";oldCssClass="collapsed";expanderText="-";}else{newCssClass="collapsed";oldCssClass="expanded";expanderText="+";}','replaceClass(this.contentDiv,newCssClass,oldCssClass);this.expanderTextNode.nodeValue=expanderText;}},remove:function(){if(!this.isRoot){this.headingDiv=null;this.expander.onclick=null;this.expander=null;this.expanderTextNode=null;this.contentDiv=null;this.containerDomNode=null;this.mainDiv.parentNode.removeChild(this.mainDiv);this.mainDiv=null;}},reverseChildren:function(){var node=null;var childDomNodes=[];while((node=this.contentDiv.firstChild)){this.contentDiv.removeChild(node);childDomNodes.push(node);}','while((node=childDomNodes.pop())){this.contentDiv.appendChild(node);}},update:function(){if(!this.isRoot){if(this.group.expandable){removeClass(this.expander,"greyedout");}else{addClass(this.expander,"greyedout");}}},clear:function(){if(this.isRoot){this.contentDiv.innerHTML="";}}});function Group(name,isRoot,initiallyExpanded){this.name=name;this.group=null;this.isRoot=isRoot;this.initiallyExpanded=initiallyExpanded;this.elementContainers=[];this.children=[];this.expanded=initiallyExpanded;this.rendered=false;this.expandable=false;}','Group.prototype=new LogItem();copyProperties(Group.prototype,{addChild:function(logItem){this.children.push(logItem);logItem.group=this;},render:function(){if(isIe){var unwrappedDomContainer,wrappedDomContainer;if(this.isRoot){unwrappedDomContainer=logMainContainer;wrappedDomContainer=logMainContainer;}else{unwrappedDomContainer=this.getUnwrappedDomContainer();wrappedDomContainer=this.getWrappedDomContainer();}','this.unwrappedElementContainer=new GroupElementContainer(this,unwrappedDomContainer,this.isRoot,false);this.wrappedElementContainer=new GroupElementContainer(this,wrappedDomContainer,this.isRoot,true);this.elementContainers=[this.unwrappedElementContainer,this.wrappedElementContainer];}else{var mainDomContainer=this.isRoot?logMainContainer:this.getMainDomContainer();this.mainElementContainer=new GroupElementContainer(this,mainDomContainer,this.isRoot,false);this.elementContainers=[this.mainElementContainer];}','this.rendered=true;},toggleExpanded:function(){this.expanded=!this.expanded;for(var i=0,len=this.elementContainers.length;i<len;i++){this.elementContainers[i].toggleExpanded();}},expand:function(){if(!this.expanded){this.toggleExpanded();}},accept:function(visitor){visitor.visitGroup(this);},reverseChildren:function(){if(this.rendered){for(var i=0,len=this.elementContainers.length;i<len;i++){this.elementContainers[i].reverseChildren();}}},update:function(){var previouslyExpandable=this.expandable;this.expandable=(this.children.length!==0);if(this.expandable!==previouslyExpandable){for(var i=0,len=this.elementContainers.length;i<len;i++){this.elementContainers[i].update();}}},flatten:function(){var visitor=new GroupFlattener();this.accept(visitor);return visitor.logEntriesAndSeparators;},removeChild:function(child,doUpdate){array_remove(this.children,child);child.group=null;if(doUpdate){this.update();}},remove:function(doUpdate,removeFromGroup){for(var i=0,len=this.children.length;i<len;i++){this.children[i].remove(false,false);}','this.children=[];this.update();if(this===currentGroup){currentGroup=this.group;}','this.doRemove(doUpdate,removeFromGroup);},serialize:function(items){items.push([LogItem.serializedItemKeys.GROUP_START,this.name]);for(var i=0,len=this.children.length;i<len;i++){this.children[i].serialize(items);}','if(this!==currentGroup){items.push([LogItem.serializedItemKeys.GROUP_END]);}},clear:function(){for(var i=0,len=this.elementContainers.length;i<len;i++){this.elementContainers[i].clear();}}});function LogEntryElementContainer(){}','LogEntryElementContainer.prototype=new LogItemContainerElement();copyProperties(LogEntryElementContainer.prototype,{remove:function(){this.doRemove();},doRemove:function(){this.mainDiv.parentNode.removeChild(this.mainDiv);this.mainDiv=null;this.contentElement=null;this.containerDomNode=null;},setContent:function(content,wrappedContent){if(content===this.formattedMessage){this.contentElement.innerHTML="";this.contentElement.appendChild(document.createTextNode(this.formattedMessage));}else{this.contentElement.innerHTML=content;}},setSearchMatch:function(isMatch){var oldCssClass=isMatch?"searchnonmatch":"searchmatch";var newCssClass=isMatch?"searchmatch":"searchnonmatch";replaceClass(this.mainDiv,newCssClass,oldCssClass);},clearSearch:function(){removeClass(this.mainDiv,"searchmatch");removeClass(this.mainDiv,"searchnonmatch");}});
196function LogEntryWrappedElementContainer(logEntry,containerDomNode){this.logEntry=logEntry;this.containerDomNode=containerDomNode;this.mainDiv=document.createElement("div");this.mainDiv.appendChild(document.createTextNode(this.logEntry.formattedMessage));this.mainDiv.className="logentry wrapped "+this.logEntry.level;this.contentElement=this.mainDiv;}','LogEntryWrappedElementContainer.prototype=new LogEntryElementContainer();LogEntryWrappedElementContainer.prototype.setContent=function(content,wrappedContent){if(content===this.formattedMessage){this.contentElement.innerHTML="";this.contentElement.appendChild(document.createTextNode(this.formattedMessage));}else{this.contentElement.innerHTML=wrappedContent;}};function LogEntryUnwrappedElementContainer(logEntry,containerDomNode){this.logEntry=logEntry;this.containerDomNode=containerDomNode;this.mainDiv=document.createElement("div");this.mainDiv.className="logentry unwrapped "+this.logEntry.level;this.pre=this.mainDiv.appendChild(document.createElement("pre"));this.pre.appendChild(document.createTextNode(this.logEntry.formattedMessage));this.pre.className="unwrapped";this.contentElement=this.pre;}','LogEntryUnwrappedElementContainer.prototype=new LogEntryElementContainer();LogEntryUnwrappedElementContainer.prototype.remove=function(){this.doRemove();this.pre=null;};function LogEntryMainElementContainer(logEntry,containerDomNode){this.logEntry=logEntry;this.containerDomNode=containerDomNode;this.mainDiv=document.createElement("div");this.mainDiv.className="logentry nonielogentry "+this.logEntry.level;this.contentElement=this.mainDiv.appendChild(document.createElement("span"));this.contentElement.appendChild(document.createTextNode(this.logEntry.formattedMessage));}','LogEntryMainElementContainer.prototype=new LogEntryElementContainer();function LogEntry(level,formattedMessage){this.level=level;this.formattedMessage=formattedMessage;this.rendered=false;}','LogEntry.prototype=new LogItem();copyProperties(LogEntry.prototype,{render:function(){var logEntry=this;var containerDomNode=this.group.contentDiv;if(isIe){this.formattedMessage=this.formattedMessage.replace(/\\r\\n/g,"\\r");this.unwrappedElementContainer=new LogEntryUnwrappedElementContainer(this,this.getUnwrappedDomContainer());this.wrappedElementContainer=new LogEntryWrappedElementContainer(this,this.getWrappedDomContainer());this.elementContainers=[this.unwrappedElementContainer,this.wrappedElementContainer];}else{this.mainElementContainer=new LogEntryMainElementContainer(this,this.getMainDomContainer());this.elementContainers=[this.mainElementContainer];}','this.content=this.formattedMessage;this.rendered=true;},setContent:function(content,wrappedContent){if(content!=this.content){if(isIe&&(content!==this.formattedMessage)){content=content.replace(/\\r\\n/g,"\\r");}','for(var i=0,len=this.elementContainers.length;i<len;i++){this.elementContainers[i].setContent(content,wrappedContent);}','this.content=content;}},getSearchMatches:function(){var matches=[];var i,len;if(isIe){var unwrappedEls=getElementsByClass(this.unwrappedElementContainer.mainDiv,"searchterm","span");var wrappedEls=getElementsByClass(this.wrappedElementContainer.mainDiv,"searchterm","span");for(i=0,len=unwrappedEls.length;i<len;i++){matches[i]=new Match(this.level,null,unwrappedEls[i],wrappedEls[i]);}}else{var els=getElementsByClass(this.mainElementContainer.mainDiv,"searchterm","span");for(i=0,len=els.length;i<len;i++){matches[i]=new Match(this.level,els[i]);}}','return matches;},setSearchMatch:function(isMatch){for(var i=0,len=this.elementContainers.length;i<len;i++){this.elementContainers[i].setSearchMatch(isMatch);}},clearSearch:function(){for(var i=0,len=this.elementContainers.length;i<len;i++){this.elementContainers[i].clearSearch();}},accept:function(visitor){visitor.visitLogEntry(this);},serialize:function(items){items.push([LogItem.serializedItemKeys.LOG_ENTRY,this.level,this.formattedMessage]);}});function LogItemVisitor(){}','LogItemVisitor.prototype={visit:function(logItem){},visitParent:function(logItem){if(logItem.group){logItem.group.accept(this);}},visitChildren:function(logItem){for(var i=0,len=logItem.children.length;i<len;i++){logItem.children[i].accept(this);}}
196,visitLogEntry:function(logEntry){this.visit(logEntry);},visitSeparator:function(separator){this.visit(separator);},visitGroup:function(group){this.visit(group);}};function GroupFlattener(){this.logEntriesAndSeparators=[];}','GroupFlattener.prototype=new LogItemVisitor();GroupFlattener.prototype.visitGroup=function(group){this.visitChildren(group);};GroupFlattener.prototype.visitLogEntry=function(logEntry){this.logEntriesAndSeparators.push(logEntry);};GroupFlattener.prototype.visitSeparator=function(separator){this.logEntriesAndSeparators.push(separator);};window.onload=function(){if(location.search){var queryBits=unescape(location.search).substr(1).split("&"),nameValueBits;for(var i=0,len=queryBits.length;i<len;i++){nameValueBits=queryBits[i].split("=");if(nameValueBits[0]=="log4javascript_domain"){document.domain=nameValueBits[1];break;}}}','logMainContainer=$("log");if(isIePre7){addClass(logMainContainer,"oldIe");}','rootGroup=new Group("root",true);rootGroup.render();currentGroup=rootGroup;setCommandInputWidth();setLogContainerHeight();toggleLoggingEnabled();toggleSearchEnabled();toggleSearchFilter();toggleSearchHighlight();applyFilters();checkAllLevels();toggleWrap();toggleNewestAtTop();toggleScrollToLatest();renderQueuedLogItems();loaded=true;$("command").value="";$("command").autocomplete="off";$("command").onkeydown=function(evt){evt=getEvent(evt);if(evt.keyCode==10||evt.keyCode==13){evalCommandLine();stopPropagation(evt);}else if(evt.keyCode==27){this.value="";this.focus();}else if(evt.keyCode==38&&commandHistory.length>0){currentCommandIndex=Math.max(0,currentCommandIndex-1);this.value=commandHistory[currentCommandIndex];moveCaretToEnd(this);}else if(evt.keyCode==40&&commandHistory.length>0){currentCommandIndex=Math.min(commandHistory.length-1,currentCommandIndex+1);this.value=commandHistory[currentCommandIndex];moveCaretToEnd(this);}};$("command").onkeypress=function(evt){evt=getEvent(evt);if(evt.keyCode==38&&commandHistory.length>0&&evt.preventDefault){evt.preventDefault();}};$("command").onkeyup=function(evt){evt=getEvent(evt);if(evt.keyCode==27&&evt.preventDefault){evt.preventDefault();this.focus();}};document.onkeydown=function keyEventHandler(evt){evt=getEvent(evt);switch(evt.keyCode){case 69:if(evt.shiftKey&&(evt.ctrlKey||evt.metaKey)){evalLastCommand();cancelKeyEvent(evt);return false;}','break;case 75:if(evt.shiftKey&&(evt.ctrlKey||evt.metaKey)){focusSearch();cancelKeyEvent(evt);return false;}','break;case 40:case 76:if(evt.shiftKey&&(evt.ctrlKey||evt.metaKey)){focusCommandLine();cancelKeyEvent(evt);return false;}','break;}};setTimeout(setLogContainerHeight,20);setShowCommandLine(showCommandLine);doSearch();};window.onunload=function(){if(mainWindowExists()){appender.unload();}','appender=null;};function toggleLoggingEnabled(){setLoggingEnabled($("enableLogging").checked);}','function setLoggingEnabled(enable){loggingEnabled=enable;}','var appender=null;function setAppender(appenderParam){appender=appenderParam;}','function setShowCloseButton(showCloseButton){$("closeButton").style.display=showCloseButton?"inline":"none";}','function setShowHideButton(showHideButton){$("hideButton").style.display=showHideButton?"inline":"none";}','var newestAtTop=false;function LogItemContentReverser(){}','LogItemContentReverser.prototype=new LogItemVisitor();LogItemContentReverser.prototype.visitGroup=function(group){group.reverseChildren();this.visitChildren(group);};function setNewestAtTop(isNewestAtTop){var oldNewestAtTop=newestAtTop;var i,iLen,j,jLen;newestAtTop=Boolean(isNewestAtTop);if(oldNewestAtTop!=newestAtTop){var visitor=new LogItemContentReverser();rootGroup.accept(visitor);if(currentSearch){var currentMatch=currentSearch.matches[currentMatchIndex];var matchIndex=0;var matches=[];var actOnLogEntry=function(logEntry){var logEntryMatches=logEntry.getSearchMatches();for(j=0,jLen=logEntryMatches.length;j<jLen;j++){matches[matchIndex]=logEntryMatches[j];if(currentMatch&&logEntryMatches[j].equals(currentMatch)){currentMatchIndex=matchIndex;}','matchIndex++;}};if(newestAtTop){for(i=logEntries.length-1;i>=0;i--){actOnLogEntry(logEntries[i]);}}
196else{for(i=0,iLen=logEntries.length;i<iLen;i++){actOnLogEntry(logEntries[i]);}}','currentSearch.matches=matches;if(currentMatch){currentMatch.setCurrent();}}else if(scrollToLatest){doScrollToLatest();}}','$("newestAtTop").checked=isNewestAtTop;}','function toggleNewestAtTop(){var isNewestAtTop=$("newestAtTop").checked;setNewestAtTop(isNewestAtTop);}','var scrollToLatest=true;function setScrollToLatest(isScrollToLatest){scrollToLatest=isScrollToLatest;if(scrollToLatest){doScrollToLatest();}','$("scrollToLatest").checked=isScrollToLatest;}','function toggleScrollToLatest(){var isScrollToLatest=$("scrollToLatest").checked;setScrollToLatest(isScrollToLatest);}','function doScrollToLatest(){var l=logMainContainer;if(typeof l.scrollTop!="undefined"){if(newestAtTop){l.scrollTop=0;}else{var latestLogEntry=l.lastChild;if(latestLogEntry){l.scrollTop=l.scrollHeight;}}}}','var closeIfOpenerCloses=true;function setCloseIfOpenerCloses(isCloseIfOpenerCloses){closeIfOpenerCloses=isCloseIfOpenerCloses;}','var maxMessages=null;function setMaxMessages(max){maxMessages=max;pruneLogEntries();}','var showCommandLine=false;function setShowCommandLine(isShowCommandLine){showCommandLine=isShowCommandLine;if(loaded){$("commandLine").style.display=showCommandLine?"block":"none";setCommandInputWidth();setLogContainerHeight();}}','function focusCommandLine(){if(loaded){$("command").focus();}}','function focusSearch(){if(loaded){$("searchBox").focus();}}','function getLogItems(){var items=[];for(var i=0,len=logItems.length;i<len;i++){logItems[i].serialize(items);}','return items;}','function setLogItems(items){var loggingReallyEnabled=loggingEnabled;loggingEnabled=true;for(var i=0,len=items.length;i<len;i++){switch(items[i][0]){case LogItem.serializedItemKeys.LOG_ENTRY:log(items[i][1],items[i][2]);break;case LogItem.serializedItemKeys.GROUP_START:group(items[i][1]);break;case LogItem.serializedItemKeys.GROUP_END:groupEnd();break;}}','loggingEnabled=loggingReallyEnabled;}','function log(logLevel,formattedMessage){if(loggingEnabled){var logEntry=new LogEntry(logLevel,formattedMessage);logEntries.push(logEntry);logEntriesAndSeparators.push(logEntry);logItems.push(logEntry);currentGroup.addChild(logEntry);if(loaded){if(logQueuedEventsTimer!==null){clearTimeout(logQueuedEventsTimer);}','logQueuedEventsTimer=setTimeout(renderQueuedLogItems,renderDelay);unrenderedLogItemsExist=true;}}}','function renderQueuedLogItems(){logQueuedEventsTimer=null;var pruned=pruneLogEntries();var initiallyHasMatches=currentSearch?currentSearch.hasMatches():false;for(var i=0,len=logItems.length;i<len;i++){if(!logItems[i].rendered){logItems[i].render();logItems[i].appendToLog();if(currentSearch&&(logItems[i]instanceof LogEntry)){currentSearch.applyTo(logItems[i]);}}}','if(currentSearch){if(pruned){if(currentSearch.hasVisibleMatches()){if(currentMatchIndex===null){setCurrentMatchIndex(0);}','displayMatches();}else{displayNoMatches();}}else if(!initiallyHasMatches&&currentSearch.hasVisibleMatches()){setCurrentMatchIndex(0);displayMatches();}}','if(scrollToLatest){doScrollToLatest();}','unrenderedLogItemsExist=false;}','function pruneLogEntries(){if((maxMessages!==null)&&(logEntriesAndSeparators.length>maxMessages)){var numberToDelete=logEntriesAndSeparators.length-maxMessages;var prunedLogEntries=logEntriesAndSeparators.slice(0,numberToDelete);if(currentSearch){currentSearch.removeMatches(prunedLogEntries);}','var group;for(var i=0;i<numberToDelete;i++){group=logEntriesAndSeparators[i].group;array_remove(logItems,logEntriesAndSeparators[i]);array_remove(logEntries,logEntriesAndSeparators[i]);logEntriesAndSeparators[i].remove(true,true);if(group.children.length===0&&group!==currentGroup&&group!==rootGroup){array_remove(logItems,group);group.remove(true,true);}}','logEntriesAndSeparators=array_removeFromStart(logEntriesAndSeparators,numberToDelete);return true;}','return false;}','function group(name,startExpanded){if(loggingEnabled){initiallyExpanded=(typeof startExpanded==="undefined")?true:Boolean(startExpanded);var newGroup=new Group(name,false,initiallyExpanded);currentGroup.addChild(newGroup);currentGroup=newGroup;logItems.push(newGroup);if(loaded){if(logQueuedEventsTimer!==null){clearTimeout(logQueuedEventsTimer);}','logQueuedEventsTimer=setTimeout(renderQueuedLogItems,renderDelay);unrenderedLogItemsExist=true;}}}','function groupEnd(){currentGroup=(currentGroup===rootGroup)?rootGroup:currentGroup.group;}','function mainPageReloaded(){currentGroup=rootGroup;var separator=new Separator();logEntriesAndSeparators.push(separator);logItems.push(separator);currentGroup.addChild(separator);}','function closeWindow(){if(appender&&mainWindowExists()){appender.close(true);}else{window.close();}}
196','function hide(){if(appender&&mainWindowExists()){appender.hide();}}','var mainWindow=window;var windowId="log4javascriptConsoleWindow_"+new Date().getTime()+"_"+(""+Math.random()).substr(2);function setMainWindow(win){mainWindow=win;mainWindow[windowId]=window;if(opener&&closeIfOpenerCloses){pollOpener();}}','function pollOpener(){if(closeIfOpenerCloses){if(mainWindowExists()){setTimeout(pollOpener,500);}else{closeWindow();}}}','function mainWindowExists(){try{return(mainWindow&&!mainWindow.closed&&mainWindow[windowId]==window);}catch(ex){}','return false;}','var logLevels=["TRACE","DEBUG","INFO","WARN","ERROR","FATAL"];function getCheckBox(logLevel){return $("switch_"+logLevel);}','function getIeWrappedLogContainer(){return $("log_wrapped");}','function getIeUnwrappedLogContainer(){return $("log_unwrapped");}','function applyFilters(){for(var i=0;i<logLevels.length;i++){if(getCheckBox(logLevels[i]).checked){addClass(logMainContainer,logLevels[i]);}else{removeClass(logMainContainer,logLevels[i]);}}','updateSearchFromFilters();}','function toggleAllLevels(){var turnOn=$("switch_ALL").checked;for(var i=0;i<logLevels.length;i++){getCheckBox(logLevels[i]).checked=turnOn;if(turnOn){addClass(logMainContainer,logLevels[i]);}else{removeClass(logMainContainer,logLevels[i]);}}}','function checkAllLevels(){for(var i=0;i<logLevels.length;i++){if(!getCheckBox(logLevels[i]).checked){getCheckBox("ALL").checked=false;return;}}','getCheckBox("ALL").checked=true;}','function clearLog(){rootGroup.clear();currentGroup=rootGroup;logEntries=[];logItems=[];logEntriesAndSeparators=[];doSearch();}','function toggleWrap(){var enable=$("wrap").checked;if(enable){addClass(logMainContainer,"wrap");}else{removeClass(logMainContainer,"wrap");}','refreshCurrentMatch();}','var searchTimer=null;function scheduleSearch(){try{clearTimeout(searchTimer);}catch(ex){}','searchTimer=setTimeout(doSearch,500);}','function Search(searchTerm,isRegex,searchRegex,isCaseSensitive){this.searchTerm=searchTerm;this.isRegex=isRegex;this.searchRegex=searchRegex;this.isCaseSensitive=isCaseSensitive;this.matches=[];}','Search.prototype={hasMatches:function(){return this.matches.length>0;},hasVisibleMatches:function(){if(this.hasMatches()){for(var i=0;i<this.matches.length;i++){if(this.matches[i].isVisible()){return true;}}}','return false;},match:function(logEntry){var entryText=String(logEntry.formattedMessage);var matchesSearch=false;if(this.isRegex){matchesSearch=this.searchRegex.test(entryText);}else if(this.isCaseSensitive){matchesSearch=(entryText.indexOf(this.searchTerm)>-1);}else{matchesSearch=(entryText.toLowerCase().indexOf(this.searchTerm.toLowerCase())>-1);}','return matchesSearch;},getNextVisibleMatchIndex:function(){for(var i=currentMatchIndex+1;i<this.matches.length;i++){if(this.matches[i].isVisible()){return i;}}','for(i=0;i<=currentMatchIndex;i++){if(this.matches[i].isVisible()){return i;}}','return-1;},getPreviousVisibleMatchIndex:function(){for(var i=currentMatchIndex-1;i>=0;i--){if(this.matches[i].isVisible()){return i;}}','for(var i=this.matches.length-1;i>=currentMatchIndex;i--){if(this.matches[i].isVisible()){return i;}}','return-1;},applyTo:function(logEntry){var doesMatch=this.match(logEntry);if(doesMatch){logEntry.group.expand();logEntry.setSearchMatch(true);var logEntryContent;var wrappedLogEntryContent;var searchTermReplacementStartTag="<span class=\\\"searchterm\\\">";var searchTermReplacementEndTag="<"+"/span>";var preTagName=isIe?"pre":"span";var preStartTag="<"+preTagName+" class=\\\"pre\\\">";var preEndTag="<"+"/"+preTagName+">";var startIndex=0;var searchIndex,matchedText,textBeforeMatch;if(this.isRegex){var flags=this.isCaseSensitive?"g":"gi";var capturingRegex=new RegExp("("+this.searchRegex.source+")",flags);var rnd=(""+Math.random()).substr(2);var startToken="%%s"+rnd+"%%";var endToken="%%e"+rnd+"%%";logEntryContent=logEntry.formattedMessage.replace(capturingRegex,startToken+"$1"+endToken);logEntryContent=escapeHtml(logEntryContent);var result;var searchString=logEntryContent;logEntryContent="";wrappedLogEntryContent="";while((searchIndex=searchString.indexOf(startToken,startIndex))>-1){var endTokenIndex=searchString.indexOf(endToken,searchIndex);matchedText=searchString.substring(searchIndex+startToken.length,endTokenIndex);textBeforeMatch=searchString.substring(startIndex,searchIndex);logEntryContent+=preStartTag+textBeforeMatch+preEndTag;logEntryContent+=searchTermReplacementStartTag+preStartTag+matchedText+','preEndTag+searchTermReplacementEndTag;if(isIe){wrappedLogEntryContent+=textBeforeMatch+searchTermReplacementStartTag+','matchedText+searchTermReplacementEndTag;}','startIndex=endTokenIndex+endToken.length;}','logEntryContent+=preStartTag+searchString.substr(startIndex)+preEndTag;if(isIe){wrappedLogEntryContent+=searchString.substr(startIndex);}}else{logEntryContent="";wrappedLogEntryContent="";var searchTermReplacementLength=searchTermReplacementStartTag.length+','this.searchTerm.length+searchTermReplacementEndTag.length;var searchTermLength=this.searchTerm.length;var searchTermLowerCase=this.searchTerm.toLowerCase();var logTextLowerCase=logEntry.formattedMessage.toLowerCase();while((searchIndex=logTextLowerCase.indexOf(searchTermLowerCase,startIndex))>-1){matchedText=escapeHtml(logEntry.formattedMessage.substr(searchIndex,this.searchTerm.length));textBeforeMatch=escapeHtml(logEntry.formattedMessage.substring(startIndex,searchIndex));var searchTermReplacement=searchTermReplacementStartTag+','preStartTag+matchedText+preEndTag+searchTermReplacementEndTag;logEntryContent+=preStartTag+textBeforeMatch+preEndTag+searchTermReplacement;if(isIe){wrappedLogEntryContent+=textBeforeMatch+searchTermReplacementStartTag+','matchedText+searchTermReplacementEndTag;}','startIndex=searchIndex+searchTermLength;}','var textAfterLastMatch=escapeHtml(logEntry.formattedMessage.substr(startIndex));logEntryContent+=preStartTag+textAfterLastMatch+preEndTag;if(isIe){wrappedLogEntryContent+=textAfterLastMatch;}}','logEntry.setContent(logEntryContent,wrappedLogEntryContent);var logEntryMatches=logEntry.getSearchMatches();this.matches=this.matches.concat(logEntryMatches);}else{logEntry.setSearchMatch(false);logEntry.setContent(logEntry.formattedMessage,logEntry.formattedMessage);}','return doesMatch;},removeMatches:function(logEntries){var matchesToRemoveCount=0;var currentMatchRemoved=false;var matchesToRemove=[];var i,iLen,j,jLen;for(i=0,iLen=this.matches.length;i<iLen;i++){for(j=0,jLen=logEntries.length;j<jLen;j++){if(this.matches[i].belongsTo(logEntries[j])){matchesToRemove.push(this.matches[i]);if(i===currentMatchIndex){currentMatchRemoved=true;}}}}','var newMatch=currentMatchRemoved?null:this.matches[currentMatchIndex];if(currentMatchRemoved){for(i=currentMatchIndex,iLen=this.matches.length;i<iLen;i++){if(this.matches[i].isVisible()&&!array_contains(matchesToRemove,this.matches[i])){newMatch=this.matches[i];break;}}}','for(i=0,iLen=matchesToRemove.length;i<iLen;i++){array_remove(this.matches,matchesToRemove[i]);matchesToRemove[i].remove();}
196','if(this.hasVisibleMatches()){if(newMatch===null){setCurrentMatchIndex(0);}else{var newMatchIndex=0;for(i=0,iLen=this.matches.length;i<iLen;i++){if(newMatch===this.matches[i]){newMatchIndex=i;break;}}','setCurrentMatchIndex(newMatchIndex);}}else{currentMatchIndex=null;displayNoMatches();}}};function getPageOffsetTop(el,container){var currentEl=el;var y=0;while(currentEl&&currentEl!=container){y+=currentEl.offsetTop;currentEl=currentEl.offsetParent;}','return y;}','function scrollIntoView(el){var logContainer=logMainContainer;if(!$("wrap").checked){var logContainerLeft=logContainer.scrollLeft;var logContainerRight=logContainerLeft+logContainer.offsetWidth;var elLeft=el.offsetLeft;var elRight=elLeft+el.offsetWidth;if(elLeft<logContainerLeft||elRight>logContainerRight){logContainer.scrollLeft=elLeft-(logContainer.offsetWidth-el.offsetWidth)/2;}}','var logContainerTop=logContainer.scrollTop;var logContainerBottom=logContainerTop+logContainer.offsetHeight;var elTop=getPageOffsetTop(el)-getToolBarsHeight();var elBottom=elTop+el.offsetHeight;if(elTop<logContainerTop||elBottom>logContainerBottom){logContainer.scrollTop=elTop-(logContainer.offsetHeight-el.offsetHeight)/2;}}','function Match(logEntryLevel,spanInMainDiv,spanInUnwrappedPre,spanInWrappedDiv){this.logEntryLevel=logEntryLevel;this.spanInMainDiv=spanInMainDiv;if(isIe){this.spanInUnwrappedPre=spanInUnwrappedPre;this.spanInWrappedDiv=spanInWrappedDiv;}','this.mainSpan=isIe?spanInUnwrappedPre:spanInMainDiv;}','Match.prototype={equals:function(match){return this.mainSpan===match.mainSpan;},setCurrent:function(){if(isIe){addClass(this.spanInUnwrappedPre,"currentmatch");addClass(this.spanInWrappedDiv,"currentmatch");var elementToScroll=$("wrap").checked?this.spanInWrappedDiv:this.spanInUnwrappedPre;scrollIntoView(elementToScroll);}else{addClass(this.spanInMainDiv,"currentmatch");scrollIntoView(this.spanInMainDiv);}},belongsTo:function(logEntry){if(isIe){return isDescendant(this.spanInUnwrappedPre,logEntry.unwrappedPre);}else{return isDescendant(this.spanInMainDiv,logEntry.mainDiv);}},setNotCurrent:function(){if(isIe){removeClass(this.spanInUnwrappedPre,"currentmatch");removeClass(this.spanInWrappedDiv,"currentmatch");}else{removeClass(this.spanInMainDiv,"currentmatch");}},isOrphan:function(){return isOrphan(this.mainSpan);},isVisible:function(){return getCheckBox(this.logEntryLevel).checked;},remove:function(){if(isIe){this.spanInUnwrappedPre=null;this.spanInWrappedDiv=null;}else{this.spanInMainDiv=null;}}};var currentSearch=null;var currentMatchIndex=null;function doSearch(){var searchBox=$("searchBox");var searchTerm=searchBox.value;var isRegex=$("searchRegex").checked;var isCaseSensitive=$("searchCaseSensitive").checked;var i;if(searchTerm===""){$("searchReset").disabled=true;$("searchNav").style.display="none";removeClass(document.body,"searching");removeClass(searchBox,"hasmatches");removeClass(searchBox,"nomatches");for(i=0;i<logEntries.length;i++){logEntries[i].clearSearch();logEntries[i].setContent(logEntries[i].formattedMessage,logEntries[i].formattedMessage);}','currentSearch=null;setLogContainerHeight();}else{$("searchReset").disabled=false;$("searchNav").style.display="block";var searchRegex;var regexValid;if(isRegex){try{searchRegex=isCaseSensitive?new RegExp(searchTerm,"g"):new RegExp(searchTerm,"gi");regexValid=true;replaceClass(searchBox,"validregex","invalidregex");searchBox.title="Valid regex";}catch(ex){regexValid=false;replaceClass(searchBox,"invalidregex","validregex");searchBox.title="Invalid regex: "+(ex.message?ex.message:(ex.description?ex.description:"unknown error"));return;}}else{searchBox.title="";removeClass(searchBox,"validregex");removeClass(searchBox,"invalidregex");}','addClass(document.body,"searching");currentSearch=new Search(searchTerm,isRegex,searchRegex,isCaseSensitive);for(i=0;i<logEntries.length;i++){currentSearch.applyTo(logEntries[i]);}','setLogContainerHeight();if(currentSearch.hasVisibleMatches()){setCurrentMatchIndex(0);displayMatches();}else{displayNoMatches();}}}','function updateSearchFromFilters(){if(currentSearch){if(currentSearch.hasMatches()){if(currentMatchIndex===null){currentMatchIndex=0;}','var currentMatch=currentSearch.matches[currentMatchIndex];if(currentMatch.isVisible()){displayMatches();setCurrentMatchIndex(currentMatchIndex);}else{currentMatch.setNotCurrent();var nextVisibleMatchIndex=currentSearch.getNextVisibleMatchIndex();if(nextVisibleMatchIndex>-1){setCurrentMatchIndex(nextVisibleMatchIndex);displayMatches();}else{displayNoMatches();}}}else{displayNoMatches();}}}','function refreshCurrentMatch(){if(currentSearch&&currentSearch.hasVisibleMatches()){setCurrentMatchIndex(currentMatchIndex);}}','function displayMatches(){replaceClass($("searchBox"),"hasmatches","nomatches");$("searchBox").title=""+currentSearch.matches.length+" matches found";$("searchNav").style.display="block";
196setLogContainerHeight();}','function displayNoMatches(){replaceClass($("searchBox"),"nomatches","hasmatches");$("searchBox").title="No matches found";$("searchNav").style.display="none";setLogContainerHeight();}','function toggleSearchEnabled(enable){enable=(typeof enable=="undefined")?!$("searchDisable").checked:enable;$("searchBox").disabled=!enable;$("searchReset").disabled=!enable;$("searchRegex").disabled=!enable;$("searchNext").disabled=!enable;$("searchPrevious").disabled=!enable;$("searchCaseSensitive").disabled=!enable;$("searchNav").style.display=(enable&&($("searchBox").value!=="")&&currentSearch&&currentSearch.hasVisibleMatches())?"block":"none";if(enable){removeClass($("search"),"greyedout");addClass(document.body,"searching");if($("searchHighlight").checked){addClass(logMainContainer,"searchhighlight");}else{removeClass(logMainContainer,"searchhighlight");}','if($("searchFilter").checked){addClass(logMainContainer,"searchfilter");}else{removeClass(logMainContainer,"searchfilter");}','$("searchDisable").checked=!enable;}else{addClass($("search"),"greyedout");removeClass(document.body,"searching");removeClass(logMainContainer,"searchhighlight");removeClass(logMainContainer,"searchfilter");}','setLogContainerHeight();}','function toggleSearchFilter(){var enable=$("searchFilter").checked;if(enable){addClass(logMainContainer,"searchfilter");}else{removeClass(logMainContainer,"searchfilter");}','refreshCurrentMatch();}','function toggleSearchHighlight(){var enable=$("searchHighlight").checked;if(enable){addClass(logMainContainer,"searchhighlight");}else{removeClass(logMainContainer,"searchhighlight");}}','function clearSearch(){$("searchBox").value="";doSearch();}','function searchNext(){if(currentSearch!==null&&currentMatchIndex!==null){currentSearch.matches[currentMatchIndex].setNotCurrent();var nextMatchIndex=currentSearch.getNextVisibleMatchIndex();if(nextMatchIndex>currentMatchIndex||confirm("Reached the end of the page. Start from the top?")){setCurrentMatchIndex(nextMatchIndex);}}}','function searchPrevious(){if(currentSearch!==null&&currentMatchIndex!==null){currentSearch.matches[currentMatchIndex].setNotCurrent();var previousMatchIndex=currentSearch.getPreviousVisibleMatchIndex();if(previousMatchIndex<currentMatchIndex||confirm("Reached the start of the page. Continue from the bottom?")){setCurrentMatchIndex(previousMatchIndex);}}}','function setCurrentMatchIndex(index){currentMatchIndex=index;currentSearch.matches[currentMatchIndex].setCurrent();}','function addClass(el,cssClass){if(!hasClass(el,cssClass)){if(el.className){el.className+=" "+cssClass;}else{el.className=cssClass;}}}','function hasClass(el,cssClass){if(el.className){var classNames=el.className.split(" ");return array_contains(classNames,cssClass);}','return false;}','function removeClass(el,cssClass){if(hasClass(el,cssClass)){var existingClasses=el.className.split(" ");var newClasses=[];for(var i=0,len=existingClasses.length;i<len;i++){if(existingClasses[i]!=cssClass){newClasses[newClasses.length]=existingClasses[i];}}','el.className=newClasses.join(" ");}}','function replaceClass(el,newCssClass,oldCssClass){removeClass(el,oldCssClass);addClass(el,newCssClass);}','function getElementsByClass(el,cssClass,tagName){var elements=el.getElementsByTagName(tagName);var matches=[];for(var i=0,len=elements.length;i<len;i++){if(hasClass(elements[i],cssClass)){matches.push(elements[i]);}}','return matches;}','function $(id){return document.getElementById(id);}','function isDescendant(node,ancestorNode){while(node!=null){if(node===ancestorNode){return true;}','node=node.parentNode;}','return false;}','function isOrphan(node){var currentNode=node;while(currentNode){if(currentNode==document.body){return false;}','currentNode=currentNode.parentNode;}','return true;}','function escapeHtml(str){return str.replace(/&/g,"&amp;").replace(/[<]/g,"&lt;").replace(/>/g,"&gt;");}','function getWindowWidth(){if(window.innerWidth){return window.innerWidth;}else if(document.documentElement&&document.documentElement.clientWidth){return document.documentElement.clientWidth;}else if(document.body){return document.body.clientWidth;}','return 0;}','function getWindowHeight(){if(window.innerHeight){return window.innerHeight;}else if(document.documentElement&&document.documentElement.clientHeight){return document.documentElement.clientHeight;}else if(document.body){return document.body.clientHeight;}','return 0;}','function getToolBarsHeight(){return $("switches").offsetHeight;}','function getChromeHeight(){var height=getToolBarsHeight();if(showCommandLine){height+=$("commandLine").offsetHeight;}','return height;}','function setLogContainerHeight(){if(logMainContainer){var windowHeight=getWindowHeight();$("body").style.height=getWindowHeight()+"px";logMainContainer.style.height=""+','Math.max(0,windowHeight-getChromeHeight())+"px";}}','function setCommandInputWidth(){if(showCommandLine){$("command").style.width=""+Math.max(0,$("commandLineContainer").offsetWidth-','($("evaluateButton").offsetWidth+13))+"px";}}','window.onresize=function(){setCommandInputWidth();setLogContainerHeight();};if(!Array.prototype.push){Array.prototype.push=function(){for(var i=0,len=arguments.length;i<len;i++){this[this.length]=arguments[i];}','return this.length;};}','if(!Array.prototype.pop){Array.prototype.pop=function(){if(this.length>0){var val=this[this.length-1];this.length=this.length-1;return val;}};}','if(!Array.prototype.shift){Array.prototype.shift=function(){if(this.length>0){var firstItem=this[0];for(var i=0,len=this.length-1;i<len;i++){this[i]=this[i+1];}','this.length=this.length-1;return firstItem;}};}','if(!Array.prototype.splice){Array.prototype.splice=function(startIndex,deleteCount){var itemsAfterDeleted=this.slice(startIndex+deleteCount);var itemsDeleted=this.slice(startIndex,startIndex+deleteCount);this.length=startIndex;var argumentsArray=[];for(var i=0,len=arguments.length;i<len;i++){argumentsArray[i]=arguments[i];}','var itemsToAppend=(argumentsArray.length>2)?itemsAfterDeleted=argumentsArray.slice(2).concat(itemsAfterDeleted):itemsAfterDeleted;for(i=0,len=itemsToAppend.length;i<len;i++){this.push(itemsToAppend[i]);}','return itemsDeleted;};}','function array_remove(arr,val){var index=-1;for(var i=0,len=arr.length;i<len;i++){if(arr[i]===val){index=i;break;}}','if(index>=0){arr.splice(index,1);return index;}else{return false;}}','function array_removeFromStart(array,numberToRemove){if(Array.prototype.splice){array.splice(0,numberToRemove);}else{for(var i=numberToRemove,len=array.length;i<len;i++){array[i-numberToRemove]=array[i];}','array.length=array.length-numberToRemove;}','return array;}','function array_contains(arr,val){for(var i=0,len=arr.length;i<len;i++){if(arr[i]==val){return true;}}','return false;}','function getErrorMessage(ex){if(ex.message){return ex.message;}else if(ex.description){return ex.description;}','return""+ex;}','function moveCaretToEnd(input){if(input.setSelectionRange){input.focus();var length=input.value.length;input.setSelectionRange(length,length);}else if(input.createTextRange){var range=input.createTextRange();range.collapse(false);range.select();}
196','input.focus();}','function stopPropagation(evt){if(evt.stopPropagation){evt.stopPropagation();}else if(typeof evt.cancelBubble!="undefined"){evt.cancelBubble=true;}}','function getEvent(evt){return evt?evt:event;}','function getTarget(evt){return evt.target?evt.target:evt.srcElement;}','function getRelatedTarget(evt){if(evt.relatedTarget){return evt.relatedTarget;}else if(evt.srcElement){switch(evt.type){case"mouseover":return evt.fromElement;case"mouseout":return evt.toElement;default:return evt.srcElement;}}}','function cancelKeyEvent(evt){evt.returnValue=false;stopPropagation(evt);}','function evalCommandLine(){var expr=$("command").value;evalCommand(expr);$("command").value="";}','function evalLastCommand(){if(lastCommand!=null){evalCommand(lastCommand);}}','var lastCommand=null;var commandHistory=[];var currentCommandIndex=0;function evalCommand(expr){if(appender){appender.evalCommandAndAppend(expr);}else{var prefix=">>> "+expr+"\\r\\n";try{log("INFO",prefix+eval(expr));}catch(ex){log("ERROR",prefix+"Error: "+getErrorMessage(ex));}}','if(expr!=commandHistory[commandHistory.length-1]){commandHistory.push(expr);if(appender){appender.storeCommandHistory(commandHistory);}}','currentCommandIndex=(expr==commandHistory[currentCommandIndex])?currentCommandIndex+1:commandHistory.length;lastCommand=expr;}','//]]>','</script>','<style type="text/css">','body{background-color:white;color:black;padding:0;margin:0;font-family:tahoma,verdana,arial,helvetica,sans-serif;overflow:hidden}div#switchesContainer input{margin-bottom:0}div.toolbar{border-top:solid #ffffff 1px;border-bottom:solid #aca899 1px;background-color:#f1efe7;padding:3px 5px;font-size:68.75%}div.toolbar,div#search input{font-family:tahoma,verdana,arial,helvetica,sans-serif}div.toolbar input.button{padding:0 5px;font-size:100%}div.toolbar input.hidden{display:none}div#switches input#clearButton{margin-left:20px}div#levels label{font-weight:bold}div#levels label,div#options label{margin-right:5px}div#levels label#wrapLabel{font-weight:normal}div#search label{margin-right:10px}div#search label.searchboxlabel{margin-right:0}div#search input{font-size:100%}div#search input.validregex{color:green}div#search input.invalidregex{color:red}div#search input.nomatches{color:white;background-color:#ff6666}div#search input.nomatches{color:white;background-color:#ff6666}div#searchNav{display:none}div#commandLine{display:none}div#commandLine input#command{font-size:100%;font-family:Courier New,Courier}div#commandLine input#evaluateButton{}*.greyedout{color:gray !important;border-color:gray !important}*.greyedout *.alwaysenabled{color:black}*.unselectable{-khtml-user-select:none;-moz-user-select:none;user-select:none}div#log{font-family:Courier New,Courier;font-size:75%;width:100%;overflow:auto;clear:both;position:relative}div.group{border-color:#cccccc;border-style:solid;border-width:1px 0 1px 1px;overflow:visible}div.oldIe div.group,div.oldIe div.group *,div.oldIe *.logentry{height:1%}div.group div.groupheading span.expander{border:solid black 1px;font-family:Courier New,Courier;font-size:0.833em;background-color:#eeeeee;position:relative;top:-1px;color:black;padding:0 2px;cursor:pointer;cursor:hand;height:1%}div.group div.groupcontent{margin-left:10px;padding-bottom:2px;overflow:visible}div.group div.expanded{display:block}div.group div.collapsed{display:none}*.logentry{overflow:visible;display:none;white-space:pre}span.pre{white-space:pre}pre.unwrapped{display:inline !important}pre.unwrapped pre.pre,div.wrapped pre.pre{display:inline}div.wrapped pre.pre{white-space:normal}div.wrapped{display:none}body.searching *.logentry span.currentmatch{color:white !important;background-color:green !important}body.searching div.searchhighlight *.logentry span.searchterm{color:black;background-color:yellow}div.wrap *.logentry{white-space:normal !important;border-width:0 0 1px 0;border-color:#dddddd;border-style:dotted}div.wrap #log_wrapped,#log_unwrapped{display:block}div.wrap #log_unwrapped,#log_wrapped{display:none}div.wrap *.logentry span.pre{overflow:visible;white-space:normal}div.wrap *.logentry pre.unwrapped{display:none}div.wrap *.logentry span.wrapped{display:inline}div.searchfilter *.searchnonmatch{display:none !important}div#log *.TRACE,label#label_TRACE{color:#666666}div#log *.DEBUG,label#label_DEBUG{color:green}div#log *.INFO,label#label_INFO{color:#000099}div#log *.WARN,label#label_WARN{color:#999900}div#log *.ERROR,label#label_ERROR{color:red}div#log *.FATAL,label#label_FATAL{color:#660066}div.TRACE#log *.TRACE,div.DEBUG#log *.DEBUG,div.INFO#log *.INFO,div.WARN#log *.WARN,div.ERROR#log *.ERROR,div.FATAL#log *.FATAL{display:block}div#log div.separator{background-color:#cccccc;margin:5px 0;line-height:1px}','</style>','</head>','<body id="body">','<div id="switchesContainer">','<div id="switches">','<div id="levels" class="toolbar">','Filters:','<input type="checkbox" id="switch_TRACE" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide trace messages" /><label for="switch_TRACE" id="label_TRACE">trace</label>','<input type="checkbox" id="switch_DEBUG" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide debug messages" /><label for="switch_DEBUG" id="label_DEBUG">debug</label>','<input type="checkbox" id="switch_INFO" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide info messages" /><label for="switch_INFO" id="label_INFO">info</label>','<input type="checkbox" id="switch_WARN" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide warn messages" /><label for="switch_WARN" id="label_WARN">warn</label>','<input type="checkbox" id="switch_ERROR" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide error messages" /><label for="switch_ERROR" id="label_ERROR">error</label>','<input type="checkbox" id="switch_FATAL" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide fatal messages" /><label for="switch_FATAL" id="label_FATAL">fatal</label>','<input type="checkbox" id="switch_ALL" onclick="toggleAllLevels(); applyFilters()" checked="checked" title="Show/hide all messages" /><label for="switch_ALL" id="label_ALL">all</label>','</div>','<div id="search" class="toolbar">','<label for="searchBox" class="searchboxlabel">Search:</label> <input type="text" id="searchBox" onclick="toggleSearchEnabled(true)" onkeyup="scheduleSearch()" size="20" />','<input type="button" id="searchReset" disabled="disabled" value="Reset" onclick="clearSearch()" class="button" title="Reset the search" />','<input type="checkbox" id="searchRegex" onclick="doSearch()" title="If checked, search is treated as a regular expression" /><label for="searchRegex">Regex</label>','<input type="checkbox" id="searchCaseSensitive" onclick="doSearch()" title="If checked, search is case sensitive" /><label for="searchCaseSensitive">Match case</label>','<input type="checkbox" id="searchDisable" onclick="toggleSearchEnabled()" title="Enable/disable search" /><label for="searchDisable" class="alwaysenabled">Disable</label>','<div id="searchNav">','<input type="button" id="searchNext" disabled="disabled" value="Next" onclick="searchNext(
196)" class="button" title="Go to the next matching log entry" />','<input type="button" id="searchPrevious" disabled="disabled" value="Previous" onclick="searchPrevious()" class="button" title="Go to the previous matching log entry" />','<input type="checkbox" id="searchFilter" onclick="toggleSearchFilter()" title="If checked, non-matching log entries are filtered out" /><label for="searchFilter">Filter</label>','<input type="checkbox" id="searchHighlight" onclick="toggleSearchHighlight()" title="Highlight matched search terms" /><label for="searchHighlight" class="alwaysenabled">Highlight all</label>','</div>','</div>','<div id="options" class="toolbar">','Options:','<input type="checkbox" id="enableLogging" onclick="toggleLoggingEnabled()" checked="checked" title="Enable/disable logging" /><label for="enableLogging" id="enableLoggingLabel">Log</label>','<input type="checkbox" id="wrap" onclick="toggleWrap()" title="Enable / disable word wrap" /><label for="wrap" id="wrapLabel">Wrap</label>','<input type="checkbox" id="newestAtTop" onclick="toggleNewestAtTop()" title="If checked, causes newest messages to appear at the top" /><label for="newestAtTop" id="newestAtTopLabel">Newest at the top</label>','<input type="checkbox" id="scrollToLatest" onclick="toggleScrollToLatest()" checked="checked" title="If checked, window automatically scrolls to a new message when it is added" /><label for="scrollToLatest" id="scrollToLatestLabel">Scroll to latest</label>','<input type="button" id="clearButton" value="Clear" onclick="clearLog()" class="button" title="Clear all log messages"  />','<input type="button" id="hideButton" value="Hide" onclick="hide()" class="hidden button" title="Hide the console" />','<input type="button" id="closeButton" value="Close" onclick="closeWindow()" class="hidden button" title="Close the window" />','</div>','</div>','</div>','<div id="log" class="TRACE DEBUG INFO WARN ERROR FATAL"></div>','<div id="commandLine" class="toolbar">','<div id="commandLineContainer">','<input type="text" id="command" title="Enter a JavaScript command here and hit return or press \'Evaluate\'" />','<input type="button" id="evaluateButton" value="Evaluate" class="button" title="Evaluate the command" onclick="evalCommandLine()" />','</div>','</div>','</body>','</html>',''];};var defaultCommandLineFunctions=[];ConsoleAppender=function(){};
196var consoleAppenderIdCounter=1;ConsoleAppender.prototype=new Appender();ConsoleAppender.prototype.create=function(inPage,container,lazyInit,initiallyMinimized,useDocumentWrite,width,height,focusConsoleWindow){var appender=this;var initialized=false;var consoleWindowCreated=false;var consoleWindowLoaded=false;var consoleClosed=false;var queuedLoggingEvents=[];var isSupported=true;var consoleAppenderId=consoleAppenderIdCounter++;initiallyMinimized=extractBooleanFromParam(initiallyMinimized,this.defaults.initiallyMinimized);lazyInit=extractBooleanFromParam(lazyInit,this.defaults.lazyInit);useDocumentWrite=extractBooleanFromParam(useDocumentWrite,this.defaults.useDocumentWrite);var newestMessageAtTop=this.defaults.newestMessageAtTop;var scrollToLatestMessage=this.defaults.scrollToLatestMessage;width=width?width:this.defaults.width;height=height?height:this.defaults.height;var maxMessages=this.defaults.maxMessages;var showCommandLine=this.defaults.showCommandLine;var commandLineObjectExpansionDepth=this.defaults.commandLineObjectExpansionDepth;var showHideButton=this.defaults.showHideButton;var showCloseButton=this.defaults.showCloseButton;var showLogEntryDeleteButtons=this.defaults.showLogEntryDeleteButtons;this.setLayout(this.defaults.layout);var init,createWindow,safeToAppend,getConsoleWindow,open;var appenderName=inPage?"InPageAppender":"PopUpAppender";var checkCanConfigure=function(configOptionName){if(consoleWindowCreated){handleError(appenderName+": configuration option '"+configOptionName+"' may not be set after the appender has been initialized");return false;}
197return true;};var consoleWindowExists=function(){return(consoleWindowLoaded&&isSupported&&!consoleClosed);};this.isNewestMessageAtTop=function(){return newestMessageAtTop;};this.setNewestMessageAtTop=function(newestMessageAtTopParam){newestMessageAtTop=bool(newestMessageAtTopParam);if(consoleWindowExists()){getConsoleWindow().setNewestAtTop(newestMessageAtTop);}};this.isScrollToLatestMessage=function(){return scrollToLatestMessage;};this.setScrollToLatestMessage=function(scrollToLatestMessageParam){scrollToLatestMessage=bool(scrollToLatestMessageParam);if(consoleWindowExists()){getConsoleWindow().setScrollToLatest(scrollToLatestMessage);}};this.getWidth=function(){return width;};this.setWidth=function(widthParam){if(checkCanConfigure("width")){width=extractStringFromParam(widthParam,width);}};this.getHeight=function(){return height;};this.setHeight=function(heightParam){if(checkCanConfigure("height")){height=extractStringFromParam(heightParam,height);}};this.getMaxMessages=function(){return maxMessages;};this.setMaxMessages=function(maxMessagesParam){maxMessages=extractIntFromParam(maxMessagesParam,maxMessages);if(consoleWindowExists()){getConsoleWindow().setMaxMessages(maxMessages);}};this.isShowCommandLine=function(){return showCommandLine;};this.setShowCommandLine=function(showCommandLineParam){showCommandLine=bool(showCommandLineParam);if(consoleWindowExists()){getConsoleWindow().setShowCommandLine(showCommandLine);}};this.isShowHideButton=function(){return showHideButton;};this.setShowHideButton=function(showHideButtonParam){showHideButton=bool(showHideButtonParam);if(consoleWindowExists()){getConsoleWindow().setShowHideButton(showHideButton);}};this.isShowCloseButton=function(){return showCloseButton;};this.setShowCloseButton=function(showCloseButtonParam){showCloseButton=bool(showCloseButtonParam);if(consoleWindowExists()){getConsoleWindow().setShowCloseButton(showCloseButton);}};this.getCommandLineObjectExpansionDepth=function(){return commandLineObjectExpansionDepth;};this.setCommandLineObjectExpansionDepth=function(commandLineObjectExpansionDepthParam){commandLineObjectExpansionDepth=extractIntFromParam(commandLineObjectExpansionDepthParam,commandLineObjectExpansionDepth);};var minimized=initiallyMinimized;this.isInitiallyMinimized=function(){return initiallyMinimized;};this.setInitiallyMinimized=function(initiallyMinimizedParam){if(checkCanConfigure("initiallyMinimized")){initiallyMinimized=bool(initiallyMinimizedParam);minimized=initiallyMinimized;}};this.isUseDocumentWrite=function(){return useDocumentWrite;};this.setUseDocumentWrite=function(useDocumentWriteParam){if(checkCanConfigure("useDocumentWrite")){useDocumentWrite=bool(useDocumentWriteParam);}};function QueuedLoggingEvent(loggingEvent,formattedMessage){this.loggingEvent=loggingEvent;this.levelName=loggingEvent.level.name;this.formattedMessage=formattedMessage;}
198QueuedLoggingEvent.prototype.append=function(){getConsoleWindow().log(this.levelName,this.formattedMessage);};function QueuedGroup(name,initiallyExpanded){this.name=name;this.initiallyExpanded=initiallyExpanded;}
199QueuedGroup.prototype.append=function(){getConsoleWindow().group(this.name,this.initiallyExpanded);};function QueuedGroupEnd(){}
200QueuedGroupEnd.prototype.append=function(){getConsoleWindow().groupEnd();};var checkAndAppend=function(){safeToAppend();
200if(!initialized){init();}else if(consoleClosed&&reopenWhenClosed){createWindow();}
201if(safeToAppend()){appendQueuedLoggingEvents();}};this.append=function(loggingEvent){if(isSupported){var formattedMessage=appender.getLayout().format(loggingEvent);if(this.getLayout().ignoresThrowable()){formattedMessage+=loggingEvent.getThrowableStrRep();}
202queuedLoggingEvents.push(new QueuedLoggingEvent(loggingEvent,formattedMessage));checkAndAppend();}};this.group=function(name,initiallyExpanded){if(isSupported){queuedLoggingEvents.push(new QueuedGroup(name,initiallyExpanded));checkAndAppend();}};this.groupEnd=function(){if(isSupported){queuedLoggingEvents.push(new QueuedGroupEnd());checkAndAppend();}};var appendQueuedLoggingEvents=function(){var currentLoggingEvent;while(queuedLoggingEvents.length>0){queuedLoggingEvents.shift().append();}
203if(focusConsoleWindow){getConsoleWindow().focus();}};this.setAddedToLogger=function(logger){this.loggers.push(logger);if(enabled&&!lazyInit){init();}};this.clear=function(){if(consoleWindowExists()){getConsoleWindow().clearLog();}
204queuedLoggingEvents.length=0;};this.focus=function(){if(consoleWindowExists()){getConsoleWindow().focus();}};this.focusCommandLine=function(){if(consoleWindowExists()){getConsoleWindow().focusCommandLine();}};this.focusSearch=function(){if(consoleWindowExists()){getConsoleWindow().focusSearch();}};var commandWindow=window;this.getCommandWindow=function(){return commandWindow;};this.setCommandWindow=function(commandWindowParam){commandWindow=commandWindowParam;};this.executeLastCommand=function(){if(consoleWindowExists()){getConsoleWindow().evalLastCommand();}};var commandLayout=new PatternLayout("%m");this.getCommandLayout=function(){return commandLayout;};this.setCommandLayout=function(commandLayoutParam){commandLayout=commandLayoutParam;};this.evalCommandAndAppend=function(expr){var commandReturnValue={appendResult:true,isError:false};var commandOutput="";try{var result,i;if(!commandWindow.eval&&commandWindow.execScript){commandWindow.execScript("null");}
205var commandLineFunctionsHash={};for(i=0,len=commandLineFunctions.length;i<len;i++){commandLineFunctionsHash[commandLineFunctions[i][0]]=commandLineFunctions[i][1];}
206var objectsToRestore=[];var addObjectToRestore=function(name){objectsToRestore.push([name,commandWindow[name]]);};addObjectToRestore("appender");commandWindow.appender=appender;addObjectToRestore("commandReturnValue");commandWindow.commandReturnValue=commandReturnValue;addObjectToRestore("commandLineFunctionsHash");commandWindow.commandLineFunctionsHash=commandLineFunctionsHash;var addFunctionToWindow=function(name){addObjectToRestore(name);commandWindow[name]=function(){return this.commandLineFunctionsHash[name](appender,arguments,commandReturnValue);};};for(i=0,len=commandLineFunctions.length;i<len;i++){addFunctionToWindow(commandLineFunctions[i][0]);}
207if(commandWindow===window&&commandWindow.execScript){addObjectToRestore("evalExpr");addObjectToRestore("result");window.evalExpr=expr;commandWindow.execScript("window.result=eval(window.evalExpr);");result=window.result;}
207else{result=commandWindow.eval(expr);}
208commandOutput=isUndefined(result)?result:formatObjectExpansion(result,commandLineObjectExpansionDepth);for(i=0,len=objectsToRestore.length;i<len;i++){commandWindow[objectsToRestore[i][0]]=objectsToRestore[i][1];}}catch(ex){commandOutput="Error evaluating command: "+getExceptionStringRep(ex);commandReturnValue.isError=true;}
209if(commandReturnValue.appendResult){var message=">>> "+expr;if(!isUndefined(commandOutput)){message+=newLine+commandOutput;}
210var level=commandReturnValue.isError?Level.ERROR:Level.INFO;var loggingEvent=new LoggingEvent(null,new Date(),level,[message],null);var mainLayout=this.getLayout();this.setLayout(commandLayout);this.append(loggingEvent);this.setLayout(mainLayout);}};var commandLineFunctions=defaultCommandLineFunctions.concat([]);this.addCommandLineFunction=function(functionName,commandLineFunction){commandLineFunctions.push([functionName,commandLineFunction]);};var commandHistoryCookieName="log4javascriptCommandHistory";this.storeCommandHistory=function(commandHistory){setCookie(commandHistoryCookieName,commandHistory.join(","));};var writeHtml=function(doc){var lines=getConsoleHtmlLines();doc.open();for(var i=0,len=lines.length;i<len;i++){doc.writeln(lines[i]);}
211doc.close();};this.setEventTypes(["load","unload"]);var consoleWindowLoadHandler=function(){var win=getConsoleWindow();win.setAppender(appender);win.setNewestAtTop(newestMessageAtTop);win.setScrollToLatest(scrollToLatestMessage);win.setMaxMessages(maxMessages);win.setShowCommandLine(showCommandLine);win.setShowHideButton(showHideButton);win.setShowCloseButton(showCloseButton);win.setMainWindow(window);var storedValue=getCookie(commandHistoryCookieName);if(storedValue){win.commandHistory=storedValue.split(",");win.currentCommandIndex=win.commandHistory.length;}
212appender.dispatchEvent("load",{"win":win});};this.unload=function(){logLog.debug("unload "+this+", caller: "+this.unload.caller);if(!consoleClosed){logLog.debug("really doing unload "+this);consoleClosed=true;consoleWindowLoaded=false;consoleWindowCreated=false;appender.dispatchEvent("unload",{});}};var pollConsoleWindow=function(windowTest,interval,successCallback,errorMessage){function doPoll(){try{if(consoleClosed){clearInterval(poll);}
213if(windowTest(getConsoleWindow())){clearInterval(poll);successCallback();}}catch(ex){clearInterval(poll);isSupported=false;handleError(errorMessage,ex);}}
214var poll=setInterval(doPoll,interval);};var getConsoleUrl=function(){var documentDomainSet=(document.domain!=location.hostname);return useDocumentWrite?"":getBaseUrl()+"console.html"+
215(documentDomainSet?"?log4javascript_domain="+escape(document.domain):"");};if(inPage){var containerElement=null;var cssProperties=[];this.addCssProperty=function(name,value){if(checkCanConfigure("cssProperties")){cssProperties.push([name,value]);}};var windowCreationStarted=false;var iframeContainerDiv;var iframeId=uniqueId+"_InPageAppender_"+consoleAppenderId;this.hide=function(){if(initialized&&consoleWindowCreated){if(consoleWindowExists()){getConsoleWindow().$("command").blur();}
216iframeContainerDiv.style.display="none";minimized=true;}};this.show=function(){if(initialized){if(consoleWindowCreated){iframeContainerDiv.style.display="block";this.setShowCommandLine(showCommandLine);minimized=false;}else if(!windowCreationStarted){createWindow(true);}}};this.isVisible=function(){return!minimized&&!consoleClosed;};this.close=function(fromButton){if(!consoleClosed&&(!fromButton||confirm("This will permanently remove the console from the page. No more messages will be logged. Do you wish to continue?"))){iframeContainerDiv.parentNode.removeChild(iframeContainerDiv);this.unload();}};open=function(){var initErrorMessage="InPageAppender.open: unable to create console iframe";function finalInit(){try{if(!initiallyMinimized){appender.show();}
217consoleWindowLoadHandler();consoleWindowLoaded=true;appendQueuedLoggingEvents();}catch(ex){isSupported=false;handleError(initErrorMessage,ex);}}
218function writeToDocument(){try{var windowTest=function(win){return isLoaded(win);};if(useDocumentWrite){writeHtml(getConsoleWindow().document);}
219if(windowTest(getConsoleWindow())){finalInit();}else{pollConsoleWindow(windowTest,100,finalInit,initErrorMessage);}}catch(ex){isSupported=false;handleError(initErrorMessage,ex);}}
220minimized=false;
220iframeContainerDiv=containerElement.appendChild(document.createElement("div"));iframeContainerDiv.style.width=width;iframeContainerDiv.style.height=height;iframeContainerDiv.style.border="solid gray 1px";for(var i=0,len=cssProperties.length;i<len;i++){iframeContainerDiv.style[cssProperties[i][0]]=cssProperties[i][1];}
221var iframeSrc=useDocumentWrite?"":" src='"+getConsoleUrl()+"'";iframeContainerDiv.innerHTML="<iframe id='"+iframeId+"' name='"+iframeId+"' width='100%' height='100%' frameborder='0'"+iframeSrc+"scrolling='no'></iframe>";consoleClosed=false;var iframeDocumentExistsTest=function(win){try{return bool(win)&&bool(win.document);}catch(ex){return false;}};if(iframeDocumentExistsTest(getConsoleWindow())){writeToDocument();}else{pollConsoleWindow(iframeDocumentExistsTest,100,writeToDocument,initErrorMessage);}
222consoleWindowCreated=true;};createWindow=function(show){if(show||!initiallyMinimized){var pageLoadHandler=function(){if(!container){containerElement=document.createElement("div");containerElement.style.position="fixed";containerElement.style.left="0";containerElement.style.right="0";containerElement.style.bottom="0";document.body.appendChild(containerElement);appender.addCssProperty("borderWidth","1px 0 0 0");appender.addCssProperty("zIndex",1000000);open();}else{try{var el=document.getElementById(container);if(el.nodeType==1){containerElement=el;}
223open();}catch(ex){handleError("InPageAppender.init: invalid container element '"+container+"' supplied",ex);}}};if(pageLoaded&&container&&container.appendChild){containerElement=container;open();}else if(pageLoaded){pageLoadHandler();}else{log4javascript.addEventListener("load",pageLoadHandler);}
224windowCreationStarted=true;}};init=function(){createWindow();initialized=true;};getConsoleWindow=function(){var iframe=window.frames[iframeId];if(iframe){return iframe;}};safeToAppend=function(){if(isSupported&&!consoleClosed){if(consoleWindowCreated&&!consoleWindowLoaded&&getConsoleWindow()&&isLoaded(getConsoleWindow())){consoleWindowLoaded=true;}
225return consoleWindowLoaded;}
226return false;};}else{var useOldPopUp=appender.defaults.useOldPopUp;var complainAboutPopUpBlocking=appender.defaults.complainAboutPopUpBlocking;var reopenWhenClosed=this.defaults.reopenWhenClosed;this.isUseOldPopUp=function(){return useOldPopUp;};this.setUseOldPopUp=function(useOldPopUpParam){if(checkCanConfigure("useOldPopUp")){useOldPopUp=bool(useOldPopUpParam);}};this.isComplainAboutPopUpBlocking=function(){return complainAboutPopUpBlocking;};this.setComplainAboutPopUpBlocking=function(complainAboutPopUpBlockingParam){if(checkCanConfigure("complainAboutPopUpBlocking")){complainAboutPopUpBlocking=bool(complainAboutPopUpBlockingParam);}};this.isFocusPopUp=function(){return focusConsoleWindow;};this.setFocusPopUp=function(focusPopUpParam){focusConsoleWindow=bool(focusPopUpParam);};this.isReopenWhenClosed=function(){return reopenWhenClosed;};this.setReopenWhenClosed=function(reopenWhenClosedParam){reopenWhenClosed=bool(reopenWhenClosedParam);};this.close=function(){logLog.debug("close "+this);try{popUp.close();this.unload();}catch(ex){}};this.hide=function(){logLog.debug("hide "+this);if(consoleWindowExists()){this.close();}};this.show=function(){logLog.debug("show "+this);if(!consoleWindowCreated){open();}};this.isVisible=function(){return safeToAppend();};var popUp;open=function(){var windowProperties="width="+width+",height="+height+",status,resizable";var windowName="PopUp_"+location.host.replace(/[^a-z0-9]/gi,"_")+"_"+consoleAppenderId;if(!useOldPopUp||!useDocumentWrite){windowName=windowName+"_"+uniqueId;}
227var checkPopUpClosed=function(win){if(consoleClosed){return true;}else{try{return bool(win)&&win.closed;}catch(ex){}}
228return false;};var popUpClosedCallback=function(){if(!consoleClosed){appender.unload();}};function finalInit(){getConsoleWindow().setCloseIfOpenerCloses(!useOldPopUp||!useDocumentWrite);consoleWindowLoadHandler();consoleWindowLoaded=true;appendQueuedLoggingEvents();pollConsoleWindow(checkPopUpClosed,500,popUpClosedCallback,"PopUpAppender.checkPopUpClosed: error checking pop-up window");}
229try{popUp=window.open(getConsoleUrl(),windowName,windowProperties);consoleClosed=false;consoleWindowCreated=true;if(popUp){if(useDocumentWrite&&useOldPopUp&&isLoaded(popUp)){popUp.mainPageReloaded();finalInit();}else{if(useDocumentWrite){writeHtml(popUp.document);}
230var popUpLoadedTest=function(win){return bool(win)&&isLoaded(win);};if(isLoaded(popUp)){finalInit();}else{pollConsoleWindow(popUpLoadedTest,100,finalInit,"PopUpAppender.init: unable to create console window");}}}else{isSupported=false;logLog.warn("PopUpAppender.init: pop-ups blocked, please unblock to use PopUpAppender");if(complainAboutPopUpBlocking){handleError("log4javascript: pop-up windows appear to be blocked. Please unblock them to use pop-up logging.");}}}catch(ex){handleError("PopUpAppender.init: error creating pop-up",ex);}};createWindow=function(){if(!initiallyMinimized){open();}};init=function(){createWindow();initialized=true;};getConsoleWindow=function(){return popUp;};
230safeToAppend=function(){if(isSupported&&!isUndefined(popUp)&&!consoleClosed){if(popUp.closed||(consoleWindowLoaded&&isUndefined(popUp.closed))){appender.unload();logLog.debug("PopUpAppender: pop-up closed");return false;}
231if(!consoleWindowLoaded&&isLoaded(popUp)){consoleWindowLoaded=true;}}
232return isSupported&&consoleWindowLoaded&&!consoleClosed;};}
233this.getConsoleWindow=getConsoleWindow;};ConsoleAppender.addGlobalCommandLineFunction=function(functionName,commandLineFunction){defaultCommandLineFunctions.push([functionName,commandLineFunction]);};function PopUpAppender(lazyInit,initiallyMinimized,useDocumentWrite,width,height){this.create(false,null,lazyInit,initiallyMinimized,useDocumentWrite,width,height,this.defaults.focusPopUp);}
234PopUpAppender.prototype=new ConsoleAppender();PopUpAppender.prototype.defaults={layout:new PatternLayout("%d{HH:mm:ss} %-5p - %m{1}%n"),initiallyMinimized:false,focusPopUp:false,lazyInit:true,useOldPopUp:true,complainAboutPopUpBlocking:true,newestMessageAtTop:false,scrollToLatestMessage:true,width:"600",height:"400",reopenWhenClosed:false,maxMessages:null,showCommandLine:true,commandLineObjectExpansionDepth:1,showHideButton:false,showCloseButton:true,showLogEntryDeleteButtons:true,useDocumentWrite:true};PopUpAppender.prototype.toString=function(){return"PopUpAppender";};log4javascript.PopUpAppender=PopUpAppender;function InPageAppender(container,lazyInit,initiallyMinimized,useDocumentWrite,width,height){this.create(true,container,lazyInit,initiallyMinimized,useDocumentWrite,width,height,false);}
235InPageAppender.prototype=new ConsoleAppender();InPageAppender.prototype.defaults={layout:new PatternLayout("%d{HH:mm:ss} %-5p - %m{1}%n"),initiallyMinimized:false,lazyInit:true,newestMessageAtTop:false,scrollToLatestMessage:true,width:"100%",height:"220px",maxMessages:null,showCommandLine:true,commandLineObjectExpansionDepth:1,showHideButton:false,showCloseButton:false,showLogEntryDeleteButtons:true,useDocumentWrite:true};InPageAppender.prototype.toString=function(){return"InPageAppender";};log4javascript.InPageAppender=InPageAppender;log4javascript.InlineAppender=InPageAppender;})();function padWithSpaces(str,len){if(str.length<len){var spaces=[];var numberOfSpaces=Math.max(0,len-str.length);for(var i=0;i<numberOfSpaces;i++){spaces[i]=" ";}
236str+=spaces.join("");}
237return str;}
238(function(){function dir(obj){var maxLen=0;for(var p in obj){maxLen=Math.max(toStr(p).length,maxLen);}
239var propList=[];for(p in obj){var propNameStr="  "+padWithSpaces(toStr(p),maxLen+2);var propVal;try{propVal=splitIntoLines(toStr(obj[p])).join(padWithSpaces(newLine,maxLen+6));}catch(ex){propVal="[Error obtaining property. Details: "+getExceptionMessage(ex)+"]";}
240propList.push(propNameStr+propVal);}
241return propList.join(newLine);}
242var nodeTypes={ELEMENT_NODE:1,ATTRIBUTE_NODE:2,TEXT_NODE:3,CDATA_SECTION_NODE:4,ENTITY_REFERENCE_NODE:5,ENTITY_NODE:6,PROCESSING_INSTRUCTION_NODE:7,COMMENT_NODE:8,DOCUMENT_NODE:9,DOCUMENT_TYPE_NODE:10,DOCUMENT_FRAGMENT_NODE:11,NOTATION_NODE:12};var preFormattedElements=["script","pre"];var emptyElements=["br","img","hr","param","link","area","input","col","base","meta"];var indentationUnit="  ";function getXhtml(rootNode,includeRootNode,indentation,startNewLine,preformatted){includeRootNode=(typeof includeRootNode=="undefined")?true:(includeRootNode?true:false);if(typeof indentation!="string"){indentation="";}
243startNewLine=startNewLine?true:false;preformatted=preformatted?true:false;var xhtml;function isWhitespace(node){return((node.nodeType==nodeTypes.TEXT_NODE)&&/^[ \t\r\n]*$/.test(node.nodeValue));}
244function fixAttributeValue(attrValue){return attrValue.toString().replace(/\&/g,"&amp;").replace(/</g,"&lt;").replace(/\"/g,"&quot;");}
245function getStyleAttributeValue(el){var stylePairs=el.style.cssText.split(";");var styleValue="";var isFirst=true;for(var j=0,len=stylePairs.length;j<len;j++){var nameValueBits=stylePairs[j].split(":");var props=[];if(!/^\s*$/.test(nameValueBits[0])){props.push(trim(nameValueBits[0]).toLowerCase()+":"+trim(nameValueBits[1]));}
246styleValue=props.join(";");}
247return styleValue;}
248function getNamespace(el){if(el.prefix){return el.prefix;}else if(el.outerHTML){var regex=new RegExp("<([^:]+):"+el.tagName+"[^>]*>","i");if(regex.test(el.outerHTML)){return RegExp.$1.toLowerCase();}}
249return"";}
250var lt="<";var gt=">";if(includeRootNode&&rootNode.nodeType!=nodeTypes.DOCUMENT_FRAGMENT_NODE){switch(rootNode.nodeType){case nodeTypes.ELEMENT_NODE:var tagName=rootNode.tagName.toLowerCase();xhtml=startNewLine?newLine+indentation:"";xhtml+=lt;var prefix=getNamespace(rootNode);var hasPrefix=prefix?true:false;if(hasPrefix){xhtml+=prefix+":";}
251xhtml+=tagName;for(i=0,len=rootNode.attributes.length;i<len;i++){var currentAttr=rootNode.attributes[i];if(!currentAttr.specified||currentAttr.nodeValue===null||currentAttr.nodeName.toLowerCase()==="style"||typeof currentAttr.nodeValue!=="string"||currentAttr.nodeName.indexOf("_moz")===0){continue;}
252xhtml+=" "+currentAttr.nodeName.toLowerCase()+"=\"";xhtml+=fixAttributeValue(currentAttr.nodeValue);xhtml+="\"";}
253if(rootNode.style.cssText){var styleValue=getStyleAttributeValue(rootNode);
253if(styleValue!==""){xhtml+=" style=\""+getStyleAttributeValue(rootNode)+"\"";}}
254if(array_contains(emptyElements,tagName)||(hasPrefix&&!rootNode.hasChildNodes())){xhtml+="/"+gt;}else{xhtml+=gt;var childStartNewLine=!(rootNode.childNodes.length===1&&rootNode.childNodes[0].nodeType===nodeTypes.TEXT_NODE);var childPreformatted=array_contains(preFormattedElements,tagName);for(var i=0,len=rootNode.childNodes.length;i<len;i++){xhtml+=getXhtml(rootNode.childNodes[i],true,indentation+indentationUnit,childStartNewLine,childPreformatted);}
255var endTag=lt+"/"+tagName+gt;xhtml+=childStartNewLine?newLine+indentation+endTag:endTag;}
256return xhtml;case nodeTypes.TEXT_NODE:if(isWhitespace(rootNode)){xhtml="";}else{if(preformatted){xhtml=rootNode.nodeValue;}else{var lines=splitIntoLines(trim(rootNode.nodeValue));var trimmedLines=[];for(var i=0,len=lines.length;i<len;i++){trimmedLines[i]=trim(lines[i]);}
257xhtml=trimmedLines.join(newLine+indentation);}
258if(startNewLine){xhtml=newLine+indentation+xhtml;}}
259return xhtml;case nodeTypes.CDATA_SECTION_NODE:return"<![CDA"+"TA["+rootNode.nodeValue+"]"+"]>"+newLine;case nodeTypes.DOCUMENT_NODE:xhtml="";for(var i=0,len=rootNode.childNodes.length;i<len;i++){xhtml+=getXhtml(rootNode.childNodes[i],true,indentation);}
260return xhtml;default:return"";}}else{xhtml="";for(var i=0,len=rootNode.childNodes.length;i<len;i++){xhtml+=getXhtml(rootNode.childNodes[i],true,indentation+indentationUnit);}
261return xhtml;}}
262var layouts={};function createCommandLineFunctions(appender){ConsoleAppender.addGlobalCommandLineFunction("$",function(appender,args,returnValue){return document.getElementById(args[0]);});ConsoleAppender.addGlobalCommandLineFunction("dir",function(appender,args,returnValue){var lines=[];for(var i=0,len=args.length;i<len;i++){lines[i]=dir(args[i]);}
263return lines.join(newLine+newLine);});ConsoleAppender.addGlobalCommandLineFunction("dirxml",function(appender,args,returnValue){var lines=[];for(var i=0,len=args.length;i<len;i++){var win=appender.getCommandWindow();lines[i]=getXhtml(args[i]);}
264return lines.join(newLine+newLine);});ConsoleAppender.addGlobalCommandLineFunction("cd",function(appender,args,returnValue){var win,message;if(args.length===0||args[0]===""){win=window;message="Command line set to run in main window";}else{if(args[0].window==args[0]){win=args[0];message="Command line set to run in frame '"+args[0].name+"'";}else{win=window.frames[args[0]];if(win){message="Command line set to run in frame '"+args[0]+"'";}else{returnValue.isError=true;message="Frame '"+args[0]+"' does not exist";win=appender.getCommandWindow();}}}
265appender.setCommandWindow(win);return message;});ConsoleAppender.addGlobalCommandLineFunction("clear",function(appender,args,returnValue){returnValue.appendResult=false;appender.clear();});ConsoleAppender.addGlobalCommandLineFunction("keys",function(appender,args,returnValue){var keys=[];for(var k in args[0]){keys.push(k);}
266return keys;});ConsoleAppender.addGlobalCommandLineFunction("values",function(appender,args,returnValue){var values=[];for(var k in args[0]){try{values.push(args[0][k]);}catch(ex){logLog.warn("values(): Unable to obtain value for key "+k+". Details: "+getExceptionMessage(ex));}}
267return values;});ConsoleAppender.addGlobalCommandLineFunction("expansionDepth",function(appender,args,returnValue){var expansionDepth=parseInt(args[0],10);if(isNaN(expansionDepth)||expansionDepth<0){returnValue.isError=true;return""+args[0]+" is not a valid expansion depth";}else{appender.setCommandLineObjectExpansionDepth(expansionDepth);return"Object expansion depth set to "+expansionDepth;}});}
268function init(){createCommandLineFunctions();initialized=true;}
269init();})();function addWindowLoadListener(listener){var oldOnload=window.onload;if(typeof window.onload!="function"){window.onload=listener;}else{window.onload=function(evt){if(oldOnload){oldOnload(evt);}
270listener(evt);};}}
271addWindowLoadListener(function(){pageLoaded=true;log4javascript.dispatchEvent("load",{});});window.log4javascript=log4javascript;})();/*
272Copyright (c) 2011, Yahoo! Inc. All rights reserved.
273Code licensed under the BSD License:
274http://developer.yahoo.com/yui/license.html
275version: 2.9.0
276*/
277/**
278 * The YAHOO object is the single global object used by YUI Library.  It
279 * contains utility function for setting up namespaces, inheritance, and
280 * logging.  YAHOO.util, YAHOO.widget, and YAHOO.example are namespaces
281 * created automatically for and used by the library.
282 * @module yahoo
283 * @title  YAHOO Global
284 */
285
286/**
287 * YAHOO_config is not included as part of the library.  Instead it is an
288 * object that can be defined by the implementer immediately before
289 * including the YUI library.  The properties included in this object
290 * will be used to configure global properties needed as soon as the
291 * library begins to load.
292 * @class YAHOO_config
293 * @static
294 */
295
296/**
297 * A reference to a function that will be executed every time a YAHOO module
298 * is loaded.  As parameter, this function will receive the version
299 * information for the module. See <a href="YAHOO.env.html#getVersion">
300 * YAHOO.env.getVersion</a> for the description of the version data structure.
301 * @property listener
302 * @type Function
303 * @static
304 * @default undefined
305 */
306
307/**
308 * Set to true if the library will be dynamically loaded after window.onload.
309 * Defaults to false
310 * @property injecting
311 * @type boolean
312 * @static
313 * @default undefined
314 */
315
316/**
317 * Instructs the yuiloader component to dynamically load yui components and
318 * their dependencies.  See the yuiloader documentation for more information
319 * about dynamic loading
320 * @property load
321 * @static
322 * @default undefined
323 * @see yuiloader
324 */
325
326/**
327 * Forces the use of the supplied locale where applicable in the library
328 * @property locale
329 * @type string
330 * @static
331 * @default undefined
332 */
333
334if (typeof YAHOO == "undefined" || !YAHOO) {
335    /**
336     * The YAHOO global namespace object.  If YAHOO is already defined, the
337     * existing YAHOO object will not be overwritten so that defined
338     * namespaces are preserved.
339     * @class YAHOO
340     * @static
341     */
342    var YAHOO = {};
343}
344
345/**
346 * Returns the namespace specified and creates it if it doesn't exist
347 * <pre>
348 * YAHOO.namespace("property.package");
349 * YAHOO.namespace("YAHOO.property.package");
350 * </pre>
351 * Either of the above would create YAHOO.property, then
352 * YAHOO.property.package
353 *
354 * Be careful when naming packages. Reserved words may work in some browsers
355 * and not others. For instance, the following will fail in Safari:
356 * <pre>
357 * YAHOO.namespace("really.long.nested.namespace");
358 * </pre>
359 * This fails because "long" is a future reserved word in ECMAScript
360 *
361 * For implementation code that uses YUI, do not create your components
362 * in the namespaces defined by YUI (
363 * <code>YAHOO.util</code>,
364 * <code>YAHOO.widget</code>,
365 * <code>YAHOO.lang</code>,
366 * <code>YAHOO.tool</code>,
367 * <code>YAHOO.example</code>,
368 * <code>YAHOO.env</code>) -- create your own namespace (e.g., 'companyname').
369 *
370 * @method namespace
371 * @static
372 * @param  {String*} arguments 1-n namespaces to create
373 * @return {Object}  A reference to the last namespace object created
374 */
375YAHOO.namespace = function() {
376    var a=arguments, o=null, i, j, d;
377    for (i=0; i<a.length; i=i+1) {
378        d=(""+a[i]).split(".");
379        o=YAHOO;
380
381        // YAHOO is implied, so it is ignored if it is included
382        for (j=(d[0] == "YAHOO") ? 1 : 0; j<d.length; j=j+1) {
383            o[d[j]]=o[d[j]] || {};
384            o=o[d[j]];
385        }
386    }
387
388    return o;
389};
390
391/**
392 * Uses YAHOO.widget.Logger to output a log message, if the widget is
393 * available.
394 * Note: LogReader adds the message, category, and source to the DOM as HTML.
395 *
396 * @method log
397 * @static
398 * @param  {HTML}  msg  The message to log.
399 * @param  {HTML}  cat  The log category for the message.  Default
400 *                        categories are "info", "warn", "error", time".
401 *                        Custom categories can be used as well. (opt)
402 * @param  {HTML}  src  The source of the the message (opt)
403 * @return {Boolean}      True if the log operation was successful.
404 */
405YAHOO.log = function(msg, cat, src) {
406    var l=YAHOO.widget.Logger;
407    if(l && l.log) {
408        return l.log(msg, cat, src);
409    } else {
410        return false;
411    }
412};
413
414/**
415 * Registers a module with the YAHOO object
416 * @method register
417 * @static
418 * @param {String}   name    the name of the module (event, slider, etc)
419 * @param {Function} mainClass a reference to class in the module.  This
420 *                             class will be tagged with the version info
421 *                             so that it will be possible to identify the
422 *                             version that is in use when multiple versions
423 *                             have loaded
424 * @param {Object}   data      metadata object for the module.  Currently it
425 *                             is expected to contain a "version" property
426 *                             and a "build" property at minimum.
427 */
428YAHOO.register = function(name, mainClass, data) {
429    var mods = YAHOO.env.modules, m, v, b, ls, i;
430
431    if (!mods[name]) {
432        mods[name] = {
433            versions:[],
434            builds:[]
435        };
436    }
437
438    m  = mods[name];
439    v  = data.version;
440    b  = data.build;
441    ls = YAHOO.env.listeners;
442
443    m.name = name;
444    m.version = v;
445    m.build = b;
446    m.versions.push(v);
447    m.builds.push(b);
448    m.mainClass = mainClass;
449
450    // fire the module load listeners
451    for (i=0;i<ls.length;i=i+1) {
452        ls[i](m);
453    }
454    // label the main class
455    if (mainClass) {
456        mainClass.VERSION = v;
457        mainClass.BUILD = b;
458    } else {
459        YAHOO.log("mainClass is undefined for module " + name, "warn");
460    }
461};
462
463/**
464 * YAHOO.env is used to keep track of what is known about the YUI library and
465 * the browsing environment
466 * @class YAHOO.env
467 * @static
468 */
469YAHOO.env = YAHOO.env || {
470
471    /**
472     * Keeps the version info for all YUI modules that have reported themselves
473     * @property modules
474     * @type Object[]
475     */
476    modules: [],
477
478    /**
479     * List of functions that should be executed every time a YUI module
480     * reports itself.
481     * @property listeners
482     * @type Function[]
483     */
484    listeners: []
485};
486
487/**
488 * Returns the version data for the specified module:
489 *      <dl>
490 *      <dt>name:</dt>      <dd>The name of the module</dd>
491 *      <dt>version:</dt>   <dd>The version in use</dd>
492 *      <dt>build:</dt>     <dd>The build number in use</dd>
493 *      <dt>versions:</dt>  <dd>All versions that were registered</dd>
494 *      <dt>builds:</dt>    <dd>All builds that were registered.</dd>
495 *      <dt>mainClass:</dt> <dd>An object that was was stamped with the
496 *                 current version and build. If
497 *                 mainClass.VERSION != version or mainClass.BUILD != build,
498 *                 multiple versions of pieces of the library have been
499 *                 loaded, potentially causing issues.</dd>
500 *       </dl>
501 *
502 * @method getVersion
503 * @static
504 * @param {String}  name the name of the module (event, slider, etc)
505 * @return {Object} The version info
506 */
507YAHOO.env.getVersion = function(name) {
508    return YAHOO.env.modules[name] || null;
509};
510
511/**
512 * Do not fork for a browser if it can be avoided.  Use feature detection when
513 * you can.  Use the user agent as a last resort.  YAHOO.env.ua stores a version
514 * number for the browser engine, 0 otherwise.  This value may or may not map
515 * to the version number of the browser using the engine.  The value is
516 * presented as a float so that it can easily be used for boolean evaluation
517 * as well as for looking for a particular range of versions.  Because of this,
518 * some of the granularity of the version info may be lost (e.g., Gecko 1.8.0.9
519 * reports 1.8).
520 * @class YAHOO.env.ua
521 * @static
522 */
523
524/**
525 * parses a user agent string (or looks for one in navigator to parse if
526 * not supplied).
527 * @method parseUA
528 * @since 2.9.0
529 * @static
530 */
531YAHOO.env.parseUA = function(agent) {
532
533        var numberify = function(s) {
534            var c = 0;
535            return parseFloat(s.replace(/\./g, function() {
536                return (c++ == 1) ? '' : '.';
537            }));
538        },
539
540        nav = navigator,
541
542        o = {
543
544        /**
545         * Internet Explorer version number or 0.  Example: 6
546         * @property ie
547         * @type float
548         * @static
549         */
550        ie: 0,
551
552        /**
553         * Opera version number or 0.  Example: 9.2
554         * @property opera
555         * @type float
556         * @static
557         */
558        opera: 0,
559
560        /**
561         * Gecko engine revision number.  Will evaluate to 1 if Gecko
562         * is detected but the revision could not be found. Other browsers
563         * will be 0.  Example: 1.8
564         * <pre>
565         * Firefox 1.0.0.4: 1.7.8   <-- Reports 1.7
566         * Firefox 1.5.0.9: 1.8.0.9 <-- 1.8
567         * Firefox 2.0.0.3: 1.8.1.3 <-- 1.81
568         * Firefox 3.0   <-- 1.9
569         * Firefox 3.5   <-- 1.91
570         * </pre>
571         * @property gecko
572         * @type float
573         * @static
574         */
575        gecko: 0,
576
577        /**
578         * AppleWebKit version.  KHTML browsers that are not WebKit browsers
579         * will evaluate to 1, other browsers 0.  Example: 418.9
580         * <pre>
581         * Safari 1.3.2 (312.6): 312.8.1 <-- Reports 312.8 -- currently the
582         *                                   latest available for Mac OSX 10.3.
583         * Safari 2.0.2:         416     <-- hasOwnProperty introduced
584         * Safari 2.0.4:         418     <-- preventDefault fixed
585         * Safari 2.0.4 (419.3): 418.9.1 <-- One version of Safari may run
586         *                                   different versions of webkit
587         * Safari 2.0.4 (419.3): 419     <-- Tiger installations that have been
588         *                                   updated, but not updated
589         *                                   to the latest patch.
590         * Webkit 212 nightly:   522+    <-- Safari 3.0 precursor (with native
591         * SVG and many major issues fixed).
592         * Safari 3.0.4 (523.12) 523.12  <-- First Tiger release - automatic
593         * update from 2.x via the 10.4.11 OS patch.
594         * Webkit nightly 1/2008:525+    <-- Supports DOMContentLoaded event.
595         *                                   yahoo.com user agent hack removed.
596         * </pre>
597         * http://en.wikipedia.org/wiki/Safari_version_history
598         * @property webkit
599         * @type float
600         * @static
601         */
602        webkit: 0,
603
604        /**
605         * Chrome will be detected as webkit, but this property will also
606         * be populated with the Chrome version number
607         * @property chrome
608         * @type float
609         * @static
610         */
611        chrome: 0,
612
613        /**
614         * The mobile property will be set to a string containing any relevant
615         * user agent information when a modern mobile browser is detected.
616         * Currently limited to Safari on the iPhone/iPod Touch, Nokia N-series
617         * devices with the WebKit-based browser, and Opera Mini.
618         * @property mobile
619         * @type string
620         * @static
621         */
622        mobile: null,
623
624        /**
625         * Adobe AIR version number or 0.  Only populated if webkit is detected.
626         * Example: 1.0
627         * @property air
628         * @type float
629         */
630        air: 0,
631        /**
632         * Detects Apple iPad's OS version
633         * @property ipad
634         * @type float
635         * @static
636         */
637        ipad: 0,
638        /**
639         * Detects Apple iPhone's OS version
640         * @property iphone
641         * @type float
642         * @static
643         */
644        iphone: 0,
645        /**
646         * Detects Apples iPod's OS version
647         * @property ipod
648         * @type float
649         * @static
650         */
651        ipod: 0,
652        /**
653         * General truthy check for iPad, iPhone or iPod
654         * @property ios
655         * @type float
656         * @static
657         */
658        ios: null,
659        /**
660         * Detects Googles Android OS version
661         * @property android
662         * @type float
663         * @static
664         */
665        android: 0,
666        /**
667         * Detects Palms WebOS version
668         * @property webos
669         * @type float
670         * @static
671         */
672        webos: 0,
673
674        /**
675         * Google Caja version number or 0.
676         * @property caja
677         * @type float
678         */
679        caja: nav && nav.cajaVersion,
680
681        /**
682         * Set to true if the page appears to be in SSL
683         * @property secure
684         * @type boolean
685         * @static
686         */
687        secure: false,
688
689        /**
690         * The operating system.  Currently only detecting windows or macintosh
691         * @property os
692         * @type string
693         * @static
694         */
695        os: null
696
697    },
698
699    ua = agent || (navigator && navigator.userAgent),
700
701    loc = window && window.location,
702
703    href = loc && loc.href,
704
705    m;
706
707    o.secure = href && (href.toLowerCase().indexOf("https") === 0);
708
709    if (ua) {
710
711        if ((/windows|win32/i).test(ua)) {
712            o.os = 'windows';
713        } else if ((/macintosh/i).test(ua)) {
714            o.os = 'macintosh';
715        } else if ((/rhino/i).test(ua)) {
716            o.os = 'rhino';
717        }
718
719        // Modern KHTML browsers should qualify as Safari X-Grade
720        if ((/KHTML/).test(ua)) {
721            o.webkit = 1;
722        }
723        // Modern WebKit browsers are at least X-Grade
724        m = ua.match(/AppleWebKit\/([^\s]*)/);
725        if (m && m[1]) {
726            o.webkit = numberify(m[1]);
727
728            // Mobile browser check
729            if (/ Mobile\//.test(ua)) {
730                o.mobile = 'Apple'; // iPhone or iPod Touch
731
732                m = ua.match(/OS ([^\s]*)/);
733                if (m && m[1]) {
734                    m = numberify(m[1].replace('_', '.'));
735                }
736                o.ios = m;
737                o.ipad = o.ipod = o.iphone = 0;
738
739                m = ua.match(/iPad|iPod|iPhone/);
740                if (m && m[0]) {
741                    o[m[0].toLowerCase()] = o.ios;
742                }
743            } else {
744                m = ua.match(/NokiaN[^\/]*|Android \d\.\d|webOS\/\d\.\d/);
745                if (m) {
746                    // Nokia N-series, Android, webOS, ex: NokiaN95
747                    o.mobile = m[0];
748                }
749                if (/webOS/.test(ua)) {
750                    o.mobile = 'WebOS';
751                    m = ua.match(/webOS\/([^\s]*);/);
752                    if (m && m[1]) {
753                        o.webos = numberify(m[1]);
754                    }
755                }
756                if (/ Android/.test(ua)) {
757                    o.mobile = 'Android';
758                    m = ua.match(/Android ([^\s]*);/);
759                    if (m && m[1]) {
760                        o.android = numberify(m[1]);
761                    }
762
763                }
764            }
765
766            m = ua.match(/Chrome\/([^\s]*)/);
767            if (m && m[1]) {
768                o.chrome = numberify(m[1]); // Chrome
769            } else {
770                m = ua.match(/AdobeAIR\/([^\s]*)/);
771                if (m) {
772                    o.air = m[0]; // Adobe AIR 1.0 or better
773                }
774            }
775        }
776
777        if (!o.webkit) { // not webkit
778// @todo check Opera/8.01 (J2ME/MIDP; Opera Mini/2.0.4509/1316; fi; U; ssr)
779            m = ua.match(/Opera[\s\/]([^\s]*)/);
780            if (m && m[1]) {
781                o.opera = numberify(m[1]);
782                m = ua.match(/Version\/([^\s]*)/);
783                if (m && m[1]) {
784                    o.opera = numberify(m[1]); // opera 10+
785                }
786                m = ua.match(/Opera Mini[^;]*/);
787                if (m) {
788                    o.mobile = m[0]; // ex: Opera Mini/2.0.4509/1316
789                }
790            } else { // not opera or webkit
791                m = ua.match(/MSIE ([^;]*)|Trident.*; rv.([0-9.]+)/);
792                if (m && (m[1] || m[2])) {
793                    o.ie = numberify(m[1] || m[2]);
794                } else { // not opera, webkit, or ie
795                    m = ua.match(/Gecko\/([^\s]*)/);
796                    if (m) {
797                        o.gecko = 1; // Gecko detected, look for revision
798                        m = ua.match(/rv:([^\s\)]*)/);
799                        if (m && m[1]) {
800                            o.gecko = numberify(m[1]);
801                        }
802                    }
803                }
804            }
805        }
806    }
807
808    return o;
809};
810
811YAHOO.env.ua = YAHOO.env.parseUA();
812
813/*
814 * Initializes the global by creating the default namespaces and applying
815 * any new configuration information that is detected.  This is the setup
816 * for env.
817 * @method init
818 * @static
819 * @private
820 */
821(function() {
822    YAHOO.namespace("util", "widget", "example");
823    /*global YAHOO_config*/
824    if ("undefined" !== typeof YAHOO_config) {
825        var l=YAHOO_config.listener, ls=YAHOO.env.listeners,unique=true, i;
826        if (l) {
827            // if YAHOO is loaded multiple times we need to check to see if
828            // this is a new config object.  If it is, add the new component
829            // load listener to the stack
830            for (i=0; i<ls.length; i++) {
831                if (ls[i] == l) {
832                    unique = false;
833                    break;
834                }
835            }
836
837            if (unique) {
838                ls.push(l);
839            }
840        }
841    }
842})();
843/**
844 * Provides the language utilites and extensions used by the library
845 * @class YAHOO.lang
846 */
847YAHOO.lang = YAHOO.lang || {};
848
849(function() {
850
851
852var L = YAHOO.lang,
853
854    OP = Object.prototype,
855    ARRAY_TOSTRING = '[object Array]',
856    FUNCTION_TOSTRING = '[object Function]',
857    OBJECT_TOSTRING = '[object Object]',
858    NOTHING = [],
859
860    HTML_CHARS = {
861        '&': '&amp;',
862        '<': '&lt;',
863        '>': '&gt;',
864        '"': '&quot;',
865        "'": '&#x27;',
866        '/': '&#x2F;',
867        '`': '&#x60;'
868    },
869
870    // ADD = ["toString", "valueOf", "hasOwnProperty"],
871    ADD = ["toString", "valueOf"],
872
873    OB = {
874
875    /**
876     * Determines wheather or not the provided object is an array.
877     * @method isArray
878     * @param {any} o The object being testing
879     * @return {boolean} the result
880     */
881    isArray: function(o) {
882        return OP.toString.apply(o) === ARRAY_TOSTRING;
883    },
884
885    /**
886     * Determines whether or not the provided object is a boolean
887     * @method isBoolean
888     * @param {any} o The object being testing
889     * @return {boolean} the result
890     */
891    isBoolean: function(o) {
892        return typeof o === 'boolean';
893    },
894
895    /**
896     * Determines whether or not the provided object is a function.
897     * Note: Internet Explorer thinks certain functions are objects:
898     *
899     * var obj = document.createElement("object");
900     * YAHOO.lang.isFunction(obj.getAttribute) // reports false in IE
901     *
902     * var input = document.createElement("input"); // append to body
903     * YAHOO.lang.isFunction(input.focus) // reports false in IE
904     *
905     * You will have to implement additional tests if these functions
906     * matter to you.
907     *
908     * @method isFunction
909     * @param {any} o The object being testing
910     * @return {boolean} the result
911     */
912    isFunction: function(o) {
913        return (typeof o === 'function') || OP.toString.apply(o) === FUNCTION_TOSTRING;
914    },
915
916    /**
917     * Determines whether or not the provided object is null
918     * @method isNull
919     * @param {any} o The object being testing
920     * @return {boolean} the result
921     */
922    isNull: function(o) {
923        return o === null;
924    },
925
926    /**
927     * Determines whether or not the provided object is a legal number
928     * @method isNumber
929     * @param {any} o The object being testing
930     * @return {boolean} the result
931     */
932    isNumber: function(o) {
933        return typeof o === 'number' && isFinite(o);
934    },
935
936    /**
937     * Determines whether or not the provided object is of type object
938     * or function
939     * @method isObject
940     * @param {any} o The object being testing
941     * @return {boolean} the result
942     */
943    isObject: function(o) {
944return (o && (typeof o === 'object' || L.isFunction(o))) || false;
945    },
946
947    /**
948     * Determines whether or not the provided object is a string
949     * @method isString
950     * @param {any} o The object being testing
951     * @return {boolean} the result
952     */
953    isString: function(o) {
954        return typeof o === 'string';
955    },
956
957    /**
958     * Determines whether or not the provided object is undefined
959     * @method isUndefined
960     * @param {any} o The object being testing
961     * @return {boolean} the result
962     */
963    isUndefined: function(o) {
964        return typeof o === 'undefined';
965    },
966
967
968    /**
969     * IE will not enumerate native functions in a derived object even if the
970     * function was overridden.  This is a workaround for specific functions
971     * we care about on the Object prototype.
972     * @property _IEEnumFix
973     * @param {Function} r  the object to receive the augmentation
974     * @param {Function} s  the object that supplies the properties to augment
975     * @static
976     * @private
977     */
978    _IEEnumFix: (YAHOO.env.ua.ie) ? function(r, s) {
979            var i, fname, f;
980            for (i=0;i<ADD.length;i=i+1) {
981
982                fname = ADD[i];
983                f = s[fname];
984
985                if (L.isFunction(f) && f!=OP[fname]) {
986                    r[fname]=f;
987                }
988            }
989    } : function(){},
990
991    /**
992     * <p>
993     * Returns a copy of the specified string with special HTML characters
994     * escaped. The following characters will be converted to their
995     * corresponding character entities:
996     * <code>&amp; &lt; &gt; &quot; &#x27; &#x2F; &#x60;</code>
997     * </p>
998     *
999     * <p>
1000     * This implementation is based on the
1001     * <a href="http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet">OWASP
1002     * HTML escaping recommendations</a>. In addition to the characters
1003     * in the OWASP recommendation, we also escape the <code>&#x60;</code>
1004     * character, since IE interprets it as an attribute delimiter when used in
1005     * innerHTML.
1006     * </p>
1007     *
1008     * @method escapeHTML
1009     * @param {String} html String to escape.
1010     * @return {String} Escaped string.
1011     * @static
1012     * @since 2.9.0
1013     */
1014    escapeHTML: function (html) {
1015        return html.replace(/[&<>"'\/`]/g, function (match) {
1016            return HTML_CHARS[match];
1017        });
1018    },
1019
1020    /**
1021     * Utility to set up the prototype, constructor and superclass properties to
1022     * support an inheritance strategy that can chain constructors and methods.
1023     * Static members will not be inherited.
1024     *
1025     * @method extend
1026     * @static
1027     * @param {Function} subc   the object to modify
1028     * @param {Function} superc the object to inherit
1029     * @param {Object} overrides  additional properties/methods to add to the
1030     *                              subclass prototype.  These will override the
1031     *                              matching items obtained from the superclass
1032     *                              if present.
1033     */
1034    extend: function(subc, superc, overrides) {
1035        if (!superc||!subc) {
1036            throw new Error("extend failed, please check that " +
1037                            "all dependencies are included.");
1038        }
1039        var F = function() {}, i;
1040        F.prototype=superc.prototype;
1041        subc.prototype=new F();
1042        subc.prototype.constructor=subc;
1043        subc.superclass=superc.prototype;
1044        if (superc.prototype.constructor == OP.constructor) {
1045            superc.prototype.constructor=superc;
1046        }
1047
1048        if (overrides) {
1049            for (i in overrides) {
1050                if (L.hasOwnProperty(overrides, i)) {
1051                    subc.prototype[i]=overrides[i];
1052                }
1053            }
1054
1055            L._IEEnumFix(subc.prototype, overrides);
1056        }
1057    },
1058
1059    /**
1060     * Applies all properties in the supplier to the receiver if the
1061     * receiver does not have these properties yet.  Optionally, one or
1062     * more methods/properties can be specified (as additional
1063     * parameters).  This option will overwrite the property if receiver
1064     * has it already.  If true is passed as the third parameter, all
1065     * properties will be applied and _will_ overwrite properties in
1066     * the receiver.
1067     *
1068     * @method augmentObject
1069     * @static
1070     * @since 2.3.0
1071     * @param {Function} r  the object to receive the augmentation
1072     * @param {Function} s  the object that supplies the properties to augment
1073     * @param {String*|boolean}  arguments zero or more properties methods
1074     *        to augment the receiver with.  If none specified, everything
1075     *        in the supplier will be used unless it would
1076     *        overwrite an existing property in the receiver. If true
1077     *        is specified as the third parameter, all properties will
1078     *        be applied and will overwrite an existing property in
1079     *        the receiver
1080     */
1081    augmentObject: function(r, s) {
1082        if (!s||!r) {
1083            throw new Error("Absorb failed, verify dependencies.");
1084        }
1085        var a=arguments, i, p, overrideList=a[2];
1086        if (overrideList && overrideList!==true) { // only absorb the specified properties
1087            for (i=2; i<a.length; i=i+1) {
1088                r[a[i]] = s[a[i]];
1089            }
1090        } else { // take everything, overwriting only if the third parameter is true
1091            for (p in s) {
1092                if (overrideList || !(p in r)) {
1093                    r[p] = s[p];
1094                }
1095            }
1096
1097            L._IEEnumFix(r, s);
1098        }
1099
1100        return r;
1101    },
1102
1103    /**
1104     * Same as YAHOO.lang.augmentObject, except it only applies prototype properties
1105     * @see YAHOO.lang.augmentObject
1106     * @method augmentProto
1107     * @static
1108     * @param {Function} r  the object to receive the augmentation
1109     * @param {Function} s  the object that supplies the properties to augment
1110     * @param {String*|boolean}  arguments zero or more properties methods
1111     *        to augment the receiver with.  If none specified, everything
1112     *        in the supplier will be used unless it would overwrite an existing
1113     *        property in the receiver.  if true is specified as the third
1114     *        parameter, all properties will be applied and will overwrite an
1115     *        existing property in the receiver
1116     */
1117    augmentProto: function(r, s) {
1118        if (!s||!r) {
1119            throw new Error("Augment failed, verify dependencies.");
1120        }
1121        //var a=[].concat(arguments);
1122        var a=[r.prototype,s.prototype], i;
1123        for (i=2;i<arguments.length;i=i+1) {
1124            a.push(arguments[i]);
1125        }
1126        L.augmentObject.apply(this, a);
1127
1128        return r;
1129    },
1130
1131
1132    /**
1133     * Returns a simple string representation of the object or array.
1134     * Other types of objects will be returned unprocessed.  Arrays
1135     * are expected to be indexed.  Use object notation for
1136     * associative arrays.
1137     * @method dump
1138     * @since 2.3.0
1139     * @param o {Object} The object to dump
1140     * @param d {int} How deep to recurse child objects, default 3
1141     * @return {String} the dump result
1142     */
1143    dump: function(o, d) {
1144        var i,len,s=[],OBJ="{...}",FUN="f(){...}",
1145            COMMA=', ', ARROW=' => ';
1146
1147        // Cast non-objects to string
1148        // Skip dates because the std toString is what we want
1149        // Skip HTMLElement-like objects because trying to dump
1150        // an element will cause an unhandled exception in FF 2.x
1151        if (!L.isObject(o)) {
1152            return o + "";
1153        } else if (o instanceof Date || ("nodeType" in o && "tagName" in o)) {
1154            return o;
1155        } else if  (L.isFunction(o)) {
1156            return FUN;
1157        }
1158
1159        // dig into child objects the depth specifed. Default 3
1160        d = (L.isNumber(d)) ? d : 3;
1161
1162        // arrays [1, 2, 3]
1163        if (L.isArray(o)) {
1164            s.push("[");
1165            for (i=0,len=o.length;i<len;i=i+1) {
1166                if (L.isObject(o[i])) {
1167                    s.push((d > 0) ? L.dump(o[i], d-1) : OBJ);
1168                } else {
1169                    s.push(o[i]);
1170                }
1171                s.push(COMMA);
1172            }
1173            if (s.length > 1) {
1174                s.pop();
1175            }
1176            s.push("]");
1177        // objects {k1 => v1, k2 => v2}
1178        } else {
1179            s.push("{");
1180            for (i in o) {
1181                if (L.hasOwnProperty(o, i)) {
1182                    s.push(i + ARROW);
1183                    if (L.isObject(o[i])) {
1184                        s.push((d > 0) ? L.dump(o[i], d-1) : OBJ);
1185                    } else {
1186                        s.push(o[i]);
1187                    }
1188                    s.push(COMMA);
1189                }
1190            }
1191            if (s.length > 1) {
1192                s.pop();
1193            }
1194            s.push("}");
1195        }
1196
1197        return s.join("");
1198    },
1199
1200    /**
1201     * Does variable substitution on a string. It scans through the string
1202     * looking for expressions enclosed in { } braces. If an expression
1203     * is found, it is used a key on the object.  If there is a space in
1204     * the key, the first word is used for the key and the rest is provided
1205     * to an optional function to be used to programatically determine the
1206     * value (the extra information might be used for this decision). If
1207     * the value for the key in the object, or what is returned from the
1208     * function has a string value, number value, or object value, it is
1209     * substituted for the bracket expression and it repeats.  If this
1210     * value is an object, it uses the Object's toString() if this has
1211     * been overridden, otherwise it does a shallow dump of the key/value
1212     * pairs.
1213     *
1214     * By specifying the recurse option, the string is rescanned after
1215     * every replacement, allowing for nested template substitutions.
1216     * The side effect of this option is that curly braces in the
1217     * replacement content must be encoded.
1218     *
1219     * @method substitute
1220     * @since 2.3.0
1221     * @param s {String} The string that will be modified.
1222     * @param o {Object}
1222 An object containing the replacement values
1223     * @param f {Function} An optional function that can be used to
1224     *                     process each match.  It receives the key,
1225     *                     value, and any extra metadata included with
1226     *                     the key inside of the braces.
1227     * @param recurse {boolean} default true - if not false, the replaced
1228     * string will be rescanned so that nested substitutions are possible.
1229     * @return {String} the substituted string
1230     */
1231    substitute: function (s, o, f, recurse) {
1232        var i, j, k, key, v, meta, saved=[], token, lidx=s.length,
1233            DUMP='dump', SPACE=' ', LBRACE='{', RBRACE='}',
1234            dump, objstr;
1235
1236        for (;;) {
1237            i = s.lastIndexOf(LBRACE, lidx);
1238            if (i < 0) {
1239                break;
1240            }
1241            j = s.indexOf(RBRACE, i);
1242            if (i + 1 > j) {
1243                break;
1244            }
1245
1246            //Extract key and meta info
1247            token = s.substring(i + 1, j);
1248            key = token;
1249            meta = null;
1250            k = key.indexOf(SPACE);
1251            if (k > -1) {
1252                meta = key.substring(k + 1);
1253                key = key.substring(0, k);
1254            }
1255
1256            // lookup the value
1257            v = o[key];
1258
1259            // if a substitution function was provided, execute it
1260            if (f) {
1261                v = f(key, v, meta);
1262            }
1263
1264            if (L.isObject(v)) {
1265                if (L.isArray(v)) {
1266                    v = L.dump(v, parseInt(meta, 10));
1267                } else {
1268                    meta = meta || "";
1269
1270                    // look for the keyword 'dump', if found force obj dump
1271                    dump = meta.indexOf(DUMP);
1272                    if (dump > -1) {
1273                        meta = meta.substring(4);
1274                    }
1275
1276                    objstr = v.toString();
1277
1278                    // use the toString if it is not the Object toString
1279                    // and the 'dump' meta info was not found
1280                    if (objstr === OBJECT_TOSTRING || dump > -1) {
1281                        v = L.dump(v, parseInt(meta, 10));
1282                    } else {
1283                        v = objstr;
1284                    }
1285                }
1286            } else if (!L.isString(v) && !L.isNumber(v)) {
1287                // This {block} has no replace string. Save it for later.
1288                v = "~-" + saved.length + "-~";
1289                saved[saved.length] = token;
1290
1291                // break;
1292            }
1293
1294            s = s.substring(0, i) + v + s.substring(j + 1);
1295
1296            if (recurse === false) {
1297                lidx = i-1;
1298            }
1299
1300        }
1301
1302        // restore saved {block}s
1303        for (i=saved.length-1; i>=0; i=i-1) {
1304            s = s.replace(new RegExp("~-" + i + "-~"), "{"  + saved[i] + "}", "g");
1305        }
1306
1307        return s;
1308    },
1309
1310
1311    /**
1312     * Returns a string without any leading or trailing whitespace.  If
1313     * the input is not a string, the input will be returned untouched.
1314     * @method trim
1315     * @since 2.3.0
1316     * @param s {string} the string to trim
1317     * @return {string} the trimmed string
1318     */
1319    trim: function(s){
1320        try {
1321            return s.replace(/^\s+|\s+$/g, "");
1322        } catch(e) {
1323            return s;
1324        }
1325    },
1326
1327    /**
1328     * Returns a new object containing all of the properties of
1329     * all the supplied objects.  The properties from later objects
1330     * will overwrite those in earlier objects.
1331     * @method merge
1332     * @since 2.3.0
1333     * @param arguments {Object*} the objects to merge
1334     * @return the new merged object
1335     */
1336    merge: function() {
1337        var o={}, a=arguments, l=a.length, i;
1338        for (i=0; i<l; i=i+1) {
1339            L.augmentObject(o, a[i], true);
1340        }
1341        return o;
1342    },
1343
1344    /**
1345     * Executes the supplied function in the context of the supplied
1346     * object 'when' milliseconds later.  Executes the function a
1347     * single time unless periodic is set to true.
1348     * @method later
1349     * @since 2.4.0
1350     * @param when {int} the number of milliseconds to wait until the fn
1351     * is executed
1352     * @param o the context object
1353     * @param fn {Function|String} the function to execute or the name of
1354     * the method in the 'o' object to execute
1355     * @param data [Array] data that is provided to the function.  This accepts
1356     * either a single item or an array.  If an array is provided, the
1357     * function is executed with one parameter for each array item.  If
1358     * you need to pass a single array parameter, it needs to be wrapped in
1359     * an array [myarray]
1360     * @param periodic {boolean} if true, executes continuously at supplied
1361     * interval until canceled
1362     * @return a timer object. Call the cancel() method on this object to
1363     * stop the timer.
1364     */
1365    later: function(when, o, fn, data, periodic) {
1366        when = when || 0;
1367        o = o || {};
1368        var m=fn, d=data, f, r;
1369
1370        if (L.isString(fn)) {
1371            m = o[fn];
1372        }
1373
1374        if (!m) {
1375            throw new TypeError("method undefined");
1376        }
1377
1378        if (!L.isUndefined(data) && !L.isArray(d)) {
1379            d = [data];
1380        }
1381
1382        f = function() {
1383            m.apply(o, d || NOTHING);
1384        };
1385
1386        r = (periodic) ? setInterval(f, when) : setTimeout(f, when);
1387
1388        return {
1389            interval: periodic,
1390            cancel: function() {
1391                if (this.interval) {
1392                    clearInterval(r);
1393                } else {
1394                    clearTimeout(r);
1395                }
1396            }
1397        };
1398    },
1399
1400    /**
1401     * A convenience method for detecting a legitimate non-null value.
1402     * Returns false for null/undefined/NaN, true for other values,
1403     * including 0/false/''
1404     * @method isValue
1405     * @since 2.3.0
1406     * @param o {any} the item to test
1407     * @return {boolean} true if it is not null/undefined/NaN || false
1408     */
1409    isValue: function(o) {
1410        // return (o || o === false || o === 0 || o === ''); // Infinity fails
1411return (L.isObject(o) || L.isString(o) || L.isNumber(o) || L.isBoolean(o));
1412    }
1413
1414};
1415
1416/**
1417 * Determines whether or not the property was added
1418 * to the object instance.  Returns false if the property is not present
1419 * in the object, or was inherited from the prototype.
1420 * This abstraction is provided to enable hasOwnProperty for Safari 1.3.x.
1421 * There is a discrepancy between YAHOO.lang.hasOwnProperty and
1422 * Object.prototype.hasOwnProperty when the property is a primitive added to
1423 * both the instance AND prototype with the same value:
1424 * <pre>
1425 * var A = function() {};
1426 * A.prototype.foo = 'foo';
1427 * var a = new A();
1428 * a.foo = 'foo';
1429 * alert(a.hasOwnProperty('foo')); // true
1430 * alert(YAHOO.lang.hasOwnProperty(a, 'foo')); // false when using fallback
1431 * </pre>
1432 * @method hasOwnProperty
1433 * @param {any} o The object being testing
1434 * @param prop {string} the name of the property to test
1435 * @return {boolean} the result
1436 */
1437L.hasOwnProperty = (OP.hasOwnProperty) ?
1438    function(o, prop) {
1439        return o && o.hasOwnProperty && o.hasOwnProperty(prop);
1440    } : function(o, prop) {
1441        return !L.isUndefined(o[prop]) &&
1442                o.constructor.prototype[prop] !== o[prop];
1443    };
1444
1445// new lang wins
1446OB.augmentObject(L, OB, true);
1447
1448/*
1449 * An alias for <a href="YAHOO.lang.html">YAHOO.lang</a>
1450 * @class YAHOO.util.Lang
1451 */
1452YAHOO.util.Lang = L;
1453
1454/**
1455 * Same as YAHOO.lang.augmentObject, except it only applies prototype
1456 * properties.  This is an alias for augmentProto.
1457 * @see YAHOO.lang.augmentObject
1458 * @method augment
1459 * @static
1460 * @param {Function} r  the object to receive the augmentation
1461 * @param {Function} s  the object that supplies the properties to augment
1462 * @param {String*|boolean}  arguments zero or more properties methods to
1463 *        augment the receiver with.  If none specified, everything
1464 *        in the supplier will be used unless it would
1465 *        overwrite an existing property in the receiver.  if true
1466 *        is specified as the third parameter, all properties will
1467 *        be applied and will overwrite an existing property in
1468 *        the receiver
1469 */
1470L.augment = L.augmentProto;
1471
1472/**
1473 * An alias for <a href="YAHOO.lang.html#augment">YAHOO.lang.augment</a>
1474 * @for YAHOO
1475 * @method augment
1476 * @static
1477 * @param {Function} r  the object to receive the augmentation
1478 * @param {Function} s  the object that supplies the properties to augment
1479 * @param {String*}  arguments zero or more properties methods to
1480 *        augment the receiver with.  If none specified, everything
1481 *        in the supplier will be used unless it would
1482 *        overwrite an existing property in the receiver
1483 */
1484YAHOO.augment = L.augmentProto;
1485
1486/**
1487 * An alias for <a href="YAHOO.lang.html#extend">YAHOO.lang.extend</a>
1488 * @method extend
1489 * @static
1490 * @param {Function} subc   the object to modify
1491 * @param {Function} superc the object to inherit
1492 * @param {Object} overrides  additional properties/methods to add to the
1493 *        subclass prototype.  These will override the
1494 *        matching items obtained from the superclass if present.
1495 */
1496YAHOO.extend = L.extend;
1497
1498})();
1499YAHOO.register("yahoo", YAHOO, {version: "2.9.0", build: "2800"});
1500/*
1501Copyright (c) 2011, Yahoo! Inc. All rights reserved.
1502Code licensed under the BSD License:
1503http://developer.yahoo.com/yui/license.html
1504version: 2.9.0
1505*/
1506
1507/**
1508 * The CustomEvent class lets you define events for your application
1509 * that can be subscribed to by one or more independent component.
1510 *
1511 * @param {String}  type The type of event, which is passed to the callback
1512 *                  when the event fires
1513 * @param {Object}  context The context the event will fire from.  "this" will
1514 *                  refer to this object in the callback.  Default value:
1515 *                  the window object.  The listener can override this.
1516 * @param {boolean} silent pass true to prevent the event from writing to
1517 *                  the debugsystem
1518 * @param {int}     signature the signature that the custom event subscriber
1519 *                  will receive. YAHOO.util.CustomEvent.LIST or
1520 *                  YAHOO.util.CustomEvent.FLAT.  The default is
1521 *                  YAHOO.util.CustomEvent.LIST.
1522 * @param fireOnce {boolean} If configured to fire once, the custom event
1523 * will only notify subscribers a single time regardless of how many times
1524 * the event is fired.  In addition, new subscribers will be notified
1525 * immediately if the event has already been fired.
1526 * @namespace YAHOO.util
1527 * @class CustomEvent
1528 * @constructor
1529 */
1530YAHOO.util.CustomEvent = function(type, context, silent, signature, fireOnce) {
1531
1532    /**
1533     * The type of event, returned to subscribers when the event fires
1534     * @property type
1535     * @type string
1536     */
1537    this.type = type;
1538
1539    /**
1540     * The context the event will fire from by default. Defaults to the window obj.
1541     * @property scope
1542     * @type object
1543     */
1544    this.scope = context || window;
1545
1546    /**
1547     * By default all custom events are logged in the debug build. Set silent to true
1548     * to disable debug output for this event.
1549     * @property silent
1550     * @type boolean
1551     */
1552    this.silent = silent;
1553
1554    /**
1555     * If configured to fire once, the custom event will only notify subscribers
1556     * a single time regardless of how many times the event is fired.  In addition,
1557     * new subscribers will be notified immediately if the event has already been
1558     * fired.
1559     * @property fireOnce
1560     * @type boolean
1561     * @default false
1562     */
1563    this.fireOnce = fireOnce;
1564
1565    /**
1566     * Indicates whether or not this event has ever been fired.
1567     * @property fired
1568     * @type boolean
1569     * @default false
1570     */
1571    this.fired = false;
1572
1573    /**
1574     * For fireOnce events the arguments the event was fired with are stored
1575     * so that new subscribers get the proper payload.
1576     * @property firedWith
1577     * @type Array
1578     */
1579    this.firedWith = null;
1580
1581    /**
1582     * Custom events support two styles of arguments provided to the event
1583     * subscribers.
1584     * <ul>
1585     * <li>YAHOO.util.CustomEvent.LIST:
1586     *   <ul>
1587     *   <li>param1: event name</li>
1588     *   <li>param2: array of arguments sent to fire</li>
1589     *   <li>param3: <optional> a custom object supplied by the subscriber</li>
1590     *   </ul>
1591     * </li>
1592     * <li>YAHOO.util.CustomEvent.FLAT
1593     *   <ul>
1594     *   <li>param1: the first argument passed to fire.  If you need to
1595     *           pass multiple parameters, use and array or object literal</li>
1596     *   <li>param2: <optional> a custom object supplied by the subscriber</li>
1597     *   </ul>
1598     * </li>
1599     * </ul>
1600     *   @property signature
1601     *   @type int
1602     */
1603    this.signature = signature || YAHOO.util.CustomEvent.LIST;
1604
1605    /**
1606     * The subscribers to this event
1607     * @property subscribers
1608     * @type Subscriber[]
1609     */
1610    this.subscribers = [];
1611
1612    if (!this.silent) {
1613        YAHOO.log( "Creating " + this, "info", "Event" );
1614    }
1615
1616    var onsubscribeType = "_YUICEOnSubscribe";
1617
1618    // Only add subscribe events for events that are not generated by
1619    // CustomEvent
1620    if (type !== onsubscribeType) {
1621
1622        /**
1623         * Custom events provide a custom event that fires whenever there is
1624         * a new subscriber to the event.  This provides an opportunity to
1625         * handle the case where there is a non-repeating event that has
1626         * already fired has a new subscriber.
1627         *
1628         * @event subscribeEvent
1629         * @type YAHOO.util.CustomEvent
1630         * @param fn {Function} The function to execute
1631         * @param obj <Object> An object to be passed along when the event fires.
1632         * Defaults to the custom event.
1633         * @param override <boolean|Object> If true, the obj passed in becomes the
1634         * execution context of the listener. If an object, that object becomes
1635         * the execution context. Defaults to the custom event.
1636         */
1637        this.subscribeEvent =
1638                new YAHOO.util.CustomEvent(onsubscribeType, this, true);
1639
1640    }
1641
1642
1643    /**
1644     * In order to make it possible to execute the rest of the subscriber
1645     * stack when one thows an exception, the subscribers exceptions are
1646     * caught.  The most recent exception is stored in this property
1647     * @property lastError
1648     * @type Error
1649     */
1650    this.lastError = null;
1651};
1652
1653/**
1654 * Subscriber listener sigature constant.  The LIST type returns three
1655 * parameters: the event type, the array of args passed to fire, and
1656 * the optional custom object
1657 * @property YAHOO.util.CustomEvent.LIST
1658 * @static
1659 * @type int
1660 */
1661YAHOO.util.CustomEvent.LIST = 0;
1662
1663/**
1664 * Subscriber listener sigature constant.  The FLAT type returns two
1665 * parameters: the first argument passed to fire and the optional
1666 * custom object
1667 * @property YAHOO.util.CustomEvent.FLAT
1668 * @static
1669 * @type int
1670 */
1671YAHOO.util.CustomEvent.FLAT = 1;
1672
1673YAHOO.util.CustomEvent.prototype = {
1674
1675    /**
1676     * Subscribes the caller to this event
1677     * @method subscribe
1678     * @param {Function} fn        The function to execute
1679     * @param {Object}   obj       An object to be passed along when the event
1680     * fires.
1681     * @param {boolean|Object} overrideContext If true, the obj passed in
1682     * becomes the execution.
1683     * context of the listener. If an object, that object becomes the execution
1684     * context.
1685     */
1686    subscribe: function(fn, obj, overrideContext) {
1687
1688        if (!fn) {
1689throw new Error("Invalid callback for subscriber to '" + this.type + "'");
1690        }
1691
1692        if (this.subscribeEvent) {
1693            this.subscribeEvent.fire(fn, obj, overrideContext);
1694        }
1695
1696        var s = new YAHOO.util.Subscriber(fn, obj, overrideContext);
1697
1698        if (this.fireOnce && this.fired) {
1699            this.notify(s, this.firedWith);
1700        } else {
1701            this.subscribers.push(s);
1702        }
1703    },
1704
1705    /**
1706     * Unsubscribes subscribers.
1707     * @method unsubscribe
1708     * @param {Function} fn  The subscribed function to remove, if not supplied
1709     *                       all will be removed
1710     * @param {Object}   obj  The custom object passed to subscribe.  This is
1711     *                        optional, but if supplied will be used to
1712     *                        disambiguate multiple listeners that are the same
1713     *                        (e.g., you subscribe many object using a function
1714     *                        that lives on the prototype)
1715     * @return {boolean} True if the subscriber was found and detached.
1716     */
1717    unsubscribe: function(fn, obj) {
1718
1719        if (!fn) {
1720            return this.unsubscribeAll();
1721        }
1722
1723        var found = false;
1724        for (var i=0, len=this.subscribers.length; i<len; ++i) {
1725            var s = this.subscribers[i];
1726            if (s && s.contains(fn, obj)) {
1727                this._delete(i);
1728                found = true;
1729            }
1730        }
1731
1732        return found;
1733    },
1734
1735    /**
1736     * Notifies the subscribers.  The callback functions will be executed
1737     * from the context specified when the event was created, and with the
1738     * following parameters:
1739     *   <ul>
1740     *   <li>The type of event</li>
1741     *   <li>All of the arguments fire() was executed with as an array</li>
1742     *   <li>The custom object (if any) that was passed into the subscribe()
1743     *       method</li>
1744     *   </ul>
1745     * @method fire
1746     * @param {Object*} arguments an arbitrary set of parameters to pass to
1747     *                            the handler.
1748     * @return {boolean} false if one of the subscribers returned false,
1749     *                   true otherwise
1750     */
1751    fire: function() {
1752
1753        this.lastError = null;
1754
1755        var errors = [],
1756            len=this.subscribers.length;
1757
1758
1759        var args=[].slice.call(arguments, 0), ret=true, i, rebuild=false;
1760
1761        if (this.fireOnce) {
1762            if (this.fired) {
1763                YAHOO.log('fireOnce event has already fired: ' + this.type);
1764                return true;
1765            } else {
1766                this.firedWith = args;
1767            }
1768        }
1769
1770        this.fired = true;
1771
1772        if (!len && this.silent) {
1773            //YAHOO.log('DEBUG no subscribers');
1774            return true;
1775        }
1776
1777        if (!this.silent) {
1778            YAHOO.log( "Firing "       + this  + ", " +
1779                       "args: "        + args  + ", " +
1780                       "subscribers: " + len,
1781                       "info", "Event"                  );
1782        }
1783
1784        // make a copy of the subscribers so that there are
1785        // no index problems if one subscriber removes another.
1786        var subs = this.subscribers.slice();
1787
1788        for (i=0; i<len; ++i) {
1789            var s = subs[i];
1790            if (!s || !s.fn) {
1791                rebuild=true;
1792            } else {
1793
1794                ret = this.notify(s, args);
1795
1796                if (false === ret) {
1797                    if (!this.silent) {
1798                        YAHOO.log("Event stopped, sub " + i + " of " + len, "info", "Event");
1799                    }
1800
1801                    break;
1802                }
1803            }
1804        }
1805
1806        return (ret !== false);
1807    },
1808
1809    notify: function(s, args) {
1810
1811        var ret, param=null, scope = s.getScope(this.scope),
1812                 throwErrors = YAHOO.util.Event.throwErrors;
1813
1814        if (!this.silent) {
1815            YAHOO.log( this.type + "-> " + s, "info", "Event" );
1816        }
1817
1818        if (this.signature == YAHOO.util.CustomEvent.FLAT) {
1819
1820            if (args.length > 0) {
1821                param = args[0];
1822            }
1823
1824            try {
1825                ret = s.fn.call(scope, param, s.obj);
1826            } catch(e) {
1827                this.lastError = e;
1828                // errors.push(e);
1829                YAHOO.log(this + " subscriber exception: " + e, "error", "Event");
1830                if (throwErrors) {
1831                    throw e;
1832                }
1833            }
1834        } else {
1835            try {
1836                ret = s.fn.call(scope, this.type, args, s.obj);
1837            } catch(ex) {
1838                this.lastError = ex;
1839                YAHOO.log(this + " subscriber exception: " + ex, "error", "Event");
1840                if (throwErrors) {
1841                    throw ex;
1842                }
1843            }
1844        }
1845
1846        return ret;
1847    },
1848
1849    /**
1850     * Removes all listeners
1851     * @method unsubscribeAll
1852     * @return {int} The number of listeners unsubscribed
1853     */
1854    unsubscribeAll: function() {
1855        var l = this.subscribers.length, i;
1856        for (i=l-1; i>-1; i--) {
1857            this._delete(i);
1858        }
1859
1860        this.subscribers=[];
1861
1862        return l;
1863    },
1864
1865    /**
1866     * @method _delete
1867     * @private
1868     */
1869    _delete: function(index) {
1870        var s = this.subscribers[index];
1871        if (s) {
1872            delete s.fn;
1873            delete s.obj;
1874        }
1875
1876        // this.subscribers[index]=null;
1877        this.subscribers.splice(index, 1);
1878    },
1879
1880    /**
1881     * @method toString
1882     */
1883    toString: function() {
1884         return "CustomEvent: " + "'" + this.type  + "', " +
1885             "context: " + this.scope;
1886
1887    }
1888};
1889
1890/////////////////////////////////////////////////////////////////////
1891
1892/**
1893 * Stores the subscriber information to be used when the event fires.
1894 * @param {Function} fn       The function to execute
1895 * @param {Object}   obj      An object to be passed along when the event fires
1896 * @param {boolean}  overrideContext If true, the obj passed in becomes the execution
1897 *                            context of the listener
1898 * @class Subscriber
1899 * @constructor
1900 */
1901YAHOO.util.Subscriber = function(fn, obj, overrideContext) {
1902
1903    /**
1904     * The callback that will be execute when the event fires
1905     * @property fn
1906     * @type function
1907     */
1908    this.fn = fn;
1909
1910    /**
1911     * An optional custom object that will passed to the callback when
1912     * the event fires
1913     * @property obj
1914     * @type object
1915     */
1916    this.obj = YAHOO.lang.isUndefined(obj) ? null : obj;
1917
1918    /**
1919     * The default execution context for the event listener is defined when the
1920     * event is created (usually the object which contains the event).
1921     * By setting overrideContext to true, the execution context becomes the custom
1922     * object passed in by the subscriber.  If overrideContext is an object, that
1923     * object becomes the context.
1924     * @property overrideContext
1925     * @type boolean|object
1926     */
1927    this.overrideContext = overrideContext;
1928
1929};
1930
1931/**
1932 * Returns the execution context for this listener.  If overrideContext was set to true
1933 * the custom obj will be the context.  If overrideContext is an object, that is the
1934 * context, otherwise the default context will be used.
1935 * @method getScope
1936 * @param {Object} defaultScope the context to use if this listener does not
1937 *                              override it.
1938 */
1939YAHOO.util.Subscriber.prototype.getScope = function(defaultScope) {
1940    if (this.overrideContext) {
1941        if (this.overrideContext === true) {
1942            return this.obj;
1943        } else {
1944            return this.overrideContext;
1945        }
1946    }
1947    return defaultScope;
1948};
1949
1950/**
1951 * Returns true if the fn and obj match this objects properties.
1952 * Used by the unsubscribe method to match the right subscriber.
1953 *
1954 * @method contains
1955 * @param {Function} fn the function to execute
1956 * @param {Object} obj an object to be passed along when the event fires
1957 * @return {boolean} true if the supplied arguments match this
1958 *                   subscriber's signature.
1959 */
1960YAHOO.util.Subscriber.prototype.contains = function(fn, obj) {
1961    if (obj) {
1962        return (this.fn == fn && this.obj == obj);
1963    } else {
1964        return (this.fn == fn);
1965    }
1966};
1967
1968/**
1969 * @method toString
1970 */
1971YAHOO.util.Subscriber.prototype.toString = function() {
1972    return "Subscriber { obj: " + this.obj  +
1973           ", overrideContext: " +  (this.overrideContext || "no") + " }";
1974};
1975
1976/**
1977 * The Event Utility provides utilities for managing DOM Events and tools
1978 * for building event systems
1979 *
1980 * @module event
1981 * @title Event Utility
1982 * @namespace YAHOO.util
1983 * @requires yahoo
1984 */
1985
1986// The first instance of Event will win if it is loaded more than once.
1987// @TODO this needs to be changed so that only the state data that needs to
1988// be preserved is kept, while methods are overwritten/added as 
1988needed.
1989// This means that the module pattern can't be used.
1990if (!YAHOO.util.Event) {
1991
1992/**
1993 * The event utility provides functions to add and remove event listeners,
1994 * event cleansing.  It also tries to automatically remove listeners it
1995 * registers during the unload event.
1996 *
1997 * @class Event
1998 * @static
1999 */
2000    YAHOO.util.Event = function() {
2001
2002        /**
2003         * True after the onload event has fired
2004         * @property loadComplete
2005         * @type boolean
2006         * @static
2007         * @private
2008         */
2009        var loadComplete =  false,
2010
2011        /**
2012         * Cache of wrapped listeners
2013         * @property listeners
2014         * @type array
2015         * @static
2016         * @private
2017         */
2018        listeners = [],
2019
2020
2021        /**
2022         * User-defined unload function that will be fired before all events
2023         * are detached
2024         * @property unloadListeners
2025         * @type array
2026         * @static
2027         * @private
2028         */
2029        unloadListeners = [],
2030
2031        /**
2032         * The number of times to poll after window.onload.  This number is
2033         * increased if additional late-bound handlers are requested after
2034         * the page load.
2035         * @property retryCount
2036         * @static
2037         * @private
2038         */
2039        retryCount = 0,
2040
2041        /**
2042         * onAvailable listeners
2043         * @property onAvailStack
2044         * @static
2045         * @private
2046         */
2047        onAvailStack = [],
2048
2049        /**
2050         * Counter for auto id generation
2051         * @property counter
2052         * @static
2053         * @private
2054         */
2055        counter = 0,
2056
2057        /**
2058         * Normalized keycodes for webkit/safari
2059         * @property webkitKeymap
2060         * @type {int: int}
2061         * @private
2062         * @static
2063         * @final
2064         */
2065         webkitKeymap = {
2066            63232: 38, // up
2067            63233: 40, // down
2068            63234: 37, // left
2069            63235: 39, // right
2070            63276: 33, // page up
2071            63277: 34, // page down
2072            25: 9      // SHIFT-TAB (Safari provides a different key code in
2073                       // this case, even though the shiftKey modifier is set)
2074        },
2075
2076        isIE = YAHOO.env.ua.ie,
2077
2078        // String constants used by the addFocusListener and removeFocusListener methods
2079
2080        FOCUSIN = "focusin",
2081        FOCUSOUT = "focusout";
2082
2083        return {
2084
2085            /**
2086             * The number of times we should look for elements that are not
2087             * in the DOM at the time the event is requested after the document
2088             * has been loaded.  The default is 500@amp;40 ms, so it will poll
2089             * for 20 seconds or until all outstanding handlers are bound
2090             * (whichever comes first).
2091             * @property POLL_RETRYS
2092             * @type int
2093             * @static
2094             * @final
2095             */
2096            POLL_RETRYS: 500,
2097
2098            /**
2099             * The poll interval in milliseconds
2100             * @property POLL_INTERVAL
2101             * @type int
2102             * @static
2103             * @final
2104             */
2105            POLL_INTERVAL: 40,
2106
2107            /**
2108             * Element to bind, int constant
2109             * @property EL
2110             * @type int
2111             * @static
2112             * @final
2113             */
2114            EL: 0,
2115
2116            /**
2117             * Type of event, int constant
2118             * @property TYPE
2119             * @type int
2120             * @static
2121             * @final
2122             */
2123            TYPE: 1,
2124
2125            /**
2126             * Function to execute, int constant
2127             * @property FN
2128             * @type int
2129             * @static
2130             * @final
2131             */
2132            FN: 2,
2133
2134            /**
2135             * Function wrapped for context correction and cleanup, int constant
2136             * @property WFN
2137             * @type int
2138             * @static
2139             * @final
2140             */
2141            WFN: 3,
2142
2143            /**
2144             * Object passed in by the user that will be returned as a
2145             * parameter to the callback, int constant.  Specific to
2146             * unload listeners
2147             * @property OBJ
2148             * @type int
2149             * @static
2150             * @final
2151             */
2152            UNLOAD_OBJ: 3,
2153
2154            /**
2155             * Adjusted context, either the element we are registering the event
2156             * on or the custom object passed in by the listener, int constant
2157             * @property ADJ_SCOPE
2158             * @type int
2159             * @static
2160             * @final
2161             */
2162            ADJ_SCOPE: 4,
2163
2164            /**
2165             * The original obj passed into addListener
2166             * @property OBJ
2167             * @type int
2168             * @static
2169             * @final
2170             */
2171            OBJ: 5,
2172
2173            /**
2174             * The original context parameter passed into addListener
2175             * @property OVERRIDE
2176             * @type int
2177             * @static
2178             * @final
2179             */
2180            OVERRIDE: 6,
2181
2182            /**
2183             * The original capture parameter passed into addListener
2184             * @property CAPTURE
2185             * @type int
2186             * @static
2187             * @final
2188             */
2189            CAPTURE: 7,
2190
2191            /**
2192             * addListener/removeListener can throw errors in unexpected scenarios.
2193             * These errors are suppressed, the method returns false, and this property
2194             * is set
2195             * @property lastError
2196             * @static
2197             * @type Error
2198             */
2199            lastError: null,
2200
2201            /**
2202             * Safari detection
2203             * @property isSafari
2204             * @private
2205             * @static
2206             * @deprecated use YAHOO.env.ua.webkit
2207             */
2208            isSafari: YAHOO.env.ua.webkit,
2209
2210            /**
2211             * webkit version
2212             * @property webkit
2213             * @type string
2214             * @private
2215             * @static
2216             * @deprecated use YAHOO.env.ua.webkit
2217             */
2218            webkit: YAHOO.env.ua.webkit,
2219
2220            /**
2221             * IE detection
2222             * @property isIE
2223             * @private
2224             * @static
2225             * @deprecated use YAHOO.env.ua.ie
2226             */
2227            isIE: isIE,
2228
2229            /**
2230             * poll handle
2231             * @property _interval
2232             * @static
2233             * @private
2234             */
2235            _interval: null,
2236
2237            /**
2238             * document readystate poll handle
2239             * @property _dri
2240             * @static
2241             * @private
2242             */
2243             _dri: null,
2244
2245
2246            /**
2247             * Map of special event types
2248             * @property _specialTypes
2249             * @static
2250             * @private
2251             */
2252            _specialTypes: {
2253                focusin: (isIE ? "focusin" : "focus"),
2254                focusout: (isIE ? "focusout" : "blur")
2255            },
2256
2257
2258            /**
2259             * True when the document is initially usable
2260             * @property DOMReady
2261             * @type boolean
2262             * @static
2263             */
2264            DOMReady: false,
2265
2266            /**
2267             * Errors thrown by subscribers of custom events are caught
2268             * and the error message is written to the debug console.  If
2269             * this property is set to true, it will also re-throw the
2270             * error.
2271             * @property throwErrors
2272             * @type boolean
2273             * @default false
2274             */
2275            throwErrors: false,
2276
2277
2278            /**
2279             * @method startInterval
2280             * @static
2281             * @private
2282             */
2283            startInterval: function() {
2284                if (!this._interval) {
2285                    // var self = this;
2286                    // var callback = function() { self._tryPreloadAttach(); };
2287                    // this._interval = setInterval(callback, this.POLL_INTERVAL);
2288                    this._interval = YAHOO.lang.later(this.POLL_INTERVAL, this, this._tryPreloadAttach, null, true);
2289                }
2290            },
2291
2292            /**
2293             * Executes the supplied callback when the item with the supplied
2294             * id is found.  This is meant to be used to execute behavior as
2295             * soon as possible as the page loads.  If you use this after the
2296             * initial page load it will poll for a fixed time for the element.
2297             * The number of times it will poll and the frequency are
2298             * configurable.  By default it will poll for 10 seconds.
2299             *
2300             * <p>The callback is executed with a single parameter:
2301             * the custom object parameter, if provided.</p>
2302             *
2303             * @method onAvailable
2304             *
2305             * @param {string||string[]}   id the id of the element, or an array
2306             * of ids to look for.
2307             * @param {function} fn what to execute when the element is found.
2308             * @param {object}   obj an optional object to be passed back as
2309             *                   a parameter to fn.
2310             * @param {boolean|object}  overrideContext If set to true, fn will execute
2311             *                   in the context of obj, if set to an object it
2312             *                   will execute in the context of that object
2313             * @param checkContent {boolean} check child node readiness (onContentReady)
2314             * @static
2315             */
2316            onAvailable: function(id, fn, obj, overrideContext, checkContent) {
2317
2318                var a = (YAHOO.lang.isString(id)) ? [id] : id;
2319
2320                for (var i=0; i<a.length; i=i+1) {
2321                    onAvailStack.push({id:         a[i],
2322                                       fn:         fn,
2323                                       obj:        obj,
2324                                       overrideContext:   overrideContext,
2325                                       checkReady: checkContent });
2326                }
2327
2328                retryCount = this.POLL_RETRYS;
2329
2330                this.startInterval();
2331            },
2332
2333            /**
2334             * Works the same way as onAvailable, but additionally checks the
2335             * state of sibling elements to determine if the content of the
2336             * available element is safe to modify.
2337             *
2338             * <p>The callback is executed with a single parameter:
2339             * the custom object parameter, if provided.</p>
2340             *
2341             * @method onContentReady
2342             *
2343             * @param {string}   id the id of the element to look for.
2344             * @param {function} fn what to execute when the element is ready.
2345             * @param {object}   obj an optional object to be passed back as
2346             *                   a parameter to fn.
2347             * @param {boolean|object}  overrideContext If set to true, fn will execute
2348             *                   in the context of obj.  If an object, fn will
2349             *                   exectute in the context of that object
2350             *
2351             * @static
2352             */
2353            onContentReady: function(id, fn, obj, overrideContext) {
2354                this.onAvailable(id, fn, obj, overrideContext, true);
2355            },
2356
2357            /**
2358             * Executes the supplied callback when the DOM is first usable.  This
2359             * will execute immediately if called after the DOMReady event has
2360             * fired.   @todo the DOMContentReady event does not fire when the
2361             * script is dynamically injected into the page.  This means the
2362             * DOMReady custom event will never fire in FireFox or Opera when the
2363             * library is injected.  It _will_ fire in Safari, and the IE
2364             * implementation would allow for us to fire it if the defered script
2365             * is not available.  We want this to behave the same in all browsers.
2366             * Is there a way to identify when the script has been injected
2367             * instead of included inline?  Is there a way to know whether the
2368             * window onload event has fired without having had a listener attached
2369             * to it when it did so?
2370             *
2371             * <p>The callback is a CustomEvent, so the signature is:</p>
2372             * <p>type &lt;string&gt;, args &lt;array&gt;, customobject &lt;object&gt;</p>
2373             * <p>For DOMReady events, there are no fire argments, so the
2374             * signature is:</p>
2375             * <p>"DOMReady", [], obj</p>
2376             *
2377             *
2378             * @method onDOMReady
2379             *
2380             * @param {function} fn what to execute when the element is found.
2381             * @param {object}   obj an optional object to be passed back as
2382             *                   a parameter to fn.
2383             * @param {boolean|object}  overrideContext If set to true, fn will execute
2384             *                   in the context of obj, if set to an object it
2385             *                   will execute in the context of that object
2386             *
2387             * @static
2388             */
2389            // onDOMReady: function(fn, obj, overrideContext) {
2390            onDOMReady: function() {
2391                this.DOMReadyEvent.subscribe.apply(this.DOMReadyEvent, arguments);
2392            },
2393
2394
2395            /**
2396             * Appends an event handler
2397             *
2398             * @method _addListener
2399             *
2400             * @param {String|HTMLElement|Array|NodeList} el An id, an element
2401             *  reference, or a collection of ids and/or elements to assign the
2402             *  listener to.
2403             * @param {String}   sType     The type of event to append
2404             * @param {Function} fn        The method the event invokes
2405             * @param {Object}   obj    An arbitrary object that will be
2406             *                             passed as a parameter to the handler
2407             * @param {Boolean|object}  overrideContext  If true, the obj passed in becomes
2408             *                             the execution context of the listener. If an
2409             *                             object, this object becomes the execution
2410             *                             context.
2411             * @param {boolen}      capture capture or bubble phase
2412             * @return {Boolean} True if the action was successful or defered,
2413             *                        false if one or more of the elements
2414             *                        could not have the listener attached,
2415             *                        or if the operation throws an exception.
2416             * @private
2417             * @static
2418             */
2419            _addListener: function(el, sType, fn, obj, overrideContext, bCapture) {
2420
2421                if (!fn || !fn.call) {
2422                    YAHOO.log(sType + " addListener failed, invalid callback", "error", "Event");
2423                    return false;
2424                }
2425
2426                // The el argument can be an array of elements or element ids.
2427                if ( this._isValidCollection(el)) {
2428                    var ok = true;
2429                    for (var i=0,len=el.length; i<len; ++i) {
2430                        ok = this.on(el[i],
2431                                       sType,
2432                                       fn,
2433                                       obj,
2434                                       overrideContext) && ok;
2435                    }
2436                    return ok;
2437
2438                } else if (YAHOO.lang.isString(el)) {
2439                    var oEl = this.getEl(el);
2440                    // If the el argument is a string, we assume it is
2441                    // actually the id of the element.  If the page is loaded
2442                    // we convert el to the actual element, otherwise we
2443                    // defer attaching the event until onload event fires
2444
2445                    // check to see if we need to delay hooking up the event
2446                    // until after the page loads.
2447                    if (oEl) {
2448                        el = oEl;
2449                    } else {
2450                        // defer adding the event until the element is available
2451                        this.onAvailable(el, function() {
2452                           YAHOO.util.Event._addListener(el, sType, fn, obj, overrideContext, bCapture);
2453                        });
2454
2455                        return true;
2456                    }
2457                }
2458
2459                // Element should be an html element or an array if we get
2460                // here.
2461                if (!el) {
2462                    // this.logger.debug("unable to attach event " + sType);
2463                    return false;
2464                }
2465
2466                // we need to make sure we fire registered unload events
2467                // prior to automatically unhooking them.  So we hang on to
2468                // these instead of attaching them to the window and fire the
2469                // handles explicitly during our one unload event.
2470                if ("unload" == sType && obj !== this) {
2471                    unloadListeners[unloadListeners.length] =
2472                            [el, sType, fn, obj, overrideContext];
2473                    return true;
2474                }
2475
2476                // this.logger.debug("Adding handler: " + el + ", " + sType);
2477
2478                // if the user chooses to override the context, we use the custom
2479                // object passed in, otherwise the executing context will be the
2480                // HTML element that the event is registered on
2481                var context = el;
2482                if (overrideContext) {
2483                    if (overrideContext === true) {
2484                        context = obj;
2485                    } else {
2486                        context = overrideContext;
2487                    }
2488                }
2489
2490                // wrap the function so we can return the obj object when
2491                // the event fires;
2492                var wrappedFn = function(e) {
2493                        return fn.call(context, YAHOO.util.Event.getEvent(e, el),
2494                                obj);
2495                    };
2496
2497                var li = [el, sType, fn, wrappedFn, context, obj, overrideContext, bCapture];
2498                var index = listeners.length;
2499                // cache the listener so we can try to automatically unload
2500                listeners[index] = li;
2501
2502                try {
2503                    this._simpleAdd(el, sType, wrappedFn, bCapture);
2504                } catch(ex) {
2505                    // handle an error trying to attach an event.  If it fails
2506                    // we need to clean up the cache
2507                    this.lastError = ex;
2508                    this.removeListener(el, sType, fn);
2509                    return false;
2510                }
2511
2512                return true;
2513
2514            },
2515
2516            /**
2517             * Checks to see if the type requested is a special type
2518             * (as defined by the _specialTypes hash), and (if so) returns
2519             * the special type name.
2520             *
2521             * @method _getType
2522             *
2523             * @param {String}   sType     The type to look up
2524             * @private
2525             */
2526            _getType: function (type) {
2527
2528                return this._specialTypes[type] || type;
2529
2530            },
2531
2532
2533            /**
2534             * Appends an event handler
2535             *
2536             * @method addListener
2537             *
2538             * @param {String|HTMLElement|Array|NodeList} el An id, an element
2539             *  reference, or a collection of ids and/or elements to assign the
2540             *  listener to.
2541             * @param {String}   sType     The type of event to append
2542             * @param {Function} fn        The method the event invokes
2543             * @param {Object}   obj    An arbitrary object that will be
2544             *                             passed as a parameter to the handler
2545             * @param {Boolean|object}  overrideContext  If true, the obj passed in becomes
2546             *                             the execution context of the listener. If an
2547             *                             object, this object becomes the execution
2548             *                             context.
2549             * @return {Boolean} True if the action was successful or defered,
2550             *                        false if one or more of the elements
2551             *                        could not have the listener attached,
2552             *                        or if the operation throws an exception.
2553             * @static
2554             */
2555            addListener: function (el, sType, fn, obj, overrideContext) {
2556
2557                var capture = ((sType == FOCUSIN || sType == FOCUSOUT) && !YAHOO.env.ua.ie) ? true : false;
2558
2559                return this._addListener(el, this._getType(sType), fn, obj, overrideContext, capture);
2560
2561            },
2562
2563
2564            /**
2565             * Attaches a focusin event listener to the specified element for
2566             * the purpose of listening for the focus event on the element's
2567             * descendants.
2568             * @method addFocusListener
2569             *
2570             * @param {String|HTMLElement|Array|NodeList} el An id, an element
2571             *  reference, or a collection of ids and/or elements to assign the
2572             *  listener to.
2573             * @param {Function} fn        The method the event invokes
2574             * @param {Object}   obj    An arbitrary object that will be
2575             *                             passed as a parameter to the handler
2576             * @param {Boolean|object}  overrideContext  If true, the obj passed in becomes
2577             *                             the execution context of the listener. If an
2578             *                             object, this object becomes the execution
2579             *                             context.
2580             * @return {Boolean} True if the action was successful or defered,
2581             *                        false if one or more of the elements
2582             *                        could not have the listener attached,
2583             *                        or if the operation throws an exception.
2584             * @static
2585            * @deprecated use YAHOO.util.Event.on and specify "focusin" as the event type.
2586             */
2587            addFocusListener: function (el, fn, obj, overrideContext) {
2588                return this.on(el, FOCUSIN, fn, obj, overrideContext);
2589            },
2590
2591
2592            /**
2593             * Removes a focusin event listener to the specified element for
2594             * the purpose of listening for the focus event on the element's
2595             * descendants.
2596             *
2597             * @method removeFocusListener
2598             *
2599             * @param {String|HTMLElement|Array|NodeList} el An id, an element
2600             *  reference, or a collection of ids and/or elements to remove
2601             *  the listener from.
2602             * @param {Function} fn the method the event invokes.  If fn is
2603             *  undefined, then all event handlers for the type of event are
2604             *  removed.
2605             * @return {boolean} true if the unbind was successful, false
2606             *  otherwise.
2607             * @static
2608             * @deprecated use YAHOO.util.Event.removeListener and specify "focusin" as the event type.
2609             */
2610            removeFocusListener: function (el, fn) {
2611                return this.removeListener(el, FOCUSIN, fn);
2612            },
2613
2614            /**
2615             * Attaches a focusout event listener to the specified element for
2616             * the purpose of listening for the blur event on the element's
2617             * descendants.
2618             *
2619             * @method addBlurListener
2620             *
2621             * @param {String|HTMLElement|Array|NodeList} el An id, an element
2622             *  reference, or a collection of ids and/or elements to assign the
2623             *  listener to.
2624             * @param {Function} fn        The method the event invokes
2625             * @param {Object}   obj    An arbitrary object that will be
2626             *                             passed as a parameter to the handler
2627             * @param {Boolean|object}  overrideContext  If true, the obj passed in becomes
2628             *                             the execution context of the listener. If an
2629             *                             object, this object becomes the execution
2630             *                             context.
2631             * @return {Boolean} True if the action was successful or defered,
2632             *                        false if one or more of the elements
2633             *                        could not have the listener attached,
2634             *                        or if the operation throws an exception.
2635             * @static
2636             * @deprecated use YAHOO.util.Event.on and specify "focusout" as the event type.
2637             */
2638            addBlurListener: function (el, fn, obj, overrideContext) {
2639                return this.on(el, FOCUSOUT, fn, obj, overrideContext);
2640            },
2641
2642            /**
2643             * Removes a focusout event listener to the specified element for
2644             * the purpose of listening for the blur event on the element's
2645             * descendants.
2646             *
2647             * @method removeBlurListener
2648             *
2649             * @param {String|HTMLElement|Array|NodeList} el An id, an element
2650             *  reference, or a collection of ids and/or elements to remove
2651             *  the listener from.
2652             * @param {Function} fn the method the event invokes.  If fn is
2653             *  undefined, then all event handlers for the type of event are
2654             *  removed.
2655             * @return {boolean} true if the unbind was successful, false
2656             *  otherwise.
2657             * @static
2658             * @deprecated use YAHOO.util.Event.removeListener and specify "focusout" as the event type.
2659             */
2660            removeBlurListener: function (el, fn) {
2661                return this.removeListener(el, FOCUSOUT, fn);
2662            },
2663
2664            /**
2665             * Removes an event listener
2666             *
2667             * @method removeListener
2668             *
2669             * @param {String|HTMLElement|Array|NodeList} el An id, an element
2670             *  reference, or a collection of ids and/or elements to remove
2671             *  the listener from.
2672             * @param {String} sType the type of event to remove.
2673             * @param {Function} fn the method the event invokes.  If fn is
2674             *  undefined, then all event handlers for the type of event are
2675             *  removed.
2676             * @return {boolean} true if the unbind was successful, false
2677             *  otherwise.
2678             * @static
2679             */
2680            removeListener: function(el, sType, fn) {
2681                var i, len, li;
2682
2683                sType = this._getType(sType);
2684
2685                // The el argument can be a string
2686                if (typeof el == "string") {
2687                    el = this.getEl(el);
2688                // The el argument can be an array of elements or element ids.
2689                } else if ( this._isValidCollection(el)) {
2690                    var ok = true;
2691                    for (i=el.length-1; i>-1; i--) {
2692                        ok = ( this.removeListener(el[i], sType, fn) && ok );
2693                    }
2694                    return ok;
2695                }
2696
2697                if (!fn || !fn.call) {
2698                    // this.logger.debug("Error, function is not val
2698id " + fn);
2699                    //return false;
2700                    return this.purgeElement(el, false, sType);
2701                }
2702
2703                if ("unload" == sType) {
2704
2705                    for (i=unloadListeners.length-1; i>-1; i--) {
2706                        li = unloadListeners[i];
2707                        if (li &&
2708                            li[0] == el &&
2709                            li[1] == sType &&
2710                            li[2] == fn) {
2711                                unloadListeners.splice(i, 1);
2712                                // unloadListeners[i]=null;
2713                                return true;
2714                        }
2715                    }
2716
2717                    return false;
2718                }
2719
2720                var cacheItem = null;
2721
2722                // The index is a hidden parameter; needed to remove it from
2723                // the method signature because it was tempting users to
2724                // try and take advantage of it, which is not possible.
2725                var index = arguments[3];
2726
2727                if ("undefined" === typeof index) {
2728                    index = this._getCacheIndex(listeners, el, sType, fn);
2729                }
2730
2731                if (index >= 0) {
2732                    cacheItem = listeners[index];
2733                }
2734
2735                if (!el || !cacheItem) {
2736                    // this.logger.debug("cached listener not found");
2737                    return false;
2738                }
2739
2740                // this.logger.debug("Removing handler: " + el + ", " + sType);
2741
2742                var bCapture = cacheItem[this.CAPTURE] === true ? true : false;
2743
2744                try {
2745                    this._simpleRemove(el, sType, cacheItem[this.WFN], bCapture);
2746                } catch(ex) {
2747                    this.lastError = ex;
2748                    return false;
2749                }
2750
2751                // removed the wrapped handler
2752                delete listeners[index][this.WFN];
2753                delete listeners[index][this.FN];
2754                listeners.splice(index, 1);
2755                // listeners[index]=null;
2756
2757                return true;
2758
2759            },
2760
2761            /**
2762             * Returns the event's target element.  Safari sometimes provides
2763             * a text node, and this is automatically resolved to the text
2764             * node's parent so that it behaves like other browsers.
2765             * @method getTarget
2766             * @param {Event} ev the event
2767             * @param {boolean} resolveTextNode when set to true the target's
2768             *                  parent will be returned if the target is a
2769             *                  text node.  @deprecated, the text node is
2770             *                  now resolved automatically
2771             * @return {HTMLElement} the event's target
2772             * @static
2773             */
2774            getTarget: function(ev, resolveTextNode) {
2775                var t = ev.target || ev.srcElement;
2776                return this.resolveTextNode(t);
2777            },
2778
2779            /**
2780             * In some cases, some browsers will return a text node inside
2781             * the actual element that was targeted.  This normalizes the
2782             * return value for getTarget and getRelatedTarget.
2783             *
2784             * If accessing a property of the node throws an error, this is
2785             * probably the anonymous div wrapper Gecko adds inside text
2786             * nodes.  This likely will only occur when attempting to access
2787             * the relatedTarget.  In this case, we now return null because
2788             * the anonymous div is completely useless and we do not know
2789             * what the related target was because we can't even get to
2790             * the element's parent node.
2791             *
2792             * @method resolveTextNode
2793             * @param {HTMLElement} node node to resolve
2794             * @return {HTMLElement} the normized node
2795             * @static
2796             */
2797            resolveTextNode: function(n) {
2798                try {
2799                    if (n && 3 == n.nodeType) {
2800                        return n.parentNode;
2801                    }
2802                } catch(e) {
2803                    return null;
2804                }
2805
2806                return n;
2807            },
2808
2809            /**
2810             * Returns the event's pageX
2811             * @method getPageX
2812             * @param {Event} ev the event
2813             * @return {int} the event's pageX
2814             * @static
2815             */
2816            getPageX: function(ev) {
2817                var x = ev.pageX;
2818                if (!x && 0 !== x) {
2819                    x = ev.clientX || 0;
2820
2821                    if ( this.isIE ) {
2822                        x += this._getScrollLeft();
2823                    }
2824                }
2825
2826                return x;
2827            },
2828
2829            /**
2830             * Returns the event's pageY
2831             * @method getPageY
2832             * @param {Event} ev the event
2833             * @return {int} the event's pageY
2834             * @static
2835             */
2836            getPageY: function(ev) {
2837                var y = ev.pageY;
2838                if (!y && 0 !== y) {
2839                    y = ev.clientY || 0;
2840
2841                    if ( this.isIE ) {
2842                        y += this._getScrollTop();
2843                    }
2844                }
2845
2846
2847                return y;
2848            },
2849
2850            /**
2851             * Returns the pageX and pageY properties as an indexed array.
2852             * @method getXY
2853             * @param {Event} ev the event
2854             * @return {[x, y]} the pageX and pageY properties of the event
2855             * @static
2856             */
2857            getXY: function(ev) {
2858                return [this.getPageX(ev), this.getPageY(ev)];
2859            },
2860
2861            /**
2862             * Returns the event's related target
2863             * @method getRelatedTarget
2864             * @param {Event} ev the event
2865             * @return {HTMLElement} the event's relatedTarget
2866             * @static
2867             */
2868            getRelatedTarget: function(ev) {
2869                var t = ev.relatedTarget;
2870                if (!t) {
2871                    if (ev.type == "mouseout") {
2872                        t = ev.toElement;
2873                    } else if (ev.type == "mouseover") {
2874                        t = ev.fromElement;
2875                    }
2876                }
2877
2878                return this.resolveTextNode(t);
2879            },
2880
2881            /**
2882             * Returns the time of the event.  If the time is not included, the
2883             * event is modified using the current time.
2884             * @method getTime
2885             * @param {Event} ev the event
2886             * @return {Date} the time of the event
2887             * @static
2888             */
2889            getTime: function(ev) {
2890                if (!ev.time) {
2891                    var t = new Date().getTime();
2892                    try {
2893                        ev.time = t;
2894                    } catch(ex) {
2895                        this.lastError = ex;
2896                        return t;
2897                    }
2898                }
2899
2900                return ev.time;
2901            },
2902
2903            /**
2904             * Convenience method for stopPropagation + preventDefault
2905             * @method stopEvent
2906             * @param {Event} ev the event
2907             * @static
2908             */
2909            stopEvent: function(ev) {
2910                this.stopPropagation(ev);
2911                this.preventDefault(ev);
2912            },
2913
2914            /**
2915             * Stops event propagation
2916             * @method stopPropagation
2917             * @param {Event} ev the event
2918             * @static
2919             */
2920            stopPropagation: function(ev) {
2921                if (ev.stopPropagation) {
2922                    ev.stopPropagation();
2923                } else {
2924                    ev.cancelBubble = true;
2925                }
2926            },
2927
2928            /**
2929             * Prevents the default behavior of the event
2930             * @method preventDefault
2931             * @param {Event} ev the event
2932             * @static
2933             */
2934            preventDefault: function(ev) {
2935                if (ev.preventDefault) {
2936                    ev.preventDefault();
2937                } else {
2938                    ev.returnValue = false;
2939                }
2940            },
2941
2942            /**
2943             * Finds the event in the window object, the caller's arguments, or
2944             * in the arguments of another method in the callstack.  This is
2945             * executed automatically for events registered through the event
2946             * manager, so the implementer should not normally need to execute
2947             * this function at all.
2948             * @method getEvent
2949             * @param {Event} e the event parameter from the handler
2950             * @param {HTMLElement} boundEl the element the listener is attached to
2951             * @return {Event} the event
2952             * @static
2953             */
2954            getEvent: function(e, boundEl) {
2955                var ev = e || window.event;
2956
2957                if (!ev) {
2958                    var c = this.getEvent.caller;
2959                    while (c) {
2960                        ev = c.arguments[0];
2961                        if (ev && Event == ev.constructor) {
2962                            break;
2963                        }
2964                        c = c.caller;
2965                    }
2966                }
2967
2968                return ev;
2969            },
2970
2971            /**
2972             * Returns the charcode for an event
2973             * @method getCharCode
2974             * @param {Event} ev the event
2975             * @return {int} the event's charCode
2976             * @static
2977             */
2978            getCharCode: function(ev) {
2979                var code = ev.keyCode || ev.charCode || 0;
2980
2981                // webkit key normalization
2982                if (YAHOO.env.ua.webkit && (code in webkitKeymap)) {
2983                    code = webkitKeymap[code];
2984                }
2985                return code;
2986            },
2987
2988            /**
2989             * Locating the saved event handler data by function ref
2990             *
2991             * @method _getCacheIndex
2992             * @static
2993             * @private
2994             */
2995            _getCacheIndex: function(a, el, sType, fn) {
2996                for (var i=0, l=a.length; i<l; i=i+1) {
2997                    var li = a[i];
2998                    if ( li                 &&
2999                         li[this.FN] == fn  &&
3000                         li[this.EL] == el  &&
3001                         li[this.TYPE] == sType ) {
3002                        return i;
3003                    }
3004                }
3005
3006                return -1;
3007            },
3008
3009            /**
3010             * Generates an unique ID for the element if it does not already
3011             * have one.
3012             * @method generateId
3013             * @param el the element to create the id for
3014             * @return {string} the resulting id of the element
3015             * @static
3016             */
3017            generateId: function(el) {
3018                var id = el.id;
3019
3020                if (!id) {
3021                    id = "yuievtautoid-" + counter;
3022                    ++counter;
3023                    el.id = id;
3024                }
3025
3026                return id;
3027            },
3028
3029
3030            /**
3031             * We want to be able to use getElementsByTagName as a collection
3032             * to attach a group of events to.  Unfortunately, different
3033             * browsers return different types of collections.  This function
3034             * tests to determine if the object is array-like.  It will also
3035             * fail if the object is an array, but is empty.
3036             * @method _isValidCollection
3037             * @param o the object to test
3038             * @return {boolean} true if the object is array-like and populated
3039             * @static
3040             * @private
3041             */
3042            _isValidCollection: function(o) {
3043                try {
3044                    return ( o                     && // o is something
3045                             typeof o !== "string" && // o is not a string
3046                             o.length              && // o is indexed
3047                             !o.tagName            && // o is not an HTML element
3048                             !o.alert              && // o is not a window
3049                             typeof o[0] !== "undefined" );
3050                } catch(ex) {
3051                    YAHOO.log("node access error (xframe?)", "warn");
3052                    return false;
3053                }
3054
3055            },
3056
3057            /**
3058             * @private
3059             * @property elCache
3060             * DOM element cache
3061             * @static
3062             * @deprecated Elements are not cached due to issues that arise when
3063             * elements are removed and re-added
3064             */
3065            elCache: {},
3066
3067            /**
3068             * We cache elements bound by id because when the unload event
3069             * fires, we can no longer use document.getElementById
3070             * @method getEl
3071             * @static
3072             * @private
3073             * @deprecated Elements are not cached any longer
3074             */
3075            getEl: function(id) {
3076                return (typeof id === "string") ? document.getElementById(id) : id;
3077            },
3078
3079            /**
3080             * Clears the element cache
3081             * @deprecated Elements are not cached any longer
3082             * @method clearCache
3083             * @static
3084             * @private
3085             */
3086            clearCache: function() { },
3087
3088            /**
3089             * Custom event the fires when the dom is initially usable
3090             * @event DOMReadyEvent
3091             */
3092            DOMReadyEvent: new YAHOO.util.CustomEvent("DOMReady", YAHOO, 0, 0, 1),
3093
3094            /**
3095             * hook up any deferred listeners
3096             * @method _load
3097             * @static
3098             * @private
3099             */
3100            _load: function(e) {
3101
3102                if (!loadComplete) {
3103                    loadComplete = true;
3104                    var EU = YAHOO.util.Event;
3105
3106                    // Just in case DOMReady did not go off for some reason
3107                    EU._ready();
3108
3109                    // Available elements may not have been detected before the
3110                    // window load event fires. Try to find them now so that the
3111                    // the user is more likely to get the onAvailable notifications
3112                    // before the window load notification
3113                    EU._tryPreloadAttach();
3114
3115                }
3116            },
3117
3118            /**
3119             * Fires the DOMReady event listeners the first time the document is
3120             * usable.
3121             * @method _ready
3122             * @static
3123             * @private
3124             */
3125            _ready: function(e) {
3126                var EU = YAHOO.util.Event;
3127                if (!EU.DOMReady) {
3128                    EU.DOMReady=true;
3129
3130                    // Fire the content ready custom event
3131                    EU.DOMReadyEvent.fire();
3132
3133                    // Remove the DOMContentLoaded (FF/Opera)
3134                    EU._simpleRemove(document, "DOMContentLoaded", EU._ready);
3135                }
3136            },
3137
3138            /**
3139             * Polling function that runs before the onload event fires,
3140             * attempting to attach to DOM Nodes as soon as they are
3141             * available
3142             * @method _tryPreloadAttach
3143             * @static
3144             * @private
3145             */
3146            _tryPreloadAttach: function() {
3147
3148                if (onAvailStack.length === 0) {
3149                    retryCount = 0;
3150                    if (this._interval) {
3151                        // clearInterval(this._interval);
3152                        this._interval.cancel();
3153                        this._interval = null;
3154                    }
3155                    return;
3156                }
3157
3158                if (this.locked) {
3159                    return;
3160                }
3161
3162                if (this.isIE) {
3163                    // Hold off if DOMReady has not fired and check current
3164                    // readyState to protect against the IE operation aborted
3165                    // issue.
3166                    if (!this.DOMReady) {
3167                        this.startInterval();
3168                        return;
3169                    }
3170                }
3171
3172                this.locked = true;
3173
3174                // this.logger.debug("tryPreloadAttach");
3175
3176                // keep trying until after the page is loaded.  We need to
3177                // check the page load state prior to trying to bind the
3178                // elements so that we can be certain all elements have been
3179                // tested appropriately
3180                var tryAgain = !loadComplete;
3181                if (!tryAgain) {
3182                    tryAgain = (retryCount > 0 && onAvailStack.length > 0);
3183                }
3184
3185                // onAvailable
3186                var notAvail = [];
3187
3188                var executeItem = function (el, item) {
3189                    var context = el;
3190                    if (item.overrideContext) {
3191                        if (item.overrideContext === true) {
3192                            context = item.obj;
3193                        } else {
3194                            context = item.overrideContext;
3195                        }
3196                    }
3197                    item.fn.call(context, item.obj);
3198                };
3199
3200                var i, len, item, el, ready=[];
3201
3202                // onAvailable onContentReady
3203                for (i=0, len=onAvailStack.length; i<len; i=i+1) {
3204                    item = onAvailStack[i];
3205                    if (item) {
3206                        el = this.getEl(item.id);
3207                        if (el) {
3208                            if (item.checkReady) {
3209                                if (loadComplete || el.nextSibling || !tryAgain) {
3210                                    ready.push(item);
3211                                    onAvailStack[i] = null;
3212                                }
3213                            } else {
3214                                executeItem(el, item);
3215                                onAvailStack[i] = null;
3216                            }
3217                        } else {
3218                            notAvail.push(item);
3219                        }
3220                    }
3221                }
3222
3223                // make sure onContentReady fires after onAvailable
3224                for (i=0, len=ready.length; i<len; i=i+1) {
3225                    item = ready[i];
3226                    executeItem(this.getEl(item.id), item);
3227                }
3228
3229
3230                retryCount--;
3231
3232                if (tryAgain) {
3233                    for (i=onAvailStack.length-1; i>-1; i--) {
3234                        item = onAvailStack[i];
3235                        if (!item || !item.id) {
3236                            onAvailStack.splice(i, 1);
3237                        }
3238                    }
3239
3240                    this.startInterval();
3241                } else {
3242                    if (this._interval) {
3243                        // clearInterval(this._interval);
3244                        this._interval.cancel();
3245                        this._interval = null;
3246                    }
3247                }
3248
3249                this.locked = false;
3250
3251            },
3252
3253            /**
3254             * Removes all listeners attached to the given element via addListener.
3255             * Optionally, the node's children can also be purged.
3256             * Optionally, you can specify a specific type of event to remove.
3257             * @method purgeElement
3258             * @param {HTMLElement} el the element to purge
3259             * @param {boolean} recurse recursively purge this element's children
3260             * as well.  Use with caution.
3261             * @param {string} sType optional type of listener to purge. If
3262             * left out, all listeners will be removed
3263             * @static
3264             */
3265            purgeElement: function(el, recurse, sType) {
3266                var oEl = (YAHOO.lang.isString(el)) ? this.getEl(el) : el;
3267                var elListeners = this.getListeners(oEl, sType), i, len;
3268                if (elListeners) {
3269                    for (i=elListeners.length-1; i>-1; i--) {
3270                        var l = elListeners[i];
3271                        this.removeListener(oEl, l.type, l.fn);
3272                    }
3273                }
3274
3275                var n = 100;
3276                if (recurse && oEl && oEl.childNodes) {
3277                    if (YAHOO.env.ua.ie > 8 || YAHOO.env.ua.ie == 0) {
3278                        for (i=0,len=el.childNodes.length; i<len ; ++i) {
3279                            this.purgeElement(el.childNodes[i], recurse, sType);
3280                        }
3281                    } else {
3282                        var elements = new Array();
3283                        this.gatherElements(el, elements);
3284                        this.purgeElementBatch(elements, n, sType);
3285                    }
3286                }
3287            },
3288 
3289            purgeElementBatch: function(elements, n, sType) {
3290                var cycle = 0;
3291                while (elements.length > 0) {
3292                    var elListeners = this.getListeners(elements[0], sType);
3293                    if (elListeners) {
3294                        for (var i=0,len=elListeners.length; i<len ; i++) {
3295                            var l = elListeners[i];
3296                            this.removeListener(elements[0], l.type, l.fn);
3297                        }
3298                    }
3299                    elements.shift();
3300                    cycle++;
3301                    if (cycle == n) {
3302                        var t = setTimeout(function(){YAHOO.util.Event.purgeElementBatch(elements, n, sType);}, 1);
3303                        break;
3304                    }
3305                }
3306            },
3307 
3308            gatherElements: function(el, elements) {
3309                var nodes = new Array();
3310                nodes.push(el);
3311                elements.push(el);
3312                var nextNodes = new Array();
3313                while (nodes.length > 0) {
3314                    nextNodes.length = 0;
3315                    for (var i=0, len=nodes.length; i<len; i++) {
3316                        var curNode = nodes[i];
3317                        if (curNode.childNodes) {
3318                            for (var j=0, curLen=curNode.childNodes.length; j<curLen ; j++) {
3319                                nextNodes.push(curNode.childNodes[j]);
3320                                elements.push(curNode.childNodes[j]);
3321                            }
3322                        }
3323                    }
3324                    nodes.length = 0;
3325                    nodes = nextNodes.slice(0);
3326                }
3327            },
3328
3329            /**
3330             * Returns all listeners attached to the given element via addListener.
3331             * Optionally, you can specify a specific type of event to return.
3332             * @method getListeners
3333             * @param el {HTMLElement|string} the element or element id to inspect
3334             * @param sType {string} optional type of listener to return. If
3335             * left out, all listeners will be returned
3336             * @return {Object} the listener. Contains the following fields:
3337             * &nbsp;&nbsp;type:   (string)   the type of event
3338             * &nbsp;&nbsp;fn:     (function) the callback supplied to addListener
3339             * &nbsp;&nbsp;obj:    (object)   the custom object supplied to addListener
3340             * &nbsp;&nbsp;adjust: (boolean|object)  whether or not to adjust the default context
3341             * &nbsp;&nbsp;scope: (boolean)  the derived context based on the adjust parameter
3342             * &nbsp;&nbsp;index:  (int)      its position in the Event util listener cache
3343             * @static
3344             */
3345            getListeners: function(el, sType) {
3346                var results=[], searchLists;
3347                if (!sType) {
3348                    searchLists = [listeners, unloadListeners];
3349                } else if (sType === "unload") {
3350                    searchLists = [unloadListeners];
3351                } else {
3352                    sType = this._getType(sType);
3353                    searchLists = [listeners];
3354                }
3355
3356                var oEl = (YAHOO.lang.isString(el)) ? this.getEl(el) : el;
3357
3358                for (var j=0;j<searchLists.length; j=j+1) {
3359                    var searchList = searchLists[j];
3360                    if (searchList) {
3361                        for (var i=0,len=searchList.length; i<len ; ++i) {
3362                            var l = searchList[i];
3363                            if ( l  && l[this.EL] === oEl &&
3364                                    (!sType || sType === l[this.TYPE]) ) {
3365                                results.push({
3366                                    type:   l[this.TYPE],
3367                                    fn:     l[this.FN],
3368                                    obj:    l[this.OBJ],
3369                                    adjust: l[this.OVERRIDE],
3370                                    scope:  l[this.ADJ_SCOPE],
3371                                    index:  i
3372                                });
3373                            }
3374                        }
3375                    }
3376                }
3377
3378                return (results.length) ? results : null;
3379            },
3380
3381            /**
3382             * Removes all listeners registered by pe.event.  Called
3383             * automatically during the unload event.
3384             * @method _unload
3385             * @static
3386             * @private
3387             */
3388            _unload: function(e) {
3389
3390                var EU = YAHOO.util.Event, i, j, l, len, index,
3391                         ul = unloadListeners.slice(), context;
3392
3393                // execute and clear stored unload listeners
3394                for (i=0, len=unloadListeners.length; i<len; ++i) {
3395                    l = ul[i];
3396                    if (l) {
3397                        try {
3398                            context = window;
3399                            if (l[EU.ADJ_SCOPE]) {
3400                                if (l[EU.ADJ_SCOPE] === true) {
3401                                    context = l[EU.UNLOAD_OBJ];
3402                                } else {
3403                                    context = l[EU.ADJ_SCOPE];
3404                                }
3405                            }
3406                            l[EU.FN].call(context, EU.getEvent(e, l[EU.EL]
3406), l[EU.UNLOAD_OBJ] );
3407                        } catch(e1) {}
3408                        ul[i] = null;
3409                    }
3410                }
3411
3412                l = null;
3413                context = null;
3414                unloadListeners = null;
3415
3416                // Remove listeners to handle IE memory leaks
3417                // 2.5.0 listeners are removed for all browsers again.  FireFox preserves
3418                // at least some listeners between page refreshes, potentially causing
3419                // errors during page load (mouseover listeners firing before they
3420                // should if the user moves the mouse at the correct moment).
3421                if (listeners) {
3422                    for (j=listeners.length-1; j>-1; j--) {
3423                        l = listeners[j];
3424                        if (l) {
3425                            try {
3426                                EU.removeListener(l[EU.EL], l[EU.TYPE], l[EU.FN], j);
3427                            } catch(e2) {}
3428                        }
3429                    }
3430                    l=null;
3431                }
3432
3433                try {
3434                    EU._simpleRemove(window, "unload", EU._unload);
3435                    EU._simpleRemove(window, "load", EU._load);
3436                } catch(e3) {}
3437
3438            },
3439
3440            /**
3441             * Returns scrollLeft
3442             * @method _getScrollLeft
3443             * @static
3444             * @private
3445             */
3446            _getScrollLeft: function() {
3447                return this._getScroll()[1];
3448            },
3449
3450            /**
3451             * Returns scrollTop
3452             * @method _getScrollTop
3453             * @static
3454             * @private
3455             */
3456            _getScrollTop: function() {
3457                return this._getScroll()[0];
3458            },
3459
3460            /**
3461             * Returns the scrollTop and scrollLeft.  Used to calculate the
3462             * pageX and pageY in Internet Explorer
3463             * @method _getScroll
3464             * @static
3465             * @private
3466             */
3467            _getScroll: function() {
3468                var dd = document.documentElement, db = document.body;
3469                if (dd && (dd.scrollTop || dd.scrollLeft)) {
3470                    return [dd.scrollTop, dd.scrollLeft];
3471                } else if (db) {
3472                    return [db.scrollTop, db.scrollLeft];
3473                } else {
3474                    return [0, 0];
3475                }
3476            },
3477
3478            /**
3479             * Used by old versions of CustomEvent, restored for backwards
3480             * compatibility
3481             * @method regCE
3482             * @private
3483             * @static
3484             * @deprecated still here for backwards compatibility
3485             */
3486            regCE: function() {},
3487
3488            /**
3489             * Adds a DOM event directly without the caching, cleanup, context adj, etc
3490             *
3491             * @method _simpleAdd
3492             * @param {HTMLElement} el      the element to bind the handler to
3493             * @param {string}      sType   the type of event handler
3494             * @param {function}    fn      the callback to invoke
3495             * @param {boolen}      capture capture or bubble phase
3496             * @static
3497             * @private
3498             */
3499            _simpleAdd: function () {
3500                if (window.addEventListener) {
3501                    return function(el, sType, fn, capture) {
3502                        el.addEventListener(sType, fn, (capture));
3503                    };
3504                } else if (window.attachEvent) {
3505                    return function(el, sType, fn, capture) {
3506                        el.attachEvent("on" + sType, fn);
3507                    };
3508                } else {
3509                    return function(){};
3510                }
3511            }(),
3512
3513            /**
3514             * Basic remove listener
3515             *
3516             * @method _simpleRemove
3517             * @param {HTMLElement} el      the element to bind the handler to
3518             * @param {string}      sType   the type of event handler
3519             * @param {function}    fn      the callback to invoke
3520             * @param {boolen}      capture capture or bubble phase
3521             * @static
3522             * @private
3523             */
3524            _simpleRemove: function() {
3525                if (window.removeEventListener) {
3526                    return function (el, sType, fn, capture) {
3527                        el.removeEventListener(sType, fn, (capture));
3528                    };
3529                } else if (window.detachEvent) {
3530                    return function (el, sType, fn) {
3531                        el.detachEvent("on" + sType, fn);
3532                    };
3533                } else {
3534                    return function(){};
3535                }
3536            }()
3537        };
3538
3539    }();
3540
3541    (function() {
3542        var EU = YAHOO.util.Event;
3543
3544        /**
3545         * Appends an event handler.  This is an alias for <code>addListener</code>
3546         *
3547         * @method on
3548         *
3549         * @param {String|HTMLElement|Array|NodeList} el An id, an element
3550         *  reference, or a collection of ids and/or elements to assign the
3551         *  listener to.
3552         * @param {String}   sType     The type of event to append
3553         * @param {Function} fn        The method the event invokes
3554         * @param {Object}   obj    An arbitrary object that will be
3555         *                             passed as a parameter to the handler
3556         * @param {Boolean|object}  overrideContext  If true, the obj passed in becomes
3557         *                             the execution context of the listener. If an
3558         *                             object, this object becomes the execution
3559         *                             context.
3560         * @return {Boolean} True if the action was successful or defered,
3561         *                        false if one or more of the elements
3562         *                        could not have the listener attached,
3563         *                        or if the operation throws an exception.
3564         * @static
3565         */
3566        EU.on = EU.addListener;
3567
3568        /**
3569         * YAHOO.util.Event.onFocus is an alias for addFocusListener
3570         * @method onFocus
3571         * @see addFocusListener
3572         * @static
3573         * @deprecated use YAHOO.util.Event.on and specify "focusin" as the event type.
3574         */
3575        EU.onFocus = EU.addFocusListener;
3576
3577        /**
3578         * YAHOO.util.Event.onBlur is an alias for addBlurListener
3579         * @method onBlur
3580         * @see addBlurListener
3581         * @static
3582         * @deprecated use YAHOO.util.Event.on and specify "focusout" as the event type.
3583         */
3584        EU.onBlur = EU.addBlurListener;
3585
3586/*! DOMReady: based on work by: Dean Edwards/John Resig/Matthias Miller/Diego Perini */
3587
3588        // Internet Explorer: use the readyState of a defered script.
3589        // This isolates what appears to be a safe moment to manipulate
3590        // the DOM prior to when the document's readyState suggests
3591        // it is safe to do so.
3592        if (EU.isIE && EU.isIE < 11) {
3593            if (self !== self.top) {
3594                document.onreadystatechange = function() {
3595                    if (document.readyState == 'complete') {
3596                        document.onreadystatechange = null;
3597                        EU._ready();
3598                    }
3599                };
3600            } else {
3601
3602                // Process onAvailable/onContentReady items when the
3603                // DOM is ready.
3604                YAHOO.util.Event.onDOMReady(
3605                        YAHOO.util.Event._tryPreloadAttach,
3606                        YAHOO.util.Event, true);
3607
3608                var n = document.createElement('p');
3609
3610                EU._dri = setInterval(function() {
3611                    try {
3612                        // throws an error if doc is not ready
3613                        n.doScroll('left');
3614                        clearInterval(EU._dri);
3615                        EU._dri = null;
3616                        EU._ready();
3617                        n = null;
3618                    } catch (ex) {
3619                    }
3620                }, EU.POLL_INTERVAL);
3621            }
3622
3623        // The document's readyState in Safari currently will
3624        // change to loaded/complete before images are loaded.
3625        } else if (EU.webkit && EU.webkit < 525) {
3626
3627            EU._dri = setInterval(function() {
3628                var rs=document.readyState;
3629                if ("loaded" == rs || "complete" == rs) {
3630                    clearInterval(EU._dri);
3631                    EU._dri = null;
3632                    EU._ready();
3633                }
3634            }, EU.POLL_INTERVAL);
3635
3636        // FireFox and Opera: These browsers provide a event for this
3637        // moment.  The latest WebKit releases now support this event.
3638        } else {
3639
3640            EU._simpleAdd(document, "DOMContentLoaded", EU._ready);
3641
3642        }
3643        /////////////////////////////////////////////////////////////
3644
3645
3646        EU._simpleAdd(window, "load", EU._load);
3647        EU._simpleAdd(window, "unload", EU._unload);
3648        EU._tryPreloadAttach();
3649    })();
3650
3651}
3652/**
3653 * EventProvider is designed to be used with YAHOO.augment to wrap
3654 * CustomEvents in an interface that allows events to be subscribed to
3655 * and fired by name.  This makes it possible for implementing code to
3656 * subscribe to an event that either has not been created yet, or will
3657 * not be created at all.
3658 *
3659 * @Class EventProvider
3660 */
3661YAHOO.util.EventProvider = function() { };
3662
3663YAHOO.util.EventProvider.prototype = {
3664
3665    /**
3666     * Private storage of custom events
3667     * @property __yui_events
3668     * @type Object[]
3669     * @private
3670     */
3671    __yui_events: null,
3672
3673    /**
3674     * Private storage of custom event subscribers
3675     * @property __yui_subscribers
3676     * @type Object[]
3677     * @private
3678     */
3679    __yui_subscribers: null,
3680
3681    /**
3682     * Subscribe to a CustomEvent by event type
3683     *
3684     * @method subscribe
3685     * @param p_type     {string}   the type, or name of the event
3686     * @param p_fn       {function} the function to exectute when the event fires
3687     * @param p_obj      {Object}   An object to be passed along when the event
3688     *                              fires
3689     * @param overrideContext {boolean}  If true, the obj passed in becomes the
3690     *                              execution scope of the listener
3691     */
3692    subscribe: function(p_type, p_fn, p_obj, overrideContext) {
3693
3694        this.__yui_events = this.__yui_events || {};
3695        var ce = this.__yui_events[p_type];
3696
3697        if (ce) {
3698            ce.subscribe(p_fn, p_obj, overrideContext);
3699        } else {
3700            this.__yui_subscribers = this.__yui_subscribers || {};
3701            var subs = this.__yui_subscribers;
3702            if (!subs[p_type]) {
3703                subs[p_type] = [];
3704            }
3705            subs[p_type].push(
3706                { fn: p_fn, obj: p_obj, overrideContext: overrideContext } );
3707        }
3708    },
3709
3710    /**
3711     * Unsubscribes one or more listeners the from the specified event
3712     * @method unsubscribe
3713     * @param p_type {string}   The type, or name of the event.  If the type
3714     *                          is not specified, it will attempt to remove
3715     *                          the listener from all hosted events.
3716     * @param p_fn   {Function} The subscribed function to unsubscribe, if not
3717     *                          supplied, all subscribers will be removed.
3718     * @param p_obj  {Object}   The custom object passed to subscribe.  This is
3719     *                        optional, but if supplied will be used to
3720     *                        disambiguate multiple listeners that are the same
3721     *                        (e.g., you subscribe many object using a function
3722     *                        that lives on the prototype)
3723     * @return {boolean} true if the subscriber was found and detached.
3724     */
3725    unsubscribe: function(p_type, p_fn, p_obj) {
3726        this.__yui_events = this.__yui_events || {};
3727        var evts = this.__yui_events;
3728        if (p_type) {
3729            var ce = evts[p_type];
3730            if (ce) {
3731                return ce.unsubscribe(p_fn, p_obj);
3732            }
3733        } else {
3734            var ret = true;
3735            for (var i in evts) {
3736                if (YAHOO.lang.hasOwnProperty(evts, i)) {
3737                    ret = ret && evts[i].unsubscribe(p_fn, p_obj);
3738                }
3739            }
3740            return ret;
3741        }
3742
3743        return false;
3744    },
3745
3746    /**
3747     * Removes all listeners from the specified event.  If the event type
3748     * is not specified, all listeners from all hosted custom events will
3749     * be removed.
3750     * @method unsubscribeAll
3751     * @param p_type {string}   The type, or name of the event
3752     */
3753    unsubscribeAll: function(p_type) {
3754        return this.unsubscribe(p_type);
3755    },
3756
3757    /**
3758     * Creates a new custom event of the specified type.  If a custom event
3759     * by that name already exists, it will not be re-created.  In either
3760     * case the custom event is returned.
3761     *
3762     * @method createEvent
3763     *
3764     * @param p_type {string} the type, or name of the event
3765     * @param p_config {object} optional config params.  Valid properties are:
3766     *
3767     *  <ul>
3768     *    <li>
3769     *      scope: defines the default execution scope.  If not defined
3770     *      the default scope will be this instance.
3771     *    </li>
3772     *    <li>
3773     *      silent: if true, the custom event will not generate log messages.
3774     *      This is false by default.
3775     *    </li>
3776     *    <li>
3777     *      fireOnce: if true, the custom event will only notify subscribers
3778     *      once regardless of the number of times the event is fired.  In
3779     *      addition, new subscribers will be executed immediately if the
3780     *      event has already fired.
3781     *      This is false by default.
3782     *    </li>
3783     *    <li>
3784     *      onSubscribeCallback: specifies a callback to execute when the
3785     *      event has a new subscriber.  This will fire immediately for
3786     *      each queued subscriber if any exist prior to the creation of
3787     *      the event.
3788     *    </li>
3789     *  </ul>
3790     *
3791     *  @return {CustomEvent} the custom event
3792     *
3793     */
3794    createEvent: function(p_type, p_config) {
3795
3796        this.__yui_events = this.__yui_events || {};
3797        var opts = p_config || {},
3798            events = this.__yui_events, ce;
3799
3800        if (events[p_type]) {
3801YAHOO.log("EventProvider createEvent skipped: '"+p_type+"' already exists");
3802        } else {
3803
3804            ce = new YAHOO.util.CustomEvent(p_type, opts.scope || this, opts.silent,
3805                         YAHOO.util.CustomEvent.FLAT, opts.fireOnce);
3806
3807            events[p_type] = ce;
3808
3809            if (opts.onSubscribeCallback) {
3810                ce.subscribeEvent.subscribe(opts.onSubscribeCallback);
3811            }
3812
3813            this.__yui_subscribers = this.__yui_subscribers || {};
3814            var qs = this.__yui_subscribers[p_type];
3815
3816            if (qs) {
3817                for (var i=0; i<qs.length; ++i) {
3818                    ce.subscribe(qs[i].fn, qs[i].obj, qs[i].overrideContext);
3819                }
3820            }
3821        }
3822
3823        return events[p_type];
3824    },
3825
3826
3827   /**
3828     * Fire a custom event by name.  The callback functions will be executed
3829     * from the scope specified when the event was created, and with the
3830     * following parameters:
3831     *   <ul>
3832     *   <li>The first argument fire() was executed with</li>
3833     *   <li>The custom object (if any) that was passed into the subscribe()
3834     *       method</li>
3835     *   </ul>
3836     * @method fireEvent
3837     * @param p_type    {string}  the type, or name of the event
3838     * @param arguments {Object*} an arbitrary set of parameters to pass to
3839     *                            the handler.
3840     * @return {boolean} the return value from CustomEvent.fire
3841     *
3842     */
3843    fireEvent: function(p_type) {
3844
3845        this.__yui_events = this.__yui_events || {};
3846        var ce = this.__yui_events[p_type];
3847
3848        if (!ce) {
3849YAHOO.log(p_type + "event fired before it was created.");
3850            return null;
3851        }
3852
3853        var args = [];
3854        for (var i=1; i<arguments.length; ++i) {
3855            args.push(arguments[i]);
3856        }
3857        return ce.fire.apply(ce, args);
3858    },
3859
3860    /**
3861     * Returns true if the custom event of the provided type has been created
3862     * with createEvent.
3863     * @method hasEvent
3864     * @param type {string} the type, or name of the event
3865     */
3866    hasEvent: function(type) {
3867        if (this.__yui_events) {
3868            if (this.__yui_events[type]) {
3869                return true;
3870            }
3871        }
3872        return false;
3873    }
3874
3875};
3876
3877(function() {
3878
3879    var Event = YAHOO.util.Event, Lang = YAHOO.lang;
3880
3881/**
3882* KeyListener is a utility that provides an easy interface for listening for
3883* keydown/keyup events fired against DOM elements.
3884* @namespace YAHOO.util
3885* @class KeyListener
3886* @constructor
3887* @param {HTMLElement} attachTo The element or element ID to which the key
3888*                               event should be attached
3889* @param {String}      attachTo The element or element ID to which the key
3890*                               event should be attached
3891* @param {Object}      keyData  The object literal representing the key(s)
3892*                               to detect. Possible attributes are
3893*                               shift(boolean), alt(boolean), ctrl(boolean)
3894*                               and keys(either an int or an array of ints
3895*                               representing keycodes).
3896* @param {Function}    handler  The CustomEvent handler to fire when the
3897*                               key event is detected
3898* @param {Object}      handler  An object literal representing the handler.
3899* @param {String}      event    Optional. The event (keydown or keyup) to
3900*                               listen for. Defaults automatically to keydown.
3901*
3902* @knownissue the "keypress" event is completely broken in Safari 2.x and below.
3903*             the workaround is use "keydown" for key listening.  However, if
3904*             it is desired to prevent the default behavior of the keystroke,
3905*             that can only be done on the keypress event.  This makes key
3906*             handling quite ugly.
3907* @knownissue keydown is also broken in Safari 2.x and below for the ESC key.
3908*             There currently is no workaround other than choosing another
3909*             key to listen for.
3910*/
3911YAHOO.util.KeyListener = function(attachTo, keyData, handler, event) {
3912    if (!attachTo) {
3913        YAHOO.log("No attachTo element specified", "error");
3914    } else if (!keyData) {
3915        YAHOO.log("No keyData specified", "error");
3916    } else if (!handler) {
3917        YAHOO.log("No handler specified", "error");
3918    }
3919
3920    if (!event) {
3921        event = YAHOO.util.KeyListener.KEYDOWN;
3922    }
3923
3924    /**
3925    * The CustomEvent fired internally when a key is pressed
3926    * @event keyEvent
3927    * @private
3928    * @param {Object} keyData The object literal representing the key(s) to
3929    *                         detect. Possible attributes are shift(boolean),
3930    *                         alt(boolean), ctrl(boolean) and keys(either an
3931    *                         int or an array of ints representing keycodes).
3932    */
3933    var keyEvent = new YAHOO.util.CustomEvent("keyPressed");
3934
3935    /**
3936    * The CustomEvent fired when the KeyListener is enabled via the enable()
3937    * function
3938    * @event enabledEvent
3939    * @param {Object} keyData The object literal representing the key(s) to
3940    *                         detect. Possible attributes are shift(boolean),
3941    *                         alt(boolean), ctrl(boolean) and keys(either an
3942    *                         int or an array of ints representing keycodes).
3943    */
3944    this.enabledEvent = new YAHOO.util.CustomEvent("enabled");
3945
3946    /**
3947    * The CustomEvent fired when the KeyListener is disabled via the
3948    * disable() function
3949    * @event disabledEvent
3950    * @param {Object} keyData The object literal representing the key(s) to
3951    *                         detect. Possible attributes are shift(boolean),
3952    *                         alt(boolean), ctrl(boolean) and keys(either an
3953    *                         int or an array of ints representing keycodes).
3954    */
3955    this.disabledEvent = new YAHOO.util.CustomEvent("disabled");
3956
3957    if (Lang.isString(attachTo)) {
3958        attachTo = document.getElementById(attachTo); // No Dom util
3959    }
3960
3961    if (Lang.isFunction(handler)) {
3962        keyEvent.subscribe(handler);
3963    } else {
3964        keyEvent.subscribe(handler.fn, handler.scope, handler.correctScope);
3965    }
3966
3967    /**
3968    * Handles the key event when a key is pressed.
3969    * @method handleKeyPress
3970    * @param {DOMEvent} e   The keypress DOM event
3971    * @param {Object}   obj The DOM event scope object
3972    * @private
3973    */
3974    function handleKeyPress(e, obj) {
3975        if (! keyData.shift) {
3976            keyData.shift = false;
3977        }
3978        if (! keyData.alt) {
3979            keyData.alt = false;
3980        }
3981        if (! keyData.ctrl) {
3982            keyData.ctrl = false;
3983        }
3984
3985        // check held down modifying keys first
3986        if (e.shiftKey == keyData.shift &&
3987            e.altKey   == keyData.alt &&
3988            e.ctrlKey  == keyData.ctrl) { // if we pass this, all modifiers match
3989
3990            var dataItem, keys = keyData.keys, key;
3991
3992            if (YAHOO.lang.isArray(keys)) {
3993                for (var i=0;i<keys.length;i++) {
3994                    dataItem = keys[i];
3995                    key = Event.getCharCode(e);
3996
3997                    if (dataItem == key) {
3998                        keyEvent.fire(key, e);
3999                        break;
4000                    }
4001                }
4002            } else {
4003                key = Event.getCharCode(e);
4004                if (keys == key ) {
4005                    keyEvent.fire(key, e);
4006                }
4007            }
4008        }
4009    }
4010
4011    /**
4012    * Enables the KeyListener by attaching the DOM event listeners to the
4013    * target DOM element
4014    * @method enable
4015    */
4016    this.enable = function() {
4017        if (! this.enabled) {
4018            Event.on(attachTo, event, handleKeyPress);
4019            this.enabledEvent.fire(keyData);
4020        }
4021        /**
4022        * Boolean indicating the enabled/disabled state of the Tooltip
4023        * @property enabled
4024        * @type Boolean
4025        */
4026        this.enabled = true;
4027    };
4028
4029    /**
4030    * Disables the KeyListener by removing the DOM event listeners from the
4031    * target DOM element
4032    * @method disable
4033    */
4034    this.disable = function() {
4035        if (this.enabled) {
4036            Event.removeListener(attachTo, event, handleKeyPress);
4037            this.disabledEvent.fire(keyData);
4038        }
4039        this.enabled = false;
4040    };
4041
4042    /**
4043    * Returns a String representation of the object.
4044    * @method toString
4045    * @return {String}  The string representation of the KeyListener
4046    */
4047    this.toString = function() {
4048        return "KeyListener [" + keyData.keys + "] " + attachTo.tagName +
4049                (attachTo.id ? "[" + attachTo.id + "]" : "");
4050    };
4051
4052};
4053
4054var KeyListener = YAHOO.util.KeyListener;
4055
4056/**
4057 * Constant representing the DOM "keydown" event.
4058 * @property YAHOO.util.KeyListener.KEYDOWN
4059 * @static
4060 * @final
4061 * @type String
4062 */
4063KeyListener.KEYDOWN = "keydown";
4064
4065/**
4066 * Constant representing the DOM "keyup" event.
4067 * @property YAHOO.util.KeyListener.KEYUP
4068 * @static
4069 * @final
4070 * @type String
4071 */
4072KeyListener.KEYUP = "keyup";
4073
4074/**
4075 * keycode constants for a subset of the special keys
4076 * @property KEY
4077 * @static
4078 * @final
4079 */
4080KeyListener.KEY = {
4081    ALT          : 18,
4082    BACK_SPACE   : 8,
4083    CAPS_LOCK    : 20,
4084    CONTROL      : 17,
4085    DELETE       : 46,
4086    DOWN         : 40,
4087    END          : 35,
4088    ENTER        : 13,
4089    ESCAPE       : 27,
4090    HOME         : 36,
4091    LEFT         : 37,
4092    META         : 224,
4093    NUM_LOCK     : 144,
4094    PAGE_DOWN    : 34,
4095    PAGE_UP      : 33,
4096    PAUSE        : 19,
4097    PRINTSCREEN  : 44,
4098    RIGHT        : 39,
4099    SCROLL_LOCK  : 145,
4100    SHIFT        : 16,
4101    SPACE        : 32,
4102    TAB          : 9,
4103    UP           : 38
4104};
4105
4106})();
4107YAHOO.register("event", YAHOO.util.Event, {version: "2.9.0", build: "2800"});
4108/*
4109Copyright (c) 2011, Yahoo! Inc. All rights reserved.
4110Code licensed under the BSD License:
4111http://developer.yahoo.com/yui/license.html
4112version: 2.9.0
4113*/
4114/**
4115 * The dom module provides helper methods for manipulating Dom elements.
4116 * @module dom
4117 *
4118 */
4119
4120(function() {
4121    // for use with generateId (global to save state if Dom is overwritten)
4122    YAHOO.env._id_counter = YAHOO.env._id_counter || 0;
4123
4124    // internal shorthand
4125    var Y = YAHOO.util,
4126        lang = YAHOO.lang,
4127        UA = YAHOO.env.ua,
4128        trim = YAHOO.lang.trim,
4129        propertyCache = {}, // for faster hyphen converts
4130        reCache = {}, // cache className regexes
4131        RE_TABLE = /^t(?:able|d|h)$/i, // for _calcBorders
4132        RE_COLOR = /color$/i,
4133
4134        // DOM aliases 
4135        document = window.document,     
4136        documentElement = document.documentElement,
4137
4138        // string constants
4139        OWNER_DOCUMENT = 'ownerDocument',
4140        DEFAULT_VIEW = 'defaultView',
4141        DOCUMENT_ELEMENT = 'documentElement',
4142        COMPAT_MODE = 'compatMode',
4143        OFFSET_LEFT = 'offsetLeft',
4144        OFFSET_TOP = 'offsetTop',
4145        OFFSET_PARENT = 'offsetParent',
4146        PARENT_NODE = 'parentNode',
4147        NODE_TYPE = 'nodeType',
4148        TAG_NAME = 'tagName',
4149        SCROLL_LEFT = 'scrollLeft',
4150        SCROLL_TOP = 'scrollTop',
4151        GET_BOUNDING_CLIENT_RECT = 'getBoundingClientRect',
4152        GET_COMPUTED_STYLE = 'getComputedStyle',
4153        CURRENT_STYLE = 'currentStyle',
4154        CSS1_COMPAT = 'CSS1Compat',
4155        _BACK_COMPAT = 'BackCompat',
4156        _CLASS = 'class', // underscore due to reserved word
4157        CLASS_NAME = 'className',
4158        EMPTY = '',
4159        SPACE = ' ',
4160        C_START = '(?:^|\\s)',
4161        C_END = '(?= |$)',
4162        G = 'g',
4163        POSITION = 'position',
4164        FIXED = 'fixed',
4165        RELATIVE = 'relative',
4166        LEFT = 'left',
4167        TOP = 'top',
4168        MEDIUM = 'medium',
4169        BORDER_LEFT_WIDTH = 'borderLeftWidth',
4170        BORDER_TOP_WIDTH = 'borderTopWidth',
4171    
4172    // brower detection
4173        isOpera = UA.opera,
4174        isSafari = UA.webkit, 
4175        isGecko = UA.gecko, 
4176        isIE = UA.ie; 
4177    
4178    /**
4179     * Provides helper methods for DOM elements.
4180     * @namespace YAHOO.util
4181     * @class Dom
4182     * @requires yahoo, event
4183     */
4184    Y.Dom = {
4185        CUSTOM_ATTRIBUTES: (!documentElement.hasAttribute) ? { // IE < 8
4186            'for': 'htmlFor',
4187            'class': CLASS_NAME
4188        } : { // w3c
4189            'htmlFor': 'for',
4190            'className': _CLASS
4191        },
4192
4193        DOT_ATTRIBUTES: {
4194            checked: true 
4195        },
4196
4197        /**
4198         * Returns an HTMLElement reference.
4199         * @method get
4200         * @param {String | HTMLElement |Array} el Accepts a string to use as an ID for getting a DOM reference, an actual DOM reference, or an Array of IDs and/
4200or HTMLElements.
4201         * @return {HTMLElement | Array} A DOM reference to an HTML element or an array of HTMLElements.
4202         */
4203        get: function(el) {
4204            var id, nodes, c, i, len, attr, ret = null;
4205
4206            if (el) {
4207                if (typeof el == 'string' || typeof el == 'number') { // id
4208                    id = el + '';
4209                    el = document.getElementById(el);
4210                    attr = (el) ? el.attributes : null;
4211                    if (el && attr && attr.id && attr.id.value === id) { // IE: avoid false match on "name" attribute
4212                        return el;
4213                    } else if (el && document.all) { // filter by name
4214                        el = null;
4215                        nodes = document.all[id];
4216                        if (nodes && nodes.length) {
4217                            for (i = 0, len = nodes.length; i < len; ++i) {
4218                                if (nodes[i].id === id) {
4219                                    return nodes[i];
4220                                }
4221                            }
4222                        }
4223                    }
4224                } else if (Y.Element && el instanceof Y.Element) {
4225                    el = el.get('element');
4226                } else if (!el.nodeType && 'length' in el) { // array-like 
4227                    c = [];
4228                    for (i = 0, len = el.length; i < len; ++i) {
4229                        c[c.length] = Y.Dom.get(el[i]);
4230                    }
4231                    
4232                    el = c;
4233                }
4234
4235                ret = el;
4236            }
4237
4238            return ret;
4239        },
4240    
4241        getComputedStyle: function(el, property) {
4242            if (window[GET_COMPUTED_STYLE]) {
4243                return el[OWNER_DOCUMENT][DEFAULT_VIEW][GET_COMPUTED_STYLE](el, null)[property];
4244            } else if (el[CURRENT_STYLE]) {
4245                return Y.Dom.IE_ComputedStyle.get(el, property);
4246            }
4247        },
4248
4249        /**
4250         * Normalizes currentStyle and ComputedStyle.
4251         * @method getStyle
4252         * @param {String | HTMLElement |Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements.
4253         * @param {String} property The style property whose value is returned.
4254         * @return {String | Array} The current value of the style property for the element(s).
4255         */
4256        getStyle: function(el, property) {
4257            return Y.Dom.batch(el, Y.Dom._getStyle, property);
4258        },
4259
4260        // branching at load instead of runtime
4261        _getStyle: function() {
4262            if (window[GET_COMPUTED_STYLE]) { // W3C DOM method
4263                return function(el, property) {
4264                    property = (property === 'float') ? property = 'cssFloat' :
4265                            Y.Dom._toCamel(property);
4266
4267                    var value = el.style[property],
4268                        computed;
4269                    
4270                    if (!value) {
4271                        computed = el[OWNER_DOCUMENT][DEFAULT_VIEW][GET_COMPUTED_STYLE](el, null);
4272                        if (computed) { // test computed before touching for safari
4273                            value = computed[property];
4274                        }
4275                    }
4276                    
4277                    return value;
4278                };
4279            } else if (documentElement[CURRENT_STYLE]) {
4280                return function(el, property) {                         
4281                    var value;
4282
4283                    switch(property) {
4284                        case 'opacity' :// IE opacity uses filter
4285                            value = 100;
4286                            try { // will error if no DXImageTransform
4287                                value = el.filters['DXImageTransform.Microsoft.Alpha'].opacity;
4288
4289                            } catch(e) {
4290                                try { // make sure its in the document
4291                                    value = el.filters('alpha').opacity;
4292                                } catch(err) {
4293                                    YAHOO.log('getStyle: IE filter failed',
4294                                            'error', 'Dom');
4295                                }
4296                            }
4297                            return value / 100;
4298                        case 'float': // fix reserved word
4299                            property = 'styleFloat'; // fall through
4300                        default: 
4301                            property = Y.Dom._toCamel(property);
4302                            value = el[CURRENT_STYLE] ? el[CURRENT_STYLE][property] : null;
4303                            return ( el.style[property] || value );
4304                    }
4305                };
4306            }
4307        }(),
4308    
4309        /**
4310         * Wrapper for setting style properties of HTMLElements.  Normalizes "opacity" across modern browsers.
4311         * @method setStyle
4312         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/
4312or HTMLElements.
4313         * @param {String} property The style property to be set.
4314         * @param {String} val The value to apply to the given property.
4315         */
4316        setStyle: function(el, property, val) {
4317            Y.Dom.batch(el, Y.Dom._setStyle, { prop: property, val: val });
4318        },
4319
4320        _setStyle: function() {
4321            if (!window.getComputedStyle && document.documentElement.currentStyle) {
4322                return function(el, args) {
4323                    var property = Y.Dom._toCamel(args.prop),
4324                        val = args.val;
4325
4326                    if (el) {
4327                        switch (property) {
4328                            case 'opacity':
4329                                // remove filter if unsetting or full opacity
4330                                if (val === '' || val === null || val === 1) {
4331                                    el.style.removeAttribute('filter');
4332                                } else if ( lang.isString(el.style.filter) ) { // in case not appended
4333                                    el.style.filter = 'alpha(opacity=' + val * 100 + ')';
4334                                    
4335                                    if (!el[CURRENT_STYLE] || !el[CURRENT_STYLE].hasLayout) {
4336                                        el.style.zoom = 1; // when no layout or cant tell
4337                                    }
4338                                }
4339                                break;
4340                            case 'float':
4341                                property = 'styleFloat';
4342                            default:
4343                            el.style[property] = val;
4344                        }
4345                    } else {
4346                        YAHOO.log('element ' + el + ' is undefined', 'error', 'Dom');
4347                    }
4348                };
4349            } else {
4350                return function(el, args) {
4351                    var property = Y.Dom._toCamel(args.prop),
4352                        val = args.val;
4353                    if (el) {
4354                        if (property == 'float') {
4355                            property = 'cssFloat';
4356                        }
4357                        el.style[property] = val;
4358                    } else {
4359                        YAHOO.log('element ' + el + ' is undefined', 'error', 'Dom');
4360                    }
4361                };
4362            }
4363
4364        }(),
4365        
4366        /**
4367         * Gets the current position of an element based on page coordinates. 
4368         * Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
4369         * @method getXY
4370         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM
4371         * reference, or an Array of IDs and/or HTMLElements
4372         * @return {Array} The XY position of the element(s)
4373         */
4374        getXY: function(el) {
4375            return Y.Dom.batch(el, Y.Dom._getXY);
4376        },
4377
4378        _canPosition: function(el) {
4379            return ( Y.Dom._getStyle(el, 'display') !== 'none' && Y.Dom._inDoc(el) );
4380        },
4381
4382        _getXY: function(node) {
4383            var scrollLeft, scrollTop, box, doc,
4384                clientTop, clientLeft,
4385                round = Math.round, // TODO: round?
4386                xy = false;
4387
4388            if (Y.Dom._canPosition(node)) {
4389                box = node[GET_BOUNDING_CLIENT_RECT]();
4390                doc = node[OWNER_DOCUMENT];
4391                scrollLeft = Y.Dom.getDocumentScrollLeft(doc);
4392                scrollTop = Y.Dom.getDocumentScrollTop(doc);
4393                xy = [box[LEFT], box[TOP]];
4394
4395                // remove IE default documentElement offset (border)
4396                if (clientTop || clientLeft) {
4397                    xy[0] -= clientLeft;
4398                    xy[1] -= clientTop;
4399                }
4400
4401                if ((scrollTop || scrollLeft)) {
4402                    xy[0] += scrollLeft;
4403                    xy[1] += scrollTop;
4404                }
4405
4406                // gecko may return sub-pixel (non-int) values
4407                xy[0] = round(xy[0]);
4408                xy[1] = round(xy[1]);
4409            } else {
4410                YAHOO.log('getXY failed: element not positionable (either not in a document or not displayed)', 'error', 'Dom');
4411            }
4412
4413            return xy;
4414        },
4415        
4416        /**
4417         * Gets the current X position of an element based on page coordinates.  The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
4418         * @method getX
4419         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/
4419or HTMLElements
4420         * @return {Number | Array} The X position of the element(s)
4421         */
4422        getX: function(el) {
4423            var f = function(el) {
4424                return Y.Dom.getXY(el)[0];
4425            };
4426            
4427            return Y.Dom.batch(el, f, Y.Dom, true);
4428        },
4429        
4430        /**
4431         * Gets the current Y position of an element based on page coordinates.  Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
4432         * @method getY
4433         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements
4434         * @return {Number | Array} The Y position of the element(s)
4435         */
4436        getY: function(el) {
4437            var f = function(el) {
4438                return Y.Dom.getXY(el)[1];
4439            };
4440            
4441            return Y.Dom.batch(el, f, Y.Dom, true);
4442        },
4443        
4444        /**
4445         * Set the position of an html element in page coordinates, regardless of how the element is positioned.
4446         * The element(s) must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
4447         * @method setXY
4448         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements
4449         * @param {Array} pos Contains X & Y values for new position (coordinates are page-based)
4450         * @param {Boolean} noRetry By default we try and set the position a second time if the first fails
4451         */
4452        setXY: function(el, pos, noRetry) {
4453            Y.Dom.batch(el, Y.Dom._setXY, { pos: pos, noRetry: noRetry });
4454        },
4455
4456        _setXY: function(node, args) {
4457            var pos = Y.Dom._getStyle(node, POSITION),
4458                setStyle = Y.Dom.setStyle,
4459                xy = args.pos,
4460                noRetry = args.noRetry,
4461
4462                delta = [ // assuming pixels; if not we will have to retry
4463                    parseInt( Y.Dom.getComputedStyle(node, LEFT), 10 ),
4464                    parseInt( Y.Dom.getComputedStyle(node, TOP), 10 )
4465                ],
4466
4467                currentXY,
4468                newXY;
4469        
4470            currentXY = Y.Dom._getXY(node);
4471
4472            if (!xy || currentXY === false) { // has to be part of doc to have xy
4473                YAHOO.log('xy failed: node not available', 'error', 'Node');
4474                return false; 
4475            }
4476            
4477            if (pos == 'static') { // default to relative
4478                pos = RELATIVE;
4479                setStyle(node, POSITION, pos);
4480            }
4481
4482            if ( isNaN(delta[0]) ) {// in case of 'auto'
4483                delta[0] = (pos == RELATIVE) ? 0 : node[OFFSET_LEFT];
4484            } 
4485            if ( isNaN(delta[1]) ) { // in case of 'auto'
4486                delta[1] = (pos == RELATIVE) ? 0 : node[OFFSET_TOP];
4487            } 
4488
4489            if (xy[0] !== null) { // from setX
4490                setStyle(node, LEFT, xy[0] - currentXY[0] + delta[0] + 'px');
4491            }
4492
4493            if (xy[1] !== null) { // from setY
4494                setStyle(node, TOP, xy[1] - currentXY[1] + delta[1] + 'px');
4495            }
4496          
4497            if (!noRetry) {
4498                newXY = Y.Dom._getXY(node);
4499
4500                // if retry is true, try one more time if we miss 
4501               if ( (xy[0] !== null && newXY[0] != xy[0]) || 
4502                    (xy[1] !== null && newXY[1] != xy[1]) ) {
4503                   Y.Dom._setXY(node, { pos: xy, noRetry: true });
4504               }
4505            }        
4506
4507            YAHOO.log('setXY setting position to ' + xy, 'info', 'Node');
4508        },
4509        
4510        /**
4511         * Set the X position of an html element in page coordinates, regardless of how the element is positioned.
4512         * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
4513         * @method setX
4514         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/
4514or HTMLElements.
4515         * @param {Int} x The value to use as the X coordinate for the element(s).
4516         */
4517        setX: function(el, x) {
4518            Y.Dom.setXY(el, [x, null]);
4519        },
4520        
4521        /**
4522         * Set the Y position of an html element in page coordinates, regardless of how the element is positioned.
4523         * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
4524         * @method setY
4525         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements.
4526         * @param {Int} x To use as the Y coordinate for the element(s).
4527         */
4528        setY: function(el, y) {
4529            Y.Dom.setXY(el, [null, y]);
4530        },
4531        
4532        /**
4533         * Returns the region position of the given element.
4534         * The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
4535         * @method getRegion
4536         * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements.
4537         * @return {Region | Array} A Region or array of Region instances containing "top, left, bottom, right" member data.
4538         */
4539        getRegion: function(el) {
4540            var f = function(el) {
4541                var region = false;
4542                if ( Y.Dom._canPosition(el) ) {
4543                    region = Y.Region.getRegion(el);
4544                    YAHOO.log('getRegion returning ' + region, 'info', 'Dom');
4545                } else {
4546                    YAHOO.log('getRegion failed: element not positionable (either not in a document or not displayed)', 'error', 'Dom');
4547                }
4548
4549                return region;
4550            };
4551            
4552            return Y.Dom.batch(el, f, Y.Dom, true);
4553        },
4554        
4555        /**
4556         * Returns the width of the client (viewport).
4557         * @method getClientWidth
4558         * @deprecated Now using getViewportWidth.  This interface left intact for back compat.
4559         * @return {Int} The width of the viewable area of the page.
4560         */
4561        getClientWidth: function() {
4562            return Y.Dom.getViewportWidth();
4563        },
4564        
4565        /**
4566         * Returns the height of the client (viewport).
4567         * @method getClientHeight
4568         * @deprecated Now using getViewportHeight.  This interface left intact for back compat.
4569         * @return {Int} The height of the viewable area of the page.
4570         */
4571        getClientHeight: function() {
4572            return Y.Dom.getViewportHeight();
4573        },
4574
4575        /**
4576         * Returns an array of HTMLElements with the given class.
4577         * For optimized performance, include a tag and/or root node when possible.
4578         * Note: This method operates against a live collection, so modifying the 
4579         * collection in the callback (removing/appending nodes, etc.) will have
4580         * side effects.  Instead you should iterate the returned nodes array,
4581         * as you would with the native "getElementsByTagName" method. 
4582         * @method getElementsByClassName
4583         * @param {String} className The class name to match against
4584         * @param {String} tag (optional) The tag name of the elements being collected
4585         * @param {String | HTMLElement} root (optional) The HTMLElement or an ID to use as the starting point.
4586         * This element is not included in the className scan.
4587         * @param {Function} apply (optional) A function to apply to each element when found 
4588         * @param {Any} o (optional) An optional arg that is passed to the supplied method
4589         * @param {Boolean} overrides (optional) Whether or not to override the scope of "method" with "o"
4590         * @return {Array} An array of elements that have the given class name
4591         */
4592        getElementsByClassName: function(className, tag, root, apply, o, overrides) {
4593            tag = tag || '*';
4594            root = (root) ? Y.Dom.get(root) : null || document; 
4595            if (!root) {
4596                return [];
4597            }
4598
4599            var nodes = [],
4600                elements = root.getElementsByTagName(tag),
4601                hasClass = Y.Dom.hasClass;
4602
4603            for (var i = 0, len = elements.length; i < len; ++i) {
4604                if ( hasClass(elements[i], className) ) {
4605                    nodes[nodes.length] = elements[i];
4606                }
4607            }
4608            
4609            if (apply) {
4610                Y.Dom.batch(nodes, apply, o, overrides);
4611            }
4612
4613            return nodes;
4614        },
4615
4616        /**
4617         * Determines whether an HTMLElement has the given className.
4618         * @method hasClass
4619         * @param {String | HTMLElement | Array} el The element or collection to test
4620         * @param {String | RegExp} className the class name to search for, or a regular
4621         * expression to match against
4622         * @return {Boolean | Array} A boolean value or array of boolean values
4623         */
4624        hasClass: function(el, className) {
4625            return Y.Dom.batch(el, Y.Dom._hasClass, className);
4626        },
4627
4628        _hasClass: function(el, className) {
4629            var ret = false,
4630                current;
4631            
4632            if (el && className) {
4633                current = Y.Dom._getAttribute(el, CLASS_NAME) || EMPTY;
4634                if (current) { // convert line breaks, tabs and other delims to spaces
4635                    current = current.replace(/\s+/g, SPACE);
4636                }
4637
4638                if (className.exec) {
4639                    ret = className.test(current);
4640                } else {
4641                    ret = className && (SPACE + current + SPACE).
4642                        indexOf(SPACE + className + SPACE) > -1;
4643                }
4644            } else {
4645                YAHOO.log('hasClass called with invalid arguments', 'warn', 'Dom');
4646            }
4647
4648            return ret;
4649        },
4650    
4651        /**
4652         * Adds a class name to a given element or collection of elements.
4653         * @method addClass         
4654         * @param {String | HTMLElement | Array} el The element or collection to add the class to
4655         * @param {String} className the class name to add to the class attribute
4656         * @return {Boolean | Array} A pass/fail boolean or array of booleans
4657         */
4658        addClass: function(el, className) {
4659            return Y.Dom.batch(el, Y.Dom._addClass, className);
4660        },
4661
4662        _addClass: function(el, className) {
4663            var ret = false,
4664                current;
4665
4666            if (el && className) {
4667                current = Y.Dom._getAttribute(el, CLASS_NAME) || EMPTY;
4668                if ( !Y.Dom._hasClass(el, className) ) {
4669                    Y.Dom.setAttribute(el, CLASS_NAME, trim(current + SPACE + className));
4670                    ret = true;
4671                }
4672            } else {
4673                YAHOO.log('addClass called with invalid arguments', 'warn', 'Dom');
4674            }
4675
4676            return ret;
4677        },
4678    
4679        /**
4680         * Removes a class name from a given element or collection of elements.
4681         * @method removeClass         
4682         * @param {String | HTMLElement | Array} el The element or collection to remove the class from
4683         * @param {String} className the class name to remove from the class attribute
4684         * @return {Boolean | Array} A pass/fail boolean or array of booleans
4685         */
4686        removeClass: function(el, className) {
4687            return Y.Dom.batch(el, Y.Dom._removeClass, className);
4688        },
4689        
4690        _removeClass: function(el, className) {
4691            var ret = false,
4692                current,
4693                newClass,
4694                attr;
4695
4696            if (el && className) {
4697                current = Y.Dom._getAttribute(el, CLASS_NAME) || EMPTY;
4698                Y.Dom.setAttribute(el, CLASS_NAME, current.replace(Y.Dom._getClassRegex(className), EMPTY));
4699
4700                newClass = Y.Dom._getAttribute(el, CLASS_NAME);
4701                if (current !== newClass) { // else nothing changed
4702                    Y.Dom.setAttribute(el, CLASS_NAME, trim(newClass)); // trim after comparing to current class
4703                    ret = true;
4704
4705                    if (Y.Dom._getAttribute(el, CLASS_NAME) === '') { // remove class attribute if empty
4706                        attr = (el.hasAttribute && el.hasAttribute(_CLASS)) ? _CLASS : CLASS_NAME;
4707                        YAHOO.log('removeClass removing empty class attribute', 'info', 'Dom');
4708                        el.removeAttribute(attr);
4709                    }
4710                }
4711
4712            } else {
4713                YAHOO.log('removeClass called with invalid arguments', 'warn', 'Dom');
4714            }
4715
4716            return ret;
4717        },
4718        
4719        /**
4720         * Replace a class with another class for a given element or collection of elements.
4721         * If no oldClassName is present, the newClassName is simply added.
4722         * @method replaceClass  
4723         * @param {String | HTMLElement | Array} el The element or collection to remove the class from
4724         * @param {String} oldClassName the class name to be replaced
4725         * @param {String} newClassName the class name that will be replacing the old class name
4726         * @return {Boolean | Array} A pass/fail boolean or array of booleans
4727         */
4728        replaceClass: function(el, oldClassName, newClassName) {
4729            return Y.Dom.batch(el, Y.Dom._replaceClass, { from: oldClassName, to: newClassName });
4730        },
4731
4732        _replaceClass: function(el, classObj) {
4733            var className,
4734                from,
4735                to,
4736                ret = false,
4737                current;
4738
4739            if (el && classObj) {
4740                from = classObj.from;
4741                to = classObj.to;
4742
4743                if (!to) {
4744                    ret = false;
4745                }  else if (!from) { // just add if no "from"
4746                    ret = Y.Dom._addClass(el, classObj.to);
4747                } else if (from !== to) { // else nothing to replace
4748                    // May need to lead with DBLSPACE?
4749                    current = Y.Dom._getAttribute(el, CLASS_NAME) || EMPTY;
4750                    className = (SPACE + current.replace(Y.Dom._getClassRegex(from), SPACE + to).
4751                            replace(/\s+/g, SPACE)). // normalize white space
4752                            split(Y.Dom._getClassRegex(to));
4753
4754                    // insert to into what would have been the first occurrence slot
4755                    className.splice(1, 0, SPACE + to);
4756                    Y.Dom.setAttribute(el, CLASS_NAME, trim(className.join(EMPTY)));
4757                    ret = true;
4758                }
4759            } else {
4760                YAHOO.log('replaceClass called with invalid arguments', 'warn', 'Dom');
4761            }
4762
4763            return ret;
4764        },
4765        
4766        /**
4767         * Returns an ID and applies it to the element "el", if provided.
4768         * @method generateId  
4769         * @param {String | HTMLElement | Array} el (optional) An optional element array of elements to add an ID to (no ID is added if one is already present).
4770         * @param {String} prefix (optional) an optional prefix to use (defaults to "yui-gen").
4771         * @return {String | Array} The generated ID, or array of generated IDs (or original ID if already present on an element)
4772         */
4773        generateId: function(el, prefix) {
4774            prefix = prefix || 'yui-gen';
4775
4776            var f = function(el) {
4777                if (el && el.id) { // do not override existing ID
4778                    YAHOO.log('generateId returning existing id ' + el.id, 'info', 'Dom');
4779                    return el.id;
4780                }
4781
4782                var id = prefix + YAHOO.env._id_counter++;
4783                YAHOO.log('generateId generating ' + id, 'info', 'Dom');
4784
4785                if (el) {
4786                    if (el[OWNER_DOCUMENT] && el[OWNER_DOCUMENT].getElementById(id)) { // in case one already exists
4787                        // use failed id plus prefix to help ensure uniqueness
4788                        return Y.Dom.generateId(el, id + prefix);
4789                    }
4790                    el.id = id;
4791                }
4792                
4793                return id;
4794            };
4795
4796            // batch fails when no element, so just generate and return single ID
4797            return Y.Dom.batch(el, f, Y.Dom, true) || f.apply(Y.Dom, arguments);
4798        },
4799        
4800        /**
4801         * Determines whether an HTMLElement is an ancestor of another HTML element in the DOM hierarchy.
4802         * @method isAncestor
4803         * @param {String | HTMLElement} haystack The possible ancestor
4804         * @param {String | HTMLElement} needle The possible descendent
4805         * @return {Boolean} Whether or not the haystack is an ancestor of needle
4806         */
4807        isAncestor: function(haystack, needle) {
4808            haystack = Y.Dom.get(haystack);
4809            needle = Y.Dom.get(needle);
4810            
4811            var ret = false;
4812
4813            if ( (haystack && needle) && (haystack[NODE_TYPE] && needle[NODE_TYPE]) ) {
4814                if (haystack.contains && haystack !== needle) { // contains returns true when equal
4815                    ret = haystack.contains(needle);
4816                }
4817                else if (haystack.compareDocumentPosition) { // gecko
4818                    ret = !!(haystack.compareDocumentPosition(needle) & 16);
4819                }
4820            } else {
4821                YAHOO.log('isAncestor failed; invalid input: ' + haystack + ',' + needle, 'error', 'Dom');
4822            }
4823            YAHOO.log('isAncestor(' + haystack + ',' + needle + ' returning ' + ret, 'info', 'Dom');
4824            return ret;
4825        },
4826        
4827        /**
4828         * Determines whether an HTMLElement is present in the current document.
4829         * @method inDocument         
4830         * @param {String | HTMLElement} el The element to search for
4831         * @param {Object} doc An optional document to search, defaults to element's owner document 
4832         * @return {Boolean} Whether or not the element is present in the current document
4833         */
4834        inDocument: function(el, doc) {
4835            return Y.Dom._inDoc(Y.Dom.get(el), doc);
4836        },
4837
4838        _inDoc: function(el, doc) {
4839            var ret = false;
4840            if (el && el[TAG_NAME]) {
4841                doc = doc || el[OWNER_DOCUMENT]; 
4842                ret = Y.Dom.isAncestor(doc[DOCUMENT_ELEMENT], el);
4843            } else {
4844                YAHOO.log('inDocument failed: invalid input', 'error', 'Dom');
4845            }
4846            return ret;
4847        },
4848        
4849        /**
4850         * Returns an array of HTMLElements that pass the test applied by supplied boolean method.
4851         * For optimized performance, include a tag and/or root node when possible.
4852         * Note: This method operates against a live collection, so modifying the 
4853         * collection in the callback (removing/appending nodes, etc.) will have
4854         * side effects.  Instead you should iterate the returned nodes array,
4855         * as you would with the native "getElementsByTagName" method. 
4856         * @method getElementsBy
4857         * @param {Function} method - A boolean method for testing elements which receives the element as its only argument.
4858         * @param {String} tag (optional) The tag name of the elements being collected
4859         * @param {String | HTMLElement} root (optional) The HTMLElement or an ID to use as the starting point 
4860         * @param {Function} apply (optional) A function to apply to each element when found 
4861         * @param {Any} o (optional) An optional arg that is passed to the supplied method
4862         * @param {Boolean} overrides (optional) Whether or not to override the scope of "method" with "o"
4863         * @return {Array} Array of HTMLElements
4864         */
4865        getElementsBy: function(method, tag, root, apply, o, overrides, firstOnly) {
4866            tag = tag || '*';
4867            root = (root) ? Y.Dom.get(root) : null || document; 
4868
4869                var ret = (firstOnly) ? null : [],
4870                    elements;
4871            
4872            // in case Dom.get() returns null
4873            if (root) {
4874                elements = root.getElementsByTagName(tag);
4875                for (var i = 0, len = elements.length; i < len; ++i) {
4876                    if ( method(elements[i]) ) {
4877                        if (firstOnly) {
4878                            ret = elements[i]; 
4879                            break;
4880                        } else {
4881                            ret[ret.length] = elements[i];
4882                        }
4883                    }
4884                }
4885
4886                if (apply) {
4887                    Y.Dom.batch(ret, apply, o, overrides);
4888                }
4889            }
4890
4891            YAHOO.log('getElementsBy returning ' + ret, 'info', 'Dom');
4892            
4893            return ret;
4894        },
4895        
4896        /**
4897         * Returns the first HTMLElement that passes the test applied by the supplied boolean method.
4898         * @method getElementBy
4899         * @param {Function} method - A boolean method for testing elements which receives the element as its only argument.
4900         * @param {String} tag (optional) The tag name of the elements being collected
4901         * @param {String | HTMLElement} root (optional) The HTMLElement or an ID to use as the starting point 
4902         * @return {HTMLElement}
4903         */
4904        getElementBy: function(method, tag, root) {
4905            return Y.Dom.getElementsBy(method, tag, root, null, null, null, true); 
4906        },
4907
4908        /**
4909         * Runs the supplied method against each item in the Collection/Array.
4910         * The method is called with the element(s) as the first arg, and the optional param as the second ( method(el, o) ).
4911         * @method batch
4912         * @param {String | HTMLElement | Array} el (optional) An element or array of elements to apply the method to
4913         * @param {Function} method The method to apply to the element(s)
4914         * @param {Any} o (optional) An optional arg that is passed to the supplied method
4915         * @param {Boolean} overrides (optional) Whether or not to override the scope of "method" with "o"
4916         * @return {Any | Array} The return value(s) from the supplied method
4917         */
4918        batch: function(el, method, o, overrides) {
4919            var collection = [],
4920                scope = (overrides) ? o : null;
4921                
4922            el = (el && (el[TAG_NAME] || el.item)) ? el : Y.Dom.get(el);
4922 // skip get() when possible
4923            if (el && method) {
4924                if (el[TAG_NAME] || el.length === undefined) { // element or not array-like 
4925                    return method.call(scope, el, o);
4926                } 
4927
4928                for (var i = 0; i < el.length; ++i) {
4929                    collection[collection.length] = method.call(scope || el[i], el[i], o);
4930                }
4931            } else {
4932                YAHOO.log('batch called with invalid arguments', 'warn', 'Dom');
4933                return false;
4934            } 
4935            return collection;
4936        },
4937        
4938        /**
4939         * Returns the height of the document.
4940         * @method getDocumentHeight
4941         * @return {Int} The height of the actual document (which includes the body and its margin).
4942         */
4943        getDocumentHeight: function() {
4944            var scrollHeight = (document[COMPAT_MODE] != CSS1_COMPAT || isSafari) ? document.body.scrollHeight : documentElement.scrollHeight,
4945                h = Math.max(scrollHeight, Y.Dom.getViewportHeight());
4946
4947            YAHOO.log('getDocumentHeight returning ' + h, 'info', 'Dom');
4948            return h;
4949        },
4950        
4951        /**
4952         * Returns the width of the document.
4953         * @method getDocumentWidth
4954         * @return {Int} The width of the actual document (which includes the body and its margin).
4955         */
4956        getDocumentWidth: function() {
4957            var scrollWidth = (document[COMPAT_MODE] != CSS1_COMPAT || isSafari) ? document.body.scrollWidth : documentElement.scrollWidth,
4958                w = Math.max(scrollWidth, Y.Dom.getViewportWidth());
4959            YAHOO.log('getDocumentWidth returning ' + w, 'info', 'Dom');
4960            return w;
4961        },
4962
4963        /**
4964         * Returns the current height of the viewport.
4965         * @method getViewportHeight
4966         * @return {Int} The height of the viewable area of the page (excludes scrollbars).
4967         */
4968        getViewportHeight: function() {
4969            var height = self.innerHeight, // Safari, Opera
4970                mode = document[COMPAT_MODE];
4971        
4972            if ( (mode || isIE) && !isOpera ) { // IE, Gecko
4973                height = (mode == CSS1_COMPAT) ?
4974                        documentElement.clientHeight : // Standards
4975                        document.body.clientHeight; // Quirks
4976            }
4977        
4978            YAHOO.log('getViewportHeight returning ' + height, 'info', 'Dom');
4979            return height;
4980        },
4981        
4982        /**
4983         * Returns the current width of the viewport.
4984         * @method getViewportWidth
4985         * @return {Int} The width of the viewable area of the page (excludes scrollbars).
4986         */
4987        
4988        getViewportWidth: function() {
4989            var width = self.innerWidth,  // Safari
4990                mode = document[COMPAT_MODE];
4991            
4992            if (mode || isIE) { // IE, Gecko, Opera
4993                width = (mode == CSS1_COMPAT) ?
4994                        documentElement.clientWidth : // Standards
4995                        document.body.clientWidth; // Quirks
4996            }
4997            YAHOO.log('getViewportWidth returning ' + width, 'info', 'Dom');
4998            return width;
4999        },
5000
5001       /**
5002         * Returns the nearest ancestor that passes the test applied by supplied boolean method.
5003         * For performance reasons, IDs are not accepted and argument validation omitted.
5004         * @method getAncestorBy
5005         * @param {HTMLElement} node The HTMLElement to use as the starting point 
5006         * @param {Function} method - A boolean method for testing elements which receives the element as its only argument.
5007         * @return {Object} HTMLElement or null if not found
5008         */
5009        getAncestorBy: function(node, method) {
5010            while ( (node = node[PARENT_NODE]) ) { // NOTE: assignment
5011                if ( Y.Dom._testElement(node, method) ) {
5012                    YAHOO.log('getAncestorBy returning ' + node, 'info', 'Dom');
5013                    return node;
5014                }
5015            } 
5016
5017            YAHOO.log('getAncestorBy returning null (no ancestor passed test)', 'error', 'Dom');
5018            return null;
5019        },
5020        
5021        /**
5022         * Returns the nearest ancestor with the given className.
5023         * @method getAncestorByClassName
5024         * @param {String | HTMLElement} node The HTMLElement or an ID to use as the starting point 
5025         * @param {String} className
5026         * @return {Object} HTMLElement
5027         */
5028        getAncestorByClassName: function(node, className) {
5029            node = Y.Dom.get(node);
5030            if (!node) {
5031                YAHOO.log('getAncestorByClassName failed: invali
5031d node argument', 'error', 'Dom');
5032                return null;
5033            }
5034            var method = function(el) { return Y.Dom.hasClass(el, className); };
5035            return Y.Dom.getAncestorBy(node, method);
5036        },
5037
5038        /**
5039         * Returns the nearest ancestor with the given tagName.
5040         * @method getAncestorByTagName
5041         * @param {String | HTMLElement} node The HTMLElement or an ID to use as the starting point 
5042         * @param {String} tagName
5043         * @return {Object} HTMLElement
5044         */
5045        getAncestorByTagName: function(node, tagName) {
5046            node = Y.Dom.get(node);
5047            if (!node) {
5048                YAHOO.log('getAncestorByTagName failed: invalid node argument', 'error', 'Dom');
5049                return null;
5050            }
5051            var method = function(el) {
5052                 return el[TAG_NAME] && el[TAG_NAME].toUpperCase() == tagName.toUpperCase();
5053            };
5054
5055            return Y.Dom.getAncestorBy(node, method);
5056        },
5057
5058        /**
5059         * Returns the previous sibling that is an HTMLElement. 
5060         * For performance reasons, IDs are not accepted and argument validation omitted.
5061         * Returns the nearest HTMLElement sibling if no method provided.
5062         * @method getPreviousSiblingBy
5063         * @param {HTMLElement} node The HTMLElement to use as the starting point 
5064         * @param {Function} method A boolean function used to test siblings
5065         * that receives the sibling node being tested as its only argument
5066         * @return {Object} HTMLElement or null if not found
5067         */
5068        getPreviousSiblingBy: function(node, method) {
5069            while (node) {
5070                node = node.previousSibling;
5071                if ( Y.Dom._testElement(node, method) ) {
5072                    return node;
5073                }
5074            }
5075            return null;
5076        }, 
5077
5078        /**
5079         * Returns the previous sibling that is an HTMLElement 
5080         * @method getPreviousSibling
5081         * @param {String | HTMLElement} node The HTMLElement or an ID to use as the starting point 
5082         * @return {Object} HTMLElement or null if not found
5083         */
5084        getPreviousSibling: function(node) {
5085            node = Y.Dom.get(node);
5086            if (!node) {
5087                YAHOO.log('getPreviousSibling failed: invalid node argument', 'error', 'Dom');
5088                return null;
5089            }
5090
5091            return Y.Dom.getPreviousSiblingBy(node);
5092        }, 
5093
5094        /**
5095         * Returns the next HTMLElement sibling that passes the boolean method. 
5096         * For performance reasons, IDs are not accepted and argument validation omitted.
5097         * Returns the nearest HTMLElement sibling if no method provided.
5098         * @method getNextSiblingBy
5099         * @param {HTMLElement} node The HTMLElement to use as the starting point 
5100         * @param {Function} method A boolean function used to test siblings
5101         * that receives the sibling node being tested as its only argument
5102         * @return {Object} HTMLElement or null if not found
5103         */
5104        getNextSiblingBy: function(node, method) {
5105            while (node) {
5106                node = node.nextSibling;
5107                if ( Y.Dom._testElement(node, method) ) {
5108                    return node;
5109                }
5110            }
5111            return null;
5112        }, 
5113
5114        /**
5115         * Returns the next sibling that is an HTMLElement 
5116         * @method getNextSibling
5117         * @param {String | HTMLElement} node The HTMLElement or an ID to use as the starting point 
5118         * @return {Object} HTMLElement or null if not found
5119         */
5120        getNextSibling: function(node) {
5121            node = Y.Dom.get(node);
5122            if (!node) {
5123                YAHOO.log('getNextSibling failed: invalid node argument', 'error', 'Dom');
5124                return null;
5125            }
5126
5127            return Y.Dom.getNextSiblingBy(node);
5128        }, 
5129
5130        /**
5131         * Returns the first HTMLElement child that passes the test method. 
5132         * @method getFirstChildBy
5133         * @param {HTMLElement} node The HTMLElement to use as the starting point 
5134         * @param {Function} method A boolean function used to test children
5135         * that receives the node being tested as its only argument
5136         * @return {Object} HTMLElement or null if not found
5137         */
5138        getFirstChildBy: function(node, method) {
5139            var child = ( Y.Dom._testElement(node.firstChild, method) ) ? node.firstChild : null;
5140            return child || Y.Dom.getNextSiblingBy(node.firstChild, method);
5141        }, 
5142
5143        /**
5144         * Returns the first HTMLElement child. 
5145         * @method getFirstChild
5146         * @param {String | HTMLElement} node The HTMLElement or an ID to use as the starting point 
5147         * @return {Object} HTMLElement or null if not found
5148         */
5149        getFirstChild: function(node, method) {
5150            node = Y.Dom.get(node);
5151            if (!node) {
5152                YAHOO.log('getFirstChild failed: invalid node argument', 'error', 'Dom');
5153                return null;
5154            }
5155            return Y.Dom.getFirstChildBy(node);
5156        }, 
5157
5158        /**
5159         * Returns the last HTMLElement child that passes the test method. 
5160         * @method getLastChildBy
5161         * @param {HTMLElement} node The HTMLElement to use as the starting point 
5162         * @param {Function} method A boolean function used to test children
5163         * that receives the node being tested as its only argument
5164         * @return {Object} HTMLElement or null if not found
5165         */
5166        getLastChildBy: function(node, method) {
5167            if (!node) {
5168                YAHOO.log('getLastChild failed: invalid node argument', 'error', 'Dom');
5169                return null;
5170            }
5171            var child = ( Y.Dom._testElement(node.lastChild, method) ) ? node.lastChild : null;
5172            return child || Y.Dom.getPreviousSiblingBy(node.lastChild, method);
5173        }, 
5174
5175        /**
5176         * Returns the last HTMLElement child. 
5177         * @method getLastChild
5178         * @param {String | HTMLElement} node The HTMLElement or an ID to use as the starting point 
5179         * @return {Object} HTMLElement or null if not found
5180         */
5181        getLastChild: function(node) {
5182            node = Y.Dom.get(node);
5183            return Y.Dom.getLastChildBy(node);
5184        }, 
5185
5186        /**
5187         * Returns an array of HTMLElement childNodes that pass the test method. 
5188         * @method getChildrenBy
5189         * @param {HTMLElement} node The HTMLElement to start from
5190         * @param {Function} method A boolean function used to test children
5191         * that receives the node being tested as its only argument
5192         * @return {Array} A static array of HTMLElements
5193         */
5194        getChildrenBy: function(node, method) {
5195            var child = Y.Dom.getFirstChildBy(node, method),
5196                children = child ? [child] : [];
5197
5198            Y.Dom.getNextSiblingBy(child, function(node) {
5199                if ( !method || method(node) ) {
5200                    children[children.length] = node;
5201                }
5202                return false; // fail test to collect all children
5203            });
5204
5205            return children;
5206        },
5207 
5208        /**
5209         * Returns an array of HTMLElement childNodes. 
5210         * @method getChildren
5211         * @param {String | HTMLElement} node The HTMLElement or an ID to use as the starting point 
5212         * @return {Array} A static array of HTMLElements
5213         */
5214        getChildren: function(node) {
5215            node = Y.Dom.get(node);
5216            if (!node) {
5217                YAHOO.log('getChildren failed: invalid node argument', 'error', 'Dom');
5218            }
5219
5220            return Y.Dom.getChildrenBy(node);
5221        },
5222
5223        /**
5224         * Returns the left scroll value of the document 
5225         * @method getDocumentScrollLeft
5226         * @param {HTMLDocument} document (optional) The document to get the scroll value of
5227         * @return {Int}  The amount that the document is scrolled to the left
5228         */
5229        getDocumentScrollLeft: function(doc) {
5230            doc = doc || document;
5231            return Math.max(doc[DOCUMENT_ELEMENT].scrollLeft, doc.body.scrollLeft);
5232        }, 
5233
5234        /**
5235         * Returns the top scroll value of the document 
5236         * @method getDocumentScrollTop
5237         * @param {HTMLDocument} document (optional) The document to get the scroll value of
5238         * @return {Int}  The amount that the document is scrolled to the top
5239         */
5240        getDocumentScrollTop: function(doc) {
5241            doc = doc || document;
5242            return Math.max(doc[DOCUMENT_ELEMENT].scrollTop, doc.body.scrollTop);
5243        },
5244
5245        /**
5246         * Inserts the new node as the previous sibling of the reference node 
5247         * @method insertBefore
5248         * @param {String | HTMLElement} newNode The node to be inserted
5249         * @param {String | HTMLElement} referenceNode The node to insert the new node before 
5250         * @return {HTMLElement} The node that was inserted (or null if insert fails) 
5251         */
5252        insertBefore: function(newNode, referenceNode) {
5253            newNode = Y.Dom.get(newNode); 
5254            referenceNode = Y.Dom.get(referenceNode); 
5255            
5256            if (!newNode || !referenceNode || !referenceNode[PARENT_NODE]) {
5257                YAHOO.log('insertAfter failed: missing or invalid arg(s)', 'error', 'Dom');
5258                return null;
5259            }       
5260
5261            return referenceNode[PARENT_NODE].insertBefore(newNode, referenceNode); 
5262        },
5263
5264        /**
5265         * Inserts the new node as the next sibling of the reference node 
5266         * @method insertAfter
5267         * @param {String | HTMLElement} newNode The node to be inserted
5268         * @param {String | HTMLElement} referenceNode The node to insert the new node after 
5269         * @return {HTMLElement} The node that was inserted (or null if insert fails) 
5270         */
5271        insertAfter: function(newNode, referenceNode) {
5272            newNode = Y.Dom.get(newNode); 
5273            referenceNode = Y.Dom.get(referenceNode); 
5274            
5275            if (!newNode || !referenceNode || !referenceNode[PARENT_NODE]) {
5276                YAHOO.log('insertAfter failed: missing or invalid arg(s)', 'error', 'Dom');
5277                return null;
5278            }       
5279
5280            if (referenceNode.nextSibling) {
5281                return referenceNode[PARENT_NODE].insertBefore(newNode, referenceNode.nextSibling); 
5282            } else {
5283                return referenceNode[PARENT_NODE].appendChild(newNode);
5284            }
5285        },
5286
5287        /**
5288         * Creates a Region based on the viewport relative to the document. 
5289         * @method getClientRegion
5290         * @return {Region} A Region object representing the viewport which accounts for document scroll
5291         */
5292        getClientRegion: function() {
5293            var t = Y.Dom.getDocumentScrollTop(),
5294                l = Y.Dom.getDocumentScrollLeft(),
5295                r = Y.Dom.getViewportWidth() + l,
5296                b = Y.Dom.getViewportHeight() + t;
5297
5298            return new Y.Region(t, r, b, l);
5299        },
5300
5301        /**
5302         * Provides a normalized attribute interface. 
5303         * @method setAttribute
5304         * @param {String | HTMLElement} el The target element for the attribute.
5305         * @param {String} attr The attribute to set.
5306         * @param {String} val The value of the attribute.
5307         */
5308        setAttribute: function(el, attr, val) {
5309            Y.Dom.batch(el, Y.Dom._setAttribute, { attr: attr, val: val });
5310        },
5311
5312        _setAttribute: function(el, args) {
5313            var attr = Y.Dom._toCamel(args.attr),
5314                val = args.val;
5315
5316            if (el && el.setAttribute) {
5317                // set as DOM property, except for BUTTON, which errors on property setter
5318                if (Y.Dom.DOT_ATTRIBUTES[attr] && el.tagName && el.tagName != 'BUTTON') {
5319                    el[attr] = val;
5320                } else {
5321                    attr = Y.Dom.CUSTOM_ATTRIBUTES[attr] || attr;
5322                    el.setAttribute(attr, val);
5323                }
5324            } else {
5325                YAHOO.log('setAttribute method not available for ' + el, 'error', 'Dom');
5326            }
5327        },
5328
5329        /**
5330         * Provides a normalized attribute interface. 
5331         * @method getAttribute
5332         * @param {String | HTMLElement} el The target element for the attribute.
5333         * @param {String} attr The attribute to get.
5334         * @return {String} The current value of the attribute. 
5335         */
5336        getAttribute: function(el, attr) {
5337            return Y.Dom.batch(el, Y.Dom._getAttribute, attr);
5338        },
5339
5340
5341        _getAttribute: function(el, attr) {
5342            var val;
5343            attr = Y.Dom.CUSTOM_ATTRIBUTES[attr] || attr;
5344
5345            if (Y.Dom.DOT_ATTRIBUTES[attr]) {
5346                val = el[attr];
5347            } else if (el && 'getAttribute' in el) {
5348                if (/^(?:href|src)$/.test(attr)) { // use IE flag to return exact value
5349                    val = el.getAttribute(attr, 2);
5350                } else {
5351                    val = el.getAttribute(attr);
5352                }
5353            } else {
5354                YAHOO.log('getAttribute method not available for ' + el, 'error', 'Dom');
5355            }
5356
5357            return val;
5358        },
5359
5360        _toCamel: function(property) {
5361            var c = propertyCache;
5362
5363            function tU(x,l) {
5364                return l.toUpperCase();
5365            }
5366
5367            return c[property] || (c[property] = property.indexOf('-') === -1 ? 
5368                                    property :
5369                                    property.replace( /-([a-z])/gi, tU ));
5370        },
5371
5372        _getClassRegex: function(className) {
5373            var re;
5374            if (className !== undefined) { // allow empty string to pass
5375                if (className.exec) { // already a RegExp
5376                    re = className;
5377                } else {
5378                    re = reCache[className];
5379                    if (!re) {
5380                        // escape special chars (".", "[", etc.)
5381                        className = className.replace(Y.Dom._patterns.CLASS_RE_TOKENS, '\\$1');
5382                        className = className.replace(/\s+/g, SPACE); // convert line breaks and other delims
5383                        re = reCache[className] = new RegExp(C_START + className + C_END, G);
5384                    }
5385                }
5386            }
5387            return re;
5388        },
5389
5390        _patterns: {
5391            ROOT_TAG: /^body|html$/i, // body for quirks mode, html for standards,
5392            CLASS_RE_TOKENS: /([\.\(\)\^\$\*\+\?\|\[\]\{\}\\])/g
5393        },
5394
5395
5396        _testElement: function(node, method) {
5397            return node && node[NODE_TYPE] == 1 && ( !method || method(node) );
5398        },
5399
5400        _calcBorders: function(node, xy2) {
5401            var t = parseInt(Y.Dom[GET_COMPUTED_STYLE](node, BORDER_TOP_WIDTH), 10) || 0,
5402                l = parseInt(Y.Dom[GET_COMPUTED_STYLE](node, BORDER_LEFT_WIDTH), 10) || 0;
5403            if (isGecko) {
5404                if (RE_TABLE.test(node[TAG_NAME])) {
5405                    t = 0;
5406                    l = 0;
5407                }
5408            }
5409            xy2[0] += l;
5410            xy2[1] += t;
5411            return xy2;
5412        }
5413    };
5414        
5415    var _getComputedStyle = Y.Dom[GET_COMPUTED_STYLE];
5416    // fix opera computedStyle default color unit (convert to rgb)
5417    if (UA.opera) {
5418        Y.Dom[GET_COMPUTED_STYLE] = function(node, att) {
5419            var val = _getComputedStyle(node, att);
5420            if (RE_COLOR.test(att)) {
5421                val = Y.Dom.Color.toRGB(val);
5422            }
5423
5424            return val;
5425        };
5426
5427    }
5428
5429    // safari converts transparent to rgba(), others use "transparent"
5430    if (UA.webkit) {
5431        Y.Dom[GET_COMPUTED_STYLE] = function(node, att) {
5432            var val = _getComputedStyle(node, att);
5433
5434            if (val === 'rgba(0, 0, 0, 0)') {
5435                val = 'transparent'; 
5436            }
5437
5438            return val;
5439        };
5440
5441    }
5442
5443    if (UA.ie && UA.ie >= 8) {
5444        Y.Dom.DOT_ATTRIBUTES.type = true; // IE 8 errors on input.setAttribute('type')
5445    }
5446})();
5447/**
5448 * A region is a representation of an object on a grid.  It is defined
5449 * by the top, right, bottom, left extents, so is rectangular by default.  If 
5450 * other shapes are required, this class could be extended to support it.
5451 * @namespace YAHOO.util
5452 * @class Region
5453 * @param {Int} t the top extent
5454 * @param {Int} r the right extent
5455 * @param {Int} b the bottom extent
5456 * @param {Int} l the left extent
5457 * @constructor
5458 */
5459YAHOO.util.Region = function(t, r, b, l) {
5460
5461    /**
5462     * The region's top extent
5463     * @property top
5464     * @type Int
5465     */
5466    this.top = t;
5467    
5468    /**
5469     * The region's top extent
5470     * @property y
5471     * @type Int
5472     */
5473    this.y = t;
5474    
5475    /**
5476     * The region's top extent as index, for symmetry with set/getXY
5477     * @property 1
5478     * @type Int
5479     */
5480    this[1] = t;
5481
5482    /**
5483     * The region's right extent
5484     * @property right
5485     * @type int
5486     */
5487    this.right = r;
5488
5489    /**
5490     * The region's bottom extent
5491     * @property bottom
5492     * @type Int
5493     */
5494    this.bottom = b;
5495
5496    /**
5497     * The region's left extent
5498     * @property left
5499     * @type Int
5500     */
5501    this.left = l;
5502    
5503    /**
5504     * The region's left extent
5505     * @property x
5506     * @type Int
5507     */
5508    this.x = l;
5509    
5510    /**
5511     * The region's left extent as index, for symmetry with set/getXY
5512     * @property 0
5513     * @type Int
5514     */
5515    this[0] = l;
5516
5517    /**
5518     * The region's total width 
5519     * @property width 
5520     * @type Int
5521     */
5522    this.width = this.right - this.left;
5523
5524    /**
5525     * The region's total height 
5526     * @property height 
5527     * @type Int
5528     */
5529    this.height = this.bottom - this.top;
5530};
5531
5532/**
5533 * Returns true if this region contains the region passed in
5534 * @method contains
5535 * @param  {Region}  region The region to evaluate
5536 * @return {Boolean}        True if the region is contained with this region, 
5537 *                          else false
5538 */
5539YAHOO.util.Region.prototype.contains = function(region) {
5540    return ( region.left   >= this.left   && 
5541             region.right  <= this.right  && 
5542             region.top    >= this.top    && 
5543             region.bottom <= this.bottom    );
5544
5545    // this.logger.debug("does " + this + " contain " + region + " ... " + ret);
5546};
5547
5548/**
5549 * Returns the area of the region
5550 * @method getArea
5551 * @return {Int} the region's area
5552 */
5553YAHOO.util.Region.prototype.getArea = function() {
5554    return ( (this.bottom - this.top) * (this.right - this.left) );
5555};
5556
5557/**
5558 * Returns the region where the passed in region overlaps with this one
5559 * @method intersect
5560 * @param  {Region} region The region that intersects
5561 * @return {Region}        The overlap region, or null if there is no overlap
5562 */
5563YAHOO.util.Region.prototype.intersect = function(region) {
5564    var t = Math.max( this.top,    region.top    ),
5565        r = Math.min( this.right,  region.right  ),
5566        b = Math.min( this.bottom, region.bottom ),
5567        l = Math.max( this.left,   region.left   );
5568    
5569    if (b >= t && r >= l) {
5570        return new YAHOO.util.Region(t, r, b, l);
5571    } else {
5572        return null;
5573    }
5574};
5575
5576/**
5577 * Returns the region representing the smallest region that can contain both
5578 * the passed in region and this region.
5579 * @method union
5580 * @param  {Region} region The region that to create the union with
5581 * @return {Region}
5581        The union region
5582 */
5583YAHOO.util.Region.prototype.union = function(region) {
5584    var t = Math.min( this.top,    region.top    ),
5585        r = Math.max( this.right,  region.right  ),
5586        b = Math.max( this.bottom, region.bottom ),
5587        l = Math.min( this.left,   region.left   );
5588
5589    return new YAHOO.util.Region(t, r, b, l);
5590};
5591
5592/**
5593 * toString
5594 * @method toString
5595 * @return string the region properties
5596 */
5597YAHOO.util.Region.prototype.toString = function() {
5598    return ( "Region {"    +
5599             "top: "       + this.top    + 
5600             ", right: "   + this.right  + 
5601             ", bottom: "  + this.bottom + 
5602             ", left: "    + this.left   + 
5603             ", height: "  + this.height + 
5604             ", width: "    + this.width   + 
5605             "}" );
5606};
5607
5608/**
5609 * Returns a region that is occupied by the DOM element
5610 * @method getRegion
5611 * @param  {HTMLElement} el The element
5612 * @return {Region}         The region that the element occupies
5613 * @static
5614 */
5615YAHOO.util.Region.getRegion = function(el) {
5616    var p = YAHOO.util.Dom.getXY(el),
5617        t = p[1],
5618        r = p[0] + el.offsetWidth,
5619        b = p[1] + el.offsetHeight,
5620        l = p[0];
5621
5622    return new YAHOO.util.Region(t, r, b, l);
5623};
5624
5625/////////////////////////////////////////////////////////////////////////////
5626
5627
5628/**
5629 * A point is a region that is special in that it represents a single point on 
5630 * the grid.
5631 * @namespace YAHOO.util
5632 * @class Point
5633 * @param {Int} x The X position of the point
5634 * @param {Int} y The Y position of the point
5635 * @constructor
5636 * @extends YAHOO.util.Region
5637 */
5638YAHOO.util.Point = function(x, y) {
5639   if (YAHOO.lang.isArray(x)) { // accept input from Dom.getXY, Event.getXY, etc.
5640      y = x[1]; // dont blow away x yet
5641      x = x[0];
5642   }
5643 
5644    YAHOO.util.Point.superclass.constructor.call(this, y, x, y, x);
5645};
5646
5647YAHOO.extend(YAHOO.util.Point, YAHOO.util.Region);
5648
5649(function() {
5650/**
5651 * Internal methods used to add style management functionality to DOM.
5652 * @module dom
5653 * @class IEStyle
5654 * @namespace YAHOO.util.Dom
5655 */
5656
5657var Y = YAHOO.util, 
5658    CLIENT_TOP = 'clientTop',
5659    CLIENT_LEFT = 'clientLeft',
5660    PARENT_NODE = 'parentNode',
5661    RIGHT = 'right',
5662    HAS_LAYOUT = 'hasLayout',
5663    PX = 'px',
5664    OPACITY = 'opacity',
5665    AUTO = 'auto',
5666    BORDER_LEFT_WIDTH = 'borderLeftWidth',
5667    BORDER_TOP_WIDTH = 'borderTopWidth',
5668    BORDER_RIGHT_WIDTH = 'borderRightWidth',
5669    BORDER_BOTTOM_WIDTH = 'borderBottomWidth',
5670    VISIBLE = 'visible',
5671    TRANSPARENT = 'transparent',
5672    HEIGHT = 'height',
5673    WIDTH = 'width',
5674    STYLE = 'style',
5675    CURRENT_STYLE = 'currentStyle',
5676
5677// IE getComputedStyle
5678// TODO: unit-less lineHeight (e.g. 1.22)
5679    re_size = /^width|height$/,
5680    re_unit = /^(\d[.\d]*)+(em|ex|px|gd|rem|vw|vh|vm|ch|mm|cm|in|pt|pc|deg|rad|ms|s|hz|khz|%){1}?/i,
5681
5682    ComputedStyle = {
5683        /**
5684        * @method get
5685        * @description Method used by DOM to get style information for IE
5686        * @param {HTMLElement} el The element to check
5687        * @param {String} property The property to check
5688        * @returns {String} The computed style
5689        */
5690        get: function(el, property) {
5691            var value = '',
5692                current = el[CURRENT_STYLE][property];
5693
5694            if (property === OPACITY) {
5695                value = Y.Dom.getStyle(el, OPACITY);        
5696            } else if (!current || (current.indexOf && current.indexOf(PX) > -1)) { // no need to convert
5697                value = current;
5698            } else if (Y.Dom.IE_COMPUTED[property]) { // use compute function
5699                value = Y.Dom.IE_COMPUTED[property](el, property);
5700            } else if (re_unit.test(current)) { // convert to pixel
5701                value = Y.Dom.IE.ComputedStyle.getPixel(el, property);
5702            } else {
5703                value = current;
5704            }
5705
5706            return value;
5707        },
5708        /**
5709        * @method getOffset
5710        * @description Determine the offset of an element
5711        * @param {HTMLElement} el The element to check
5712        * @param {String} prop The property to check.
5713        * @return {String} The offset
5714        */
5715        getOffset: function(el, prop) {
5716            var current = el[CURRENT_STYLE][prop],                        // value of "width", "top", etc.
5717                capped = prop.charAt(0).toUpperCase() + prop.substr(1), // "Width", "Top", etc.
5718                offset = 'offset' + capped,                             // "offsetWidth", "offsetTop", etc.
5719                pixel = 'pixel' + capped,                               // "pixelWidth", "pixelTop", etc.
5720                value = '',
5721                actual;
5722
5723            if (current == AUTO) {
5724                actual = el[offset]; // offsetHeight/Top etc.
5725                if (actual === undefined) { // likely "right" or "bottom"
5726                    value = 0;
5727                }
5728
5729                value = actual;
5730                if (re_size.test(prop)) { // account for box model diff 
5731                    el[STYLE][prop] = actual; 
5732                    if (el[offset] > actual) {
5733                        // the difference is padding + border (works in Standards & Qu
5733irks modes)
5734                        value = actual - (el[offset] - actual);
5735                    }
5736                    el[STYLE][prop] = AUTO; // revert to auto
5737                }
5738            } else { // convert units to px
5739                if (!el[STYLE][pixel] && !el[STYLE][prop]) { // need to map style.width to currentStyle (no currentStyle.pixelWidth)
5740                    el[STYLE][prop] = current;              // no style.pixelWidth if no style.width
5741                }
5742                value = el[STYLE][pixel];
5743            }
5744            return value + PX;
5745        },
5746        /**
5747        * @method getBorderWidth
5748        * @description Try to determine the width of an elements border
5749        * @param {HTMLElement} el The element to check
5750        * @param {String} property The property to check
5751        * @return {String} The elements border width
5752        */
5753        getBorderWidth: function(el, property) {
5754            // clientHeight/Width = paddingBox (e.g. offsetWidth - borderWidth)
5755            // clientTop/Left = borderWidth
5756            var value = null;
5757            if (!el[CURRENT_STYLE][HAS_LAYOUT]) { // TODO: unset layout?
5758                el[STYLE].zoom = 1; // need layout to measure client
5759            }
5760
5761            switch(property) {
5762                case BORDER_TOP_WIDTH:
5763                    value = el[CLIENT_TOP];
5764                    break;
5765                case BORDER_BOTTOM_WIDTH:
5766                    value = el.offsetHeight - el.clientHeight - el[CLIENT_TOP];
5767                    break;
5768                case BORDER_LEFT_WIDTH:
5769                    value = el[CLIENT_LEFT];
5770                    break;
5771                case BORDER_RIGHT_WIDTH:
5772                    value = el.offsetWidth - el.clientWidth - el[CLIENT_LEFT];
5773                    break;
5774            }
5775            return value + PX;
5776        },
5777        /**
5778        * @method getPixel
5779        * @description Get the pixel value from a style property
5780        * @param {HTMLElement} node The element to check
5781        * @param {String} att The attribute to check
5782        * @return {String} The pixel value
5783        */
5784        getPixel: function(node, att) {
5785            // use pixelRight to convert to px
5786            var val = null,
5787                styleRight = node[CURRENT_STYLE][RIGHT],
5788                current = node[CURRENT_STYLE][att];
5789
5790            node[STYLE][RIGHT] = current;
5791            val = node[STYLE].pixelRight;
5792            node[STYLE][RIGHT] = styleRight; // revert
5793
5794            return val + PX;
5795        },
5796
5797        /**
5798        * @method getMargin
5799        * @description Get the margin value from a style property
5800        * @param {HTMLElement} node The element to check
5801        * @param {String} att The attribute to check
5802        * @return {String} The margin value
5803        */
5804        getMargin: function(node, att) {
5805            var val;
5806            if (node[CURRENT_STYLE][att] == AUTO) {
5807                val = 0 + PX;
5808            } else {
5809                val = Y.Dom.IE.ComputedStyle.getPixel(node, att);
5810            }
5811            return val;
5812        },
5813
5814        /**
5815        * @method getVisibility
5816        * @description Get the visibility of an element
5817        * @param {HTMLElement} node The element to check
5818        * @param {String} att The attribute to check
5819        * @return {String} The value
5820        */
5821        getVisibility: function(node, att) {
5822            var current;
5823            while ( (current = node[CURRENT_STYLE]) && current[att] == 'inherit') { // NOTE: assignment in test
5824                node = node[PARENT_NODE];
5825            }
5826            return (current) ? current[att] : VISIBLE;
5827        },
5828
5829        /**
5830        * @method getColor
5831        * @description Get the color of an element
5832        * @param {HTMLElement} node The element to check
5833        * @param {String} att The attribute to check
5834        * @return {String} The value
5835        */
5836        getColor: function(node, att) {
5837            return Y.Dom.Color.toRGB(node[CURRENT_STYLE][att]) || TRANSPARENT;
5838        },
5839
5840        /**
5841        * @method getBorderColor
5842        * @description Get the bordercolor of an element
5843        * @param {HTMLElement} node The element to check
5844        * @param {String} att The attribute to check
5845        * @return {String} The value
5846        */
5847        getBorderColor: function(node, att) {
5848            var current = node[CURRENT_STYLE],
5849                val = current[att] || current.color;
5850            return Y.Dom.Color.toRGB(Y.Dom.Color.toHex(val));
5851        }
5852
5853    },
5854
5855//fontSize: getPixelFont,
5856    IEComputed = {};
5857
5858IEComputed.top = IEComputed.right = IEComputed.bottom = IEComputed.left = 
5859        IEComputed[WIDTH] = IEComputed[HEIGHT] = ComputedStyle.getOffset;
5860
5861IEComputed.color = ComputedStyle.getColor;
5862
5863IEComputed[BORDER_TOP_WIDTH] = IEComputed[BORDER_RIGHT_WIDTH] =
5864        IEComputed[BORDER_BOTTOM_WIDTH] = IEComputed[BORDER_LEFT_WIDTH] =
5865        ComputedStyle.getBorderWidth;
5866
5867IEComputed.marginTop = IEComputed.marginRight = IEComputed.marginBottom =
5868        IEComputed.marginLeft = ComputedStyle.getMargin;
5869
5870IEComputed.visibility = ComputedStyle.getVisibility;
5871IEComputed.borderColor = IEComputed.borderTopColor =
5872        IEComputed.borderRightColor = IEComputed.borderBottomColor =
5873        IEComputed.borderLeftColor = ComputedStyle.getBorderColor;
5874
5875Y.Dom.IE_COMPUTED = IEComputed;
5876Y.Dom.IE_ComputedStyle = ComputedStyle;
5877})();
5878(function() {
5879/**
5880 * Add style management functionality to DOM.
5881 * @module dom
5882 * @class Color
5883 * @namespace YAHOO.util.Dom
5884 */
5885
5886var TO_STRING = 'toString',
5887    PARSE_INT = parseInt,
5888    RE = RegExp,
5889    Y = YAHOO.util;
5890
5891Y.Dom.Color = {
5892    /**
5893    * @property KEYWORDS
5894    * @type Object
5895    * @description Color keywords used when converting to Hex
5896    */
5897    KEYWORDS: {
5898        black: '000',
5899        silver: 'c0c0c0',
5900        gray: '808080',
5901        white: 'fff',
5902        maroon: '800000',
5903        red: 'f00',
5904        purple: '800080',
5905        fuchsia: 'f0f',
5906        green: '008000',
5907        lime: '0f0',
5908        olive: '808000',
5909        yellow: 'ff0',
5910        navy: '000080',
5911        blue: '00f',
5912        teal: '008080',
5913        aqua: '0ff'
5914    },
5915    /**
5916    * @property re_RGB
5917    * @private
5918    * @type Regex
5919    * @description Regex to parse rgb(0,0,0) formatted strings
5920    */
5921    re_RGB: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
5922    /**
5923    * @property re_hex
5924    * @private
5925    * @type Regex
5926    * @description Regex to parse #123456 formatted strings
5927    */
5928    re_hex: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
5929    /**
5930    * @property re_hex3
5931    * @private
5932    * @type Regex
5933    * @description Regex to parse #123 formatted strings
5934    */
5935    re_hex3: /([0-9A-F])/gi,
5936    /**
5937    * @method toRGB
5938    * @description Converts a hex or color string to an rgb string: rgb(0,0,0)
5939    * @param {String} val The string to convert to RGB notation.
5940    * @returns {String} The converted string
5941    */
5942    toRGB: function(val) {
5943        if (!Y.Dom.Color.re_RGB.test(val)) {
5944            val = Y.Dom.Color.toHex(val);
5945        }
5946
5947        if(Y.Dom.Color.re_hex.exec(val)) {
5948            val = 'rgb(' + [
5949                PARSE_INT(RE.$1, 16),
5950                PARSE_INT(RE.$2, 16),
5951                PARSE_INT(RE.$3, 16)
5952            ].join(', ') + ')';
5953        }
5954        return val;
5955    },
5956    /**
5957    * @method toHex
5958    * @description Converts an rgb or color string to a hex string: #123456
5959    * @param {String} val The string to convert to hex notation.
5960    * @returns {String} The converted string
5961    */
5962    toHex: function(val) {
5963        val = Y.Dom.Color.KEYWORDS[val] || val;
5964        if (Y.Dom.Color.re_RGB.exec(val)) {
5965            val = [
5966                Number(RE.$1).toString(16),
5967                Number(RE.$2).toString(16),
5968                Number(RE.$3).toString(16)
5969            ];
5970
5971            for (var i = 0; i < val.length; i++) {
5972                if (val[i].length < 2) {
5973                    val[i] = '0' + val[i];
5974                }
5975            }
5976
5977            val = val.join('');
5978        }
5979
5980        if (val.length < 6) {
5981            val = val.replace(Y.Dom.Color.re_hex3, '$1$1');
5982        }
5983
5984        if (val !== 'transparent' && val.indexOf('#') < 0) {
5985            val = '#' + val;
5986        }
5987
5988        return val.toUpperCase();
5989    }
5990};
5991}());
5992YAHOO.register("dom", YAHOO.util.Dom, {version: "2.9.0", build: "2800"});
5993/*
5994Copyright (c) 2011, Yahoo! Inc. All rights reserved.
5995Code licensed under the BSD License:
5996http://developer.yahoo.com/yui/license.html
5997version: 2.9.0
5998*/
5999/**
6000 * The drag and drop utility provides a framework for building drag and drop
6001 * applications.  In addition to enabling drag and drop for specific elements,
6002 * the drag and drop elements are tracked by the manager class, and the
6003 * interactions between the various elements are tracked during the drag and
6004 * the implementing code is notified about these important moments.
6005 * @module dragdrop
6006 * @title Drag and Drop
6007 * @requires yahoo,dom,event
6008 * @namespace YAHOO.util
6009 */
6010
6011// Only load the library once.  Rewriting the manager class would orphan 
6012// existing drag and drop instances.
6013if (!YAHOO.util.DragDropMgr) {
6014
6015/**
6016 * DragDropMgr is a singleton that tracks the element interaction for 
6017 * all DragDrop items in the window.  Generally, you will not call 
6018 * this class directly, but it does have helper methods that could 
6019 * be useful in your DragDrop implementations.
6020 * @class DragDropMgr
6021 * @static
6022 */
6023YAHOO.util.DragDropMgr = function() {
6024
6025    var Event = YAHOO.util.Event,
6026        Dom = YAHOO.util.Dom;
6027
6028    return {
6029        /**
6030        * This property is used to turn on global use of the shim element on all DragDrop instances, defaults to false for backcompat. (Use: YAHOO.util.DDM.useShim = true)
6031        * @property useShim
6032        * @type Boolean
6033        * @static
6034        */
6035        useShim: false,
6036        /**
6037        * This property is used to determine if the shim is active over the screen, default false.
6038        * @private
6039        * @property _shimActive
6040        * @type Boolean
6041        * @static
6042        */
6043        _shimActive: false,
6044        /**
6045        * This property is used when useShim is set on a DragDrop object to store the current state of DDM.useShim so it can be reset when a drag operation is done.
6046        * @private
6047        * @property _shimState
6048        * @type Boolean
6049        * @static
6050        */
6051        _shimState: false,
6052        /**
6053        * This property is used when useShim is set to true, it will set the opacity on the shim to .5 for debugging. Use: (YAHOO.util.DDM._debugShim = true;)
6054        * @private
6055        * @property _debugShim
6056        * @type Boolean
6057        * @static
6058        */
6059        _debugShim: false,
6060        /**
6061        * This method will create a shim element (giving it the id of yui-ddm-shim), it also attaches the mousemove and mouseup listeners to it and attaches a scroll listener on the window
6062        * @private
6063        * @method _sizeShim
6064        * @static
6065        */
6066        _createShim: function() {
6067            YAHOO.log('Creating Shim Element', 'info', 'DragDropMgr');
6068            var s = document.createElement('div');
6069            s.id = 'yui-ddm-shim';
6070            if (document.body.firstChild) {
6071                document.body.insertBefore(s, document.body.firstChild);
6072            } else {
6073                document.body.appendChild(s);
6074            }
6075            s.style.display = 'none';
6076            s.style.backgroundColor = 'red';
6077            s.style.position = 'absolute';
6078            s.style.zIndex = '99999';
6079            Dom.setStyle(s, 'opacity', '0');
6080            this._shim = s;
6081            Event.on(s, "mouseup",   this.handleMouseUp, this, true);
6082            Event.on(s, "mousemove", this.handleMouseMove, this, true);
6083            Event.on(window, 'scroll', this._sizeShim, this, true);
6084        },
6085        /**
6086        * This method will size the shim, called from activate and on window scroll event
6087        * @private
6088        * @method _sizeShim
6089        * @static
6090        */
6091        _sizeShim: function() {
6092            if (this._shimActive) {
6093                YAHOO.log('Sizing Shim', 'info', 'DragDropMgr');
6094                var s = this._shim;
6095                s.style.height = Dom.getDocumentHeight() + 'px';
6096                s.style.width = Dom.getDocumentWidth() + 'px';
6097                s.style.top = '0';
6098                s.style.left = '0';
6099            }
6100        },
6101        /**
6102        * This method will create the shim element if needed, then show the shim element, size the element and set the _shimActive property to true
6103        * @private
6104        * @method _activateShim
6105        * @static
6106        */
6107        _activateShim: function() {
6108            if (this.useShim) {
6109                YAHOO.log('Activating Shim', 'info', 'DragDropMgr');
6110                if (!this._shim) {
6111                    this._createShim();
6112                }
6113                this._shimActive = true;
6114                var s = this._shim,
6115                    o = '0';
6116                if (this._debugShim) {
6117                    o = '.5';
6118                }
6119                Dom.setStyle(s, 'opacity', o);
6120                this._sizeShim();
6121                s.style.display = 'block';
6122            }
6123        },
6124        /**
6125        * This method will hide the shim element and set the _shimActive property to false
6126        * @private
6127        * @method _deactivateShim
6128        * @static
6129        */
6130        _deactivateShim: function() {
6131            YAHOO.log('Deactivating Shim', 'info', 'DragDropMgr');
6132            this._shim.style.display = 'none';
6133            this._shimActive = false;
6134        },
6135        /**
6136        * The HTML element created to use as a shim over the document to track mouse movements
6137        * @private
6138        * @property _shim
6139        * @type HTMLElement
6140        * @static
6141        */
6142        _shim: null,
6143        /**
6144         * Two dimensional Array of registered DragDrop objects.  The first 
6145         * dimension is the DragDrop item group, the second the DragDrop 
6146         * object.
6147         * @property ids
6148         * @type {string: string}
6149         * @private
6150         * @static
6151         */
6152        ids: {},
6153
6154        /**
6155         * Array of element ids defined as drag handles.  Used to determine 
6156         * if the element that generated the mousedown event is actually the 
6157         * handle and not the html element itself.
6158         * @property handleIds
6159         * @type {string: string}
6160         * @private
6161         * @static
6162         */
6163        handleIds: {},
6164
6165        /**
6166         * the DragDrop object that is currently being dragged
6167         * @property dragCurrent
6168         * @type DragDrop
6169         * @private
6170         * @static
6171         **/
6172        dragCurrent: null,
6173
6174        /**
6175         * the DragDrop object(s) that are being hovered over
6176         * @property dragOvers
6177         * @type Array
6178         * @private
6179         * @static
6180         */
6181        dragOvers: {},
6182
6183        /**
6184         * the X distance between the cursor and the object being dragged
6185         * @property deltaX
6186         * @type int
6187         * @private
6188         * @static
6189         */
6190        deltaX: 0,
6191
6192        /**
6193         * the Y distance between the cursor and the object being dragged
6194         * @property deltaY
6195         * @type int
6196         * @private
6197         * @static
6198         */
6199        deltaY: 0,
6200
6201        /**
6202         * Flag to determine if we should prevent the default behavior of the
6203         * events we define. By default this is true, but this can be set to 
6204         * false if you need the default behavior (not recommended)
6205         * @property preventDefault
6206         * @type boolean
6207         * @static
6208         */
6209        preventDefault: true,
6210
6211        /**
6212         * Flag to determine if we should stop the propagation of the events 
6213         * we generate. This is true by default but you may want to set it to
6214         * false if the html element contains other features that require the
6215         * mouse click.
6216         * @property stopPropagation
6217         * @type boolean
6218         * @static
6219         */
6220        stopPropagation: true,
6221
6222        /**
6223         * Internal flag that is set to true when drag and drop has been
6224         * initialized
6225         * @property initialized
6226         * @private
6227         * @static
6228         */
6229        initialized: false,
6230
6231        /**
6232         * All drag and drop can be disabled.
6233         * @property locked
6234         * @private
6235         * @static
6236         */
6237        locked: false,
6238
6239        /**
6240         * Provides additional information about the the current set of
6241         * interactions.  Can be accessed from the event handlers. It
6242         * contains the following properties:
6243         *
6244         *       out:       onDragOut interactions
6245         *       enter:     onDragEnter interactions
6246         *       over:      onDragOver interactions
6247         *       drop:      onDragDrop interactions
6248         *       point:     The location of the cursor
6249         *       draggedRegion: The location of dragged element at the time
6250         *                      of the interaction
6251         *       sourceRegion: The location of the source elemtn at the time
6252         *                     of the interaction
6253         *       validDrop: boolean
6254         * @property interactionInfo
6255         * @type object
6256         * @static
6257         */
6258        interactionInfo: null,
6259
6260        /**
6261         * Called the first time an element is registered.
6262         * @method init
6263         * @private
6264         * @static
6265         */
6266        init: function() {
6267            this.initialized = true;
6268        },
6269
6270        /**
6271         * In point mode, drag and drop interaction is defined by the 
6272         * location of the cursor during the drag/drop
6273         * @property POINT
6274         * @type int
6275         * @static
6276         * @final
6277         */
6278        POINT: 0,
6279
6280        /**
6281         * In intersect mode, drag and drop interaction is defined by the 
6282         * cursor position or the amount of overlap of two or more drag and 
6283         * drop objects.
6284         * @property INTERSECT
6285         * @type int
6286         * @static
6287         * @final
6288         */
6289        INTERSECT: 1,
6290
6291        /**
6292         * In intersect mode, drag and drop interaction is defined only by the 
6293         * overlap of two or more drag and drop objects.
6294         * @property STRICT_INTERSECT
6295         * @type int
6296         * @static
6297         * @final
6298         */
6299        STRICT_INTERSECT: 2,
6300
6301        /**
6302         * The current drag and drop mode.  Default: POINT
6303         * @property mode
6304         * @type int
6305         * @static
6306         */
6307        mode: 0,
6308
6309        /**
6310         * Runs method on all drag and drop objects
6311         * @method _execOnAll
6312         * @private
6313         * @static
6314         */
6315        _execOnAll: function(sMethod, args) {
6316            for (var i in this.ids) {
6317                for (var j in this.ids[i]) {
6318                    var oDD = this.ids[i][j];
6319                    if (! this.isTypeOfDD(oDD)) {
6320                        continue;
6321                    }
6322                    oDD[sMethod].apply(oDD, args);
6323                }
6324            }
6325        },
6326
6327        /**
6328         * Drag and drop initialization.  Sets up the global event handlers
6329         * @method _onLoad
6330         * @private
6331         * @static
6332         */
6333        _onLoad: function() {
6334
6335            this.init();
6336
6337            YAHOO.log("DragDropMgr onload", "info", "DragDropMgr");
6338            Event.on(document, "mouseup",   this.handleMouseUp, this, true);
6339            Event.on(document, "mousemove", this.handleMouseMove, this, true);
6340            Event.on(window,   "unload",    this._onUnload, this, true);
6341            Event.on(window,   "resize",    this._onResize, this, true);
6342            // Event.on(window,   "mouseout",    this._test);
6343
6344        },
6345
6346        /**
6347         * Reset constraints on all drag and drop objs
6348         * @method _onResize
6349         * @private
6350         * @static
6351         */
6352        _onResize: function(e) {
6353            YAHOO.log("window resize", "info", "DragDropMgr");
6354            this._execOnAll("resetConstraints", []);
6355        },
6356
6357        /**
6358         * Lock all drag and drop functionality
6359         * @method lock
6360         * @static
6361         */
6362        lock: function() { this.locked = true; },
6363
6364        /**
6365         * Unlock all drag and drop functionality
6366         * @method unlock
6367         * @static
6368         */
6369        unlock: function() { this.locked = false; },
6370
6371        /**
6372         * Is drag and drop locked?
6373         * @method isLocked
6374         * @return {boolean} True if drag and drop is locked, false otherwise.
6375         * @static
6376         */
6377        isLocked: function() { return this.locked; },
6378
6379        /**
6380         * Location cache that is set for all drag drop objects when a drag is
6381         * initiated, cleared when the drag is finished.
6382         * @property locationCache
6383         * @private
6384         * @static
6385         */
6386        locationCache: {},
6387
6388        /**
6389         * Set useCache to false if you want to force object the lookup of each
6390         * drag and drop linked element constantly during a drag.
6391         * @property useCache
6392         * @type boolean
6393         * @static
6394         */
6395        useCache: true,
6396
6397        /**
6398         * The number of pixels that the mouse needs to move after the 
6399         * mousedown before the drag is initiated.  Default=3;
6400         * @property clickPixelThresh
6401         * @type int
6402         * @static
6403         */
6404        clickPixelThresh: 3,
6405
6406        /**
6407         * The number of milliseconds after the mousedown event to initiate the
6408         * drag if we don't get a mouseup event. Default=1000
6409         * @property clickTimeThresh
6410         * @type int
6411         * @static
6412         */
6413        clickTimeThresh: 1000,
6414
6415        /**
6416         * Flag that indicates that either the drag pixel threshold or the 
6417         * mousdown time threshold has been met
6418         * @property dragThreshMet
6419         * @type boolean
6420         * @private
6421         * @static
6422         */
6423        dragThreshMet: false,
6424
6425        /**
6426         * Timeout used for the click time threshold
6427         * @property clickTimeout
6428         * @type Object
6429         * @private
6430         * @static
6431         */
6432        clickTimeout: null,
6433
6434        /**
6435         * The X position of the mousedown event stored for later use when a 
6436         * drag threshold is met.
6437         * @property startX
6438         * @type int
6439         * @private
6440         * @static
6441         */
6442        startX: 0,
6443
6444        /**
6445         * The Y position of the mousedown event stored for later use when a 
6446         * drag threshold is met.
6447         * @property startY
6448         * @type int
6449         * @private
6450         * @static
6451         */
6452        startY: 0,
6453
6454        /**
6455         * Flag to determine if the drag event was fired from the click timeout and
6456         * not the mouse move threshold.
6457         * @property fromTimeout
6458         * @type boolean
6459         * @private
6460         * @static
6461         */
6462        fromTimeout: false,
6463
6464        /**
6465         * Each DragDrop instance must be registered with the DragDropMgr.  
6466         * This is executed in DragDrop.init()
6467         * @method regDragDrop
6468         * @param {DragDrop} oDD the DragDrop object to register
6469         * @param {String} sGroup the name of the group this element belongs to
6470         * @static
6471         */
6472        regDragDrop: function(oDD, sGroup) {
6473            if (!this.initialized) { this.init(); }
6474            
6475            if (!this.ids[sGroup]) {
6476                this.ids[sGroup] = {};
6477            }
6478            this.ids[sGroup][oDD.id] = oDD;
6479        },
6480
6481        /**
6482         * Removes the supplied dd instance from the supplied group. Executed
6483         * by DragDrop.removeFromGroup, so don't call this function directly.
6484         * @method removeDDFromGroup
6485         * @private
6486         * @static
6487         */
6488        removeDDFromGroup: function(oDD, sGroup) {
6489            if (!this.ids[sGroup]) {
6490                this.ids[sGroup] = {};
6491            }
6492
6493            var obj = this.ids[sGroup];
6494            if (obj && obj[oDD.id]) {
6495                delete obj[oDD.id];
6496            }
6497        },
6498
6499        /**
6500         * Unregisters a drag and drop item.  This is executed in 
6501         * DragDrop.unreg, use that method instead of calling this directly.
6502         * @method _remove
6503         * @private
6504         * @static
6505         */
6506        _remove: function(oDD) {
6507            for (var g in oDD.groups) {
6508                if (g) {
6509                    var item = this.ids[g];
6510                    if (item && item[oDD.id]) {
6511                        delete item[oDD.id];
6512                    }
6513                }
6514                
6515            }
6516            delete this.handleIds[oDD.id];
6517        },
6518
6519        /**
6520         * Each DragDrop handle element must be registered.  This is done
6521         * automatically when executing DragDrop.setHandleElId()
6522         * @method regHandle
6523         * @param {String} sDDId the DragDrop id this element is a handle for
6524         * @param {String} sHandleId the id of the element that is the drag 
6525         * handle
6526         * @static
6527         */
6528        regHandle: function(sDDId, sHandleId) {
6529            if (!this.handleIds[sDDId]) {
6530                this.handleIds[sDDId] = {};
6531            }
6532            this.handleIds[sDDId][sHandleId] = sHandleId;
6533        },
6534
6535        /**
6536         * Utility function to determine if a given element has been 
6537         * registered as a drag drop item.
6538         * @method isDragDrop
6539         * @param {String} id the element id to check
6540         * @return {boolean} true if this element is a DragDrop item, 
6541         * false otherwise
6542         * @static
6543         */
6544        isDragDrop: function(id) {
6545            return ( this.getDDById(id) ) ? true : false;
6546        },
6547
6548        /**
6549         * Returns the drag and drop instances that are in all groups the
6550         * passed in instance belongs to.
6551         * @method getRelated
6552         * @param {DragDrop} p_oDD the obj to get related data for
6553         * @param {boolean} bTargetsOnly if true, only return targetable objs
6554         * @return {DragDrop[]} the related instances
6555         * @static
6556         */
6557        getRelated: function(p_oDD, bTargetsOnly) {
6558            var oDDs = [];
6559            for (var i in p_oDD.groups) {
6560                for (var j in this.ids[i]) {
6561                    var dd = this.ids[i][j];
6562                    if (! this.isTypeOfDD(dd)) {
6563                        continue;
6564                    }
6565                    if (!bTargetsOnly || dd.isTarget) {
6566                        oDDs[oDDs.length] = dd;
6567                    }
6568                }
6569            }
6570
6571            return oDDs;
6572        },
6573
6574        /**
6575         * Returns true if the specified dd target is a legal target for 
6576         * the specifice drag obj
6577         * @method isLegalTarget
6578         * @param {DragDrop} the drag obj
6579         * @param {DragDrop} the target
6580         * @return {boolean} true if the target is a legal target for the 
6581         * dd obj
6582         * @static
6583         */
6584        isLegalTarget: function (oDD, oTargetDD) {
6585            var targets = this.getRelated(oDD, true);
6586            for (var i=0, len=targets.length;i<len;++i) {
6587                if (targets[i].id == oTargetDD.id) {
6588                    return true;
6589                }
6590            }
6591
6592            return false;
6593        },
6594
6595        /**
6596         * My goal is to be able to transparently determine if an object is
6597         * typeof DragDrop, and the exact subclass of DragDrop.  typeof 
6598         * returns "object", oDD.constructor.toString() always returns
6599         * "DragDrop" and not the name of the subclass.  So for now it just
6600         * evaluates a well-known variable in DragDrop.
6601         * @method isTypeOfDD
6602         * @param {Object} the object to evaluate
6603         * @return {boolean} true if typeof oDD = DragDrop
6604         * @static
6605         */
6606        isTypeOfDD: function (oDD) {
6607            return (oDD && oDD.__ygDragDrop);
6608        },
6609
6610        /**
6611         * Utility function to determine if a given element has been 
6612         * registered as a drag drop handle for the given Drag Drop object.
6613         * @method isHandle
6614         * @param {String} id the element id to check
6615         * @return {boolean} true if this element is a DragDrop handle, false 
6616         * otherwise
6617         * @static
6618         */
6619        isHandle: function(sDDId, sHandleId) {
6620            return ( this.handleIds[sDDId] && 
6621                            this.handleIds[sDDId][sHandleId] );
6622        },
6623
6624        /**
6625         * Returns the DragDrop instance for a given id
6626         * @method getDDById
6627         * @param {String} id the id of the DragDrop object
6628         * @return {DragDrop} the drag drop object, null if it is not found
6629         * @static
6630         */
6631        getDDById: function(id) {
6632            for (var i in this.ids) {
6633                if (this.ids[i][id]) {
6634                    return this.ids[i][id];
6635                }
6636            }
6637            return null;
6638        },
6639
6640        /**
6641         * Fired after a registered DragDrop object gets the mousedown event.
6642         * Sets up the events required to track the object being dragged
6643         * @method handleMouseDown
6644         * @param {Event} e the event
6645         * @param oDD the DragDrop object being dragged
6646         * @private
6647         * @static
6648         */
6649        handleMouseDown: function(e, oDD) {
6650            //this._activateShim();
6651
6652            this.currentTarget = YAHOO.util.Event.getTarget(e);
6653
6654            this.dragCurrent = oDD;
6655
6656            var el = oDD.getEl();
6657
6658            // track start position
6659            this.startX = YAHOO.util.Event.getPageX(e);
6660            this.startY = YAHOO.util.Event.getPageY(e);
6661
6662            this.deltaX = this.startX - el.offsetLeft;
6663            this.deltaY = this.startY - el.offsetTop;
6664
6665            this.dragThreshMet = false;
6666
6667            this.clickTimeout = setTimeout( 
6668                    function() { 
6669                        var DDM = YAHOO.util.DDM;
6670                        DDM.startDrag(DDM.startX, DDM.startY);
6671                        DDM.fromTimeout = true;
6672                    }, 
6673                    this.clickTimeThresh );
6674        },
6675
6676        /**
6677         * Fired when either the drag pixel threshold or the mousedown hold 
6678         * time threshold has been met.
6679         * @method startDrag
6680         * @param x {int} the X position of the original mousedown
6681         * @param y {int} the Y position of the original mousedown
6682         * @static
6683         */
6684        startDrag: function(x, y) {
6685            if (this.dragCurrent && this.dragCurrent.useShim) {
6686                this._shimState = this.useShim;
6687                this.useShim = true;
6688            }
6689            this._activateShim();
6690            YAHOO.log("firing drag start events", "info", "DragDropMgr");
6691            clearTimeout(this.clickTimeout);
6692            var dc = this.dragCurrent;
6693            if (dc && dc.events.b4StartDrag) {
6694                dc.b4StartDrag(x, y);
6695                dc.fireEvent('b4StartDragEvent', { x: x, y: y });
6696            }
6697            if (dc && dc.events.startDrag) {
6698                dc.startDrag(x, y);
6699                dc.fireEvent('startDragEvent', { x: x, y: y });
6700            }
6701            this.dragThreshMet = true;
6702        },
6703
6704        /**
6705         * Internal function to handle the mouseup event.  Will be invoked 
6706         * from the context of the document.
6707         * @method handleMouseUp
6708         * @param {Event} e the event
6709         * @private
6710         * @static
6711         */
6712        handleMouseUp: function(e) {
6713            if (this.dragCurrent) {
6714                clearTimeout(this.clickTimeout);
6715
6716                if (this.dragThreshMet) {
6717                    YAHOO.log("mouseup detected - completing drag", "info", "DragDropMgr");
6718                    if (this.fromTimeout) {
6719                        YAHOO.log('fromTimeout is true (mouse didn\'t move), call handleMouseMove so we can get the dragOver event', 'info', 'DragDropMgr');
6720                        this.fromTimeout = false;
6721                        this.handleMouseMove(e);
6722                    }
6723                    this.fromTimeout = false;
6724                    this.fireEvents(e, true);
6725                } else {
6726                    YAHOO.log("drag threshold not met", "info", "DragDropMgr");
6727                }
6728
6729                this.stopDrag(e);
6730
6731                this.stopEvent(e);
6732            }
6733        },
6734
6735        /**
6736         * Utility to stop event propagation and event default, if these 
6737         * features are turned on.
6738         * @method stopEvent
6739         * @param {Event} e the event as returned by this.getEvent()
6740         * @static
6741         */
6742        stopEvent: function(e) {
6743            if (this.stopPropagation) {
6744                YAHOO.util.Event.stopPropagation(e);
6745            }
6746
6747            if (this.preventDefault) {
6748                YAHOO.util.Event.preventDefault(e);
6749            }
6750        },
6751
6752        /** 
6753         * Ends the current drag, cleans up the state, and fires the endDrag
6754         * and mouseUp events.  Called internally when a mouseup is detected
6755         * during the drag.  Can be fired manually during the drag by passing
6756         * either another event (such as the mousemove event received in onDrag)
6757         * or a fake event with pageX and pageY defined (so that endDrag and
6758         * onMouseUp have usable position data.).  Alternatively, pass true
6759         * for the silent parameter so that the endDrag and onMouseUp events
6760         * are skipped (so no event data is needed.)
6761         *
6762         * @method stopDrag
6763         * @param {Event} e the mouseup event, another event (or a fake event) 
6764         *                  with pageX and pageY defined, or nothing if the 
6765         *                  silent parameter is true
6766         * @param {boolean} silent skips the enddrag and mouseup events if true
6767         * @static
6768         */
6769        stopDrag: function(e, silent) {
6770            // YAHOO.log("mouseup - removing event handlers");
6771            var dc = this.dragCurrent;
6772            // Fire the drag end event for the item that was dragged
6773            if (dc && !silent) {
6774                if (this.dragThreshMet) {
6775                    YAHOO.log("firing endDrag events", "info", "DragDropMgr");
6776                    if (dc.events.b4EndDrag) {
6777                        dc.b4EndDrag(e);
6778                        dc.fireEvent('b4EndDragEvent', { e: e });
6779                    }
6780                    if (dc.events.endDrag) {
6781                        dc.endDrag(e);
6782                        dc.fireEvent('endDragEvent', { e: e });
6783                    }
6784                }
6785                if (dc.events.mouseUp) {
6786                    YAHOO.log("firing dragdrop onMouseUp event", "info", "DragDropMgr");
6787                    dc.onMouseUp(e);
6788                    dc.fireEvent('mouseUpEvent', { e: e });
6789                }
6790            }
6791
6792            if (this._shimActive) {
6793                this._deactivateShim();
6794                if (this.dragCurrent && this.dragCurrent.useShim) {
6795                    this.useShim = this._shimState;
6796                    this._shimState = false;
6797                }
6798            }
6799
6800            this.dragCurrent = null;
6801            this.dragOvers = {};
6802        },
6803
6804        /** 
6805         * Internal function to handle the mousemove event.  Will be invoked 
6806         * from the context of the html element.
6807         *
6808         * @TODO figure out what we can do about mouse events lost when the 
6809         * user drags objects beyond the window boundary.  Currently we can 
6810         * detect this in internet explorer by verifying that the mouse is 
6811         * down during the mousemove event.  Firefox doesn't give us the 
6812         * button state on the mousemove event.
6813         * @method handleMouseMove
6814         * @param {Event} e the event
6815         * @private
6816         * @static
6817         */
6818        handleMouseMove: function(e) {
6819            //YAHOO.log("handlemousemove");
6820
6821            var dc = this.dragCurrent;
6822            if (dc) {
6823                // YAHOO.log("no current drag obj");
6824
6825                // var button = e.which || e.button;
6826                // YAHOO.log("which: " + e.which + ", button: "+ e.button);
6827
6828                // check for IE < 9 mouseup outside of page boundary
6829                if (YAHOO.env.ua.ie && (YAHOO.env.ua.ie < 9) && !e.button) {
6830                    YAHOO.log("button failure", "info", "DragDropMgr");
6831                    this.stopEvent(e);
6832                    return this.handleMouseUp(e);
6833                } else {
6834                    if (e.clientX < 0 || e.clientY < 0) {
6835                        //This will stop the element from leaving the viewport in FF, Opera & Safari
6836                        //Not turned on yet
6837                        //YAHOO.log("Either clientX or clientY is negative, stop the event.", "info", "DragDropMgr");
6838                        //this.stopEvent(e);
6839                        //return false;
6840                    }
6841                }
6842
6843                if (!this.dragThreshMet) {
6844                    var diffX = Math.abs(this.startX - YAHOO.util.Event.getPageX(e));
6845                    var diffY = Math.abs(this.startY - YAHOO.util.Event.getPageY(e));
6846                    // YAHOO.log("diffX: " + diffX + "diffY: " + diffY);
6847                    if (diffX > this.clickPixelThresh || 
6848                                diffY > this.clickPixelThresh) {
6849                        YAHOO.log("pixel threshold met", "info", "DragDropMgr");
6850                        this.startDrag(this.startX, this.startY);
6851                    }
6852                }
6853
6854                if (this.dragThreshMet) {
6855                    if (dc && dc.events.b4Drag) {
6856                        dc.b4Drag(e);
6857                        dc.fireEvent('b4DragEvent', { e: e});
6858                    }
6859                    if (dc && dc.events.drag) {
6860                        dc.onDrag(e);
6861                        dc.fireEvent('dragEvent', { e: e});
6862                    }
6863                    if (dc) {
6864                        this.fireEvents(e, false);
6865                    }
6866                }
6867
6868                this.stopEvent(e);
6869            }
6870        },
6871        
6872        /**
6873         * Iterates over all of the DragDrop elements to find ones we are 
6874         * hovering over or dropping on
6875         * @method fireEvents
6876         * @param {Event} e the event
6877         * @param {boolean} isDrop is this a drop op or a mouseover op?
6878         * @private
6879         * @static
6880         */
6881        fireEvents: function(e, isDrop) {
6882            var dc = this.dragCurrent;
6883
6884            // If the user did the mouse up outside of the window, we could 
6885            // get here even though we have ended the drag.
6886            // If the config option dragOnly is true, bail out and don't fire the events
6887            if (!dc || dc.isLocked() || dc.dragOnly) {
6888                return;
6889            }
6890
6891            var x = YAHOO.util.Event.getPageX(e),
6892                y = YAHOO.util.Event.getPageY(e),
6893                pt = new YAHOO.util.Point(x,y),
6894                pos = dc.getTargetCoord(pt.x, pt.y),
6895                el = dc.getDragEl(),
6896                events = ['out', 'over', 'drop', 'enter'],
6897                curRegion = new YAHOO.util.Region( pos.y, 
6898                                               pos.x + el.offsetWidth,
6899                                               pos.y + el.offsetHeight, 
6900                                               pos.x ),
6901            
6902                oldOvers = [], // cache the previous dragOver array
6903                inGroupsObj  = {},
6904                b4Results = {},
6905                inGroups  = [],
6906                data = {
6907                    outEvts: [],
6908                    overEvts: [],
6909                    dropEvts: [],
6910                    enterEvts: []
6911                };
6912
6913
6914            // Check to see if the object(s) we were hovering over is no longer 
6915            // being hovered over so we can fire the onDragOut event
6916            for (var i in this.dragOvers) {
6917
6918                var ddo = this.dragOvers[i];
6919
6920                if (! this.isTypeOfDD(ddo)) {
6921                    continue;
6922                }
6923                if (! this.isOverTarget(pt, ddo, this.mode, curRegion)) {
6924                    data.outEvts.push( ddo );
6925                }
6926
6927                oldOvers[i] = true;
6928                delete this.dragOvers[i];
6929            }
6930
6931            for (var sGroup in dc.groups) {
6932                // YAHOO.log("Processing group " + sGroup);
6933                
6934                if ("string" != typeof sGroup) {
6935                    continue;
6936                }
6937
6938                for (i in this.ids[sGroup]) {
6939                    var oDD = this.ids[sGroup][i];
6940                    if (! this.isTypeOfDD(oDD)) {
6941                        continue;
6942                    }
6943
6944                    if (oDD.isTarget && !oDD.isLocked() && oDD != dc) {
6945                        if (this.isOverTarget(pt, oDD, this.mode, curRegion)) {
6946                            inGroupsObj[sGroup] = true;
6947                            // look for drop interactions
6948                            if (isDrop) {
6949                                data.dropEvts.push( oDD );
6950                            // look for drag enter and drag over interactions
6951                            } else {
6952
6953                                // initial drag over: dragEnter fires
6954                                if (!oldOvers[oDD.id]) {
6955                                    data.enterEvts.push( oDD );
6956                                // subsequent drag overs: dragOver fires
6957                                } else {
6958                                    data.overEvts.push( oDD );
6959                                }
6960
6961                                this.dragOvers[oDD.id] = oDD;
6962                            }
6963                        }
6964                    }
6965                }
6966            }
6967
6968            this.interactionInfo = {
6969                out:       data.outEvts,
6970                enter:     data.enterEvts,
6971                over:      data.overEvts,
6972                drop:      data.dropEvts,
6973                point:     pt,
6974                draggedRegion:    curRegion,
6975                sourceRegion: this.locationCache[dc.id],
6976                validDrop: isDrop
6977            };
6978
6979            
6980            for (var inG in inGroupsObj) {
6981                inGroups.push(inG);
6982            }
6983
6984            // notify about a drop that did not find a target
6985            if (isDrop && !data.dropEvts.length) {
6986                YAHOO.log(dc.id + " dropped, but not on a target", "info", "DragDropMgr");
6987                this.interactionInfo.validDrop = false;
6988                if (dc.events.invalidDrop) {
6989                    dc.onInvalidDrop(e);
6990                    dc.fireEvent('invalidDropEvent', { e: e });
6991                }
6992            }
6993            for (i = 0; i < events.length; i++) {
6994                var tmp = null;
6995                if (data[events[i] + 'Evts']) {
6996                    tmp = data[events[i] + 'Evts'];
6997                }
6998                if (tmp && tmp.length) {
6999                    var type = events[i].charAt(0).toUpperCase() + events[i].substr(1),
7000                        ev = 'onDrag' + type,
7001                        b4 = 'b4Drag' + type,
7002                        cev = 'drag' + type + 'Event',
7003                        check = 'drag' + type;
7004                    if (this.mode) {
7005                        YAHOO.log(dc.id + ' ' + ev + ': ' + tmp, "info", "DragDropMgr");
7006                        if (dc.events[b4]) {
7007                            dc[b4](e, tmp, inGroups);
7008                            b4Results[ev] = dc.fireEvent(b4 + 'Event', { event: e, info: tmp, group: inGroups });
7009                            
7010                        }
7011                        if (dc.events[check] && (b4Results[ev] !== false)) {
7012                            dc[ev](e, tmp, inGroups);
7013                            dc.fireEvent(cev, { event: e, info: tmp, group: inGroups });
7014                        }
7015                    } else {
7016                        for (var b = 0, len = tmp.length; b < len; ++b) {
7017                            YAHOO.log(dc.id + ' ' + ev + ': ' + tmp[b].id, "info", "DragDropMgr");
7018                            if (dc.events[b4]) {
7019                                dc[b4](e, tmp[b].id, inGroups[0]);
7020                                b4Results[ev] = dc.fireEvent(b4 + 'Event', { event: e, info: tmp[b].id, group: inGroups[0] });
7021                            }
7022                            if (dc.events[check] && (b4Results[ev] !== false)) {
7023                                dc[ev](e, tmp[b].id, inGroups[0]);
7024                                dc.fireEvent(cev, { event: e, info: tmp[b].id, group: inGroups[0] });
7025                            }
7026                        }
7027                    }
7028                }
7029            }
7030        },
7031
7032        /**
7033         * Helper function for getting the best match from the list of drag 
7034         * and drop objects returned by the drag and drop events when we are 
7035         * in INTERSECT mode.  It returns either the first object that the 
7036         * cursor is over, or the object that has the greatest overlap with 
7037         * the dragged element.
7038         * @method getBestMatch
7039         * @param  {DragDrop[]} dds The array of drag and drop objects 
7040         * targeted
7041         * @return {DragDrop}       The best single match
7042         * @static
7043         */
7044        getBestMatch: function(dds) {
7045            var winner = null;
7046
7047            var len = dds.length;
7048
7049            if (len == 1) {
7050                winner = dds[0];
7051            } else {
7052                // Loop through the targeted items
7053                for (var i=0; i<len; ++i) {
7054                    var dd = dds[i];
7055                    // If the cursor is over the object, it wins.  If the 
7056                    // cursor is over multiple matches, the first one we come
7057                    // to wins.
7058                    if (this.mode == this.INTERSECT && dd.cursorIsOver) {
7059                        winner = dd;
7060                        break;
7061                    // Otherwise the object with the most overlap wins
7062                    } else {
7063                        if (!winner || !winner.overlap || (dd.overlap &&
7064                            winner.overlap.getArea() < dd.overlap.getArea())) {
7065                            winner = dd;
7066                        }
7067                    }
7068                }
7069            }
7070
7071            return winner;
7072        },
7073
7074        /**
7075         * Refreshes the cache of the top-left and bottom-right points of the 
7076         * drag and drop objects in the specified group(s).  This is in the
7077         * format that is stored in the drag and drop instance, so typical 
7078         * usage is:
7079         * <code>
7080         * YAHOO.util.DragDropMgr.refreshCache(ddinstance.groups);
7081         * </code>
7082         * Alternatively:
7083         * <code>
7084         * YAHOO.util.DragDropMgr.refreshCache({group1:true, group2:true});
7085         * </code>
7086         * @TODO this really should be an indexed array.  Alternatively this
7087         * method could accept both.
7088         * @method refreshCache
7089         * @param {Object} groups an associative array of groups to refresh
7090         * @static
7091         */
7092        refreshCache: function(groups) {
7093            YAHOO.log("refreshing element location cache", "info", "DragDropMgr");
7094
7095            // refresh everything if group array is not provided
7096            var g = groups || this.ids;
7097
7098            for (var sGroup in g) {
7099                if ("string" != typeof sGroup) {
7100                    continue;
7101                }
7102                for (var i in this.ids[sGroup]) {
7103                    var oDD = this.ids[sGroup][i];
7104
7105                    if (this.isTypeOfDD(oDD)) {
7106                        var loc = this.getLocation(oDD);
7107                        if (loc) {
7108                            this.locationCache[oDD.id] = loc;
7109                        } else {
7110                            delete this.locationCache[oDD.id];
7111YAHOO.log("Could not get the loc for " + oDD.id, "warn", "DragDropMgr");
7112                        }
7113                    }
7114                }
7115            }
7116        },
7117
7118        /**
7119         * This checks to make sure an element exists and is in the DOM.  The
7120         * main purpose is to handle cases where innerHTML is used to remove
7121         * drag and drop objects from the DOM.  IE provides an 'unspecified
7122         * error' when trying to access the offsetParent of such an element
7123         * @method verifyEl
7124         * @param {HTMLElement} el the element to check
7125         * @return {boolean} true if the element looks usable
7126         * @static
7127         */
7128        verifyEl: function(el) {
7129            try {
7130                if (el) {
7131                    var parent = el.offsetParent;
7132                    if (parent) {
7133                        return true;
7134                    }
7135                }
7136            } catch(e) {
7137                YAHOO.log("detected problem with an element", "info", "DragDropMgr");
7138            }
7139
7140            return false;
7141        },
7142        
7143        /**
7144         * Returns a Region object containing the drag and drop element's position
7145         * and size, including the padding configured for it
7146         * @method getLocation
7147         * @param {DragDrop} oDD the drag and drop object to get the 
7148         *                       location for
7149         * @return {YAHOO.util.Region} a Region object representing the total area
7150         *                             the element occupies, including any padding
7151         *                             the instance is configured for.
7152         * @static
7153         */
7154        getLocation: function(oDD) {
7155            if (! this.isTypeOfDD(oDD)) {
7156                YAHOO.log(oDD + " is not a DD obj", "info", "DragDropMgr");
7157                return null;
7158            }
7159
7160            var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
7161
7162            try {
7163                pos= YAHOO.util.Dom.getXY(el);
7164            } catch (e) { }
7165
7166            if (!pos) {
7167                YAHOO.log("getXY failed", "info", "DragDropMgr");
7168                return null;
7169            }
7170
7171            x1 = pos[0];
7172            x2 = x1 + el.offsetWidth;
7173            y1 = pos[1];
7174            y2 = y1 + el.offsetHeight;
7175
7176            t = y1 - oDD.padding[0];
7177            r = x2 + oDD.padding[1];
7178            b = y2 + oDD.padding[2];
7179            l = x1 - oDD.padding[3];
7180
7181            return new YAHOO.util.Region( t, r, b, l );
7182        },
7183
7184        /**
7185         * Checks the cursor location to see if it over the target
7186         * @method isOverTarget
7187         * @param {YAHOO.util.Point} pt The point to evaluate
7188         * @param {DragDrop} oTarget the DragDrop object we are inspecting
7189         * @param {boolean} intersect true if we are in intersect mode
7190         * @param {YAHOO.util.Region} pre-cached location of the dragged element
7191         * @return {boolean} true if the mouse is over the target
7192         * @private
7193         * @static
7194         */
7195        isOverTarget: function(pt, oTarget, intersect, curRegion) {
7196            // use cache if available
7197            var loc = this.locationCache[oTarget.id];
7198            if (!loc || !this.useCache) {
7199                YAHOO.log("cache not populated", "info", "DragDropMgr");
7200                loc = this.getLocation(oTarget);
7201                this.locationCache[oTarget.id] = loc;
7202
7203                YAHOO.log("cache: " + loc, "info", "DragDropMgr");
7204            }
7205
7206            if (!loc) {
7207                YAHOO.log("could not get the location of the element", "info", "DragDropMgr");
7208                return false;
7209            }
7210
7211            //YAHOO.log("loc: " + loc + ", pt: " + pt);
7212            oTarget.cursorIsOver = loc.contains( pt );
7213
7214            // DragDrop is using this as a sanity check for the initial mousedown
7215            // in this case we are done.  In POINT mode, if the drag obj has no
7216            // contraints, we are done. Otherwise we need to evaluate the 
7217            // region the target as occupies to determine if the dragged element
7218            // overlaps with it.
7219            
7220            var dc = this.dragCurrent;
7221            if (!dc || (!intersect && !dc.constrainX && !dc.constrainY)) {
7222
7223                //if (oTarget.cursorIsOver) {
7224                    //YAHOO.log("over " + oTarget + ", " + loc + ", " + pt, "warn");
7225                //}
7226                return oTarget.cursorIsOver;
7227            }
7228
7229            oTarget.overlap = null;
7230
7231
7232            // Get the current location of the drag element, this is the
7233            // location of the mouse event less the delta that represents
7234            // where the original mousedown happened on the element.  We
7235            // need to consider constraints and ticks as well.
7236
7237            if (!curRegion) {
7238                var pos = dc.getTargetCoord(pt.x, pt.y);
7239                var el = dc.getDragEl();
7240                curRegion = new YAHOO.util.Region( pos.y, 
7241                                                   pos.x + el.offsetWidth,
7242                                                   pos.y + el.offsetHeight, 
7243                                                   pos.x );
7244            }
7245
7246            var overlap = curRegion.intersect(loc);
7247
7248            if (overlap) {
7249                oTarget.overlap = overlap;
7250                return (intersect) ? true : oTarget.cursorIsOver;
7251            } else {
7252                return false;
7253            }
7254        },
7255
7256        /**
7257         * unload event handler
7258         * @method _onUnload
7259         * @private
7260         * @static
7261         */
7262        _onUnload: function(e, me) {
7263            this.unregAll();
7264        },
7265
7266        /**
7267         * Cleans up the drag and drop events and objects.
7268         * @method unregAll
7269         * @private
7270         * @static
7271         */
7272        unregAll: function() {
7273            YAHOO.log("unregister all", "info", "DragDropMgr");
7274
7275            if (this.dragCurrent) {
7276                this.stopDrag();
7277                this.dragCurrent = null;
7278            }
7279
7280            this._execOnAll("unreg", []);
7281
7282            //for (var i in this.elementCache) {
7283                //delete this.elementCache[i];
7284            //}
7285            //this.elementCache = {};
7286
7287            this.ids = {};
7288        },
7289
7290        /**
7291         * A cache of DOM elements
7292         * @property elementCache
7293         * @private
7294         * @static
7295         * @deprecated elements are not cached now
7296         */
7297        elementCache: {},
7298        
7299        /**
7300         * Get the wrapper for the DOM element specified
7301         * @method getElWrapper
7302         * @param {String} id the id of the element to get
7303         * @return {YAHOO.util.DDM.ElementWrapper} the wrapped element
7304         * @private
7305         * @deprecated This wrapper isn't that useful
7306         * @static
7307         */
7308        getElWrapper: function(id) {
7309            var oWrapper = this.elementCache[id];
7310            if (!oWrapper || !oWrapper.el) {
7311                oWrapper = this.elementCache[id] = 
7312                    new this.ElementWrapper(YAHOO.util.Dom.get(id));
7313            }
7314            return oWrapper;
7315        },
7316
7317        /**
7318         * Returns the actual DOM element
7319         * @method getElement
7320         * @param {String} id the id of the elment to get
7321         * @return {Object} The element
7322         * @deprecated use YAHOO.util.Dom.get instead
7323         * @static
7324         */
7325        getElement: function(id) {
7326            return YAHOO.util.Dom.get(id);
7327        },
7328        
7329        /**
7330         * Returns the style property for the DOM element (i.e., 
7331         * document.getElById(id).style)
7332         * @method getCss
7333         * @param {String} id the id of the elment to get
7334         * @return {Object} The style property of the element
7335         * @deprecated use YAHOO.util.Dom instead
7336         * @static
7337         */
7338        getCss: function(id) {
7339            var el = YAHOO.util.Dom.get(id);
7340            return (el) ? el.style : null;
7341        },
7342
7343        /**
7344         * Inner class for cached elements
7345         * @class DragDropMgr.ElementWrapper
7346         * @for DragDropMgr
7347         * @private
7348         * @deprecated
7349         */
7350        ElementWrapper: function(el) {
7351                /**
7352                 * The element
7353                 * @property el
7354                 */
7355                this.el = el || null;
7356                /**
7357                 * The element id
7358                 * @property id
7359                 */
7360                this.id = this.el && el.id;
7361                /**
7362                 * A reference to the style property
7363                 * @property css
7364                 */
7365                this.css = this.el && el.style;
7366            },
7367
7368        /**
7369         * Returns the X position of an html element
7370         * @method getPosX
7371         * @param el the element for which to get the position
7372         * @return {int} the X coordinate
7373         * @for DragDropMgr
7374         * @deprecated use YAHOO.util.Dom.getX instead
7375         * @static
7376         */
7377        getPosX: function(el) {
7378            return YAHOO.util.Dom.getX(el);
7379        },
7380
7381        /**
7382         * Returns the Y position of an html element
7383         * @method getPosY
7384         * @param el the element for which to get the position
7385         * @return {int} the Y coordinate
7386         * @deprecated use YAHOO.util.Dom.getY instead
7387         * @static
7388         */
7389        getPosY: function(el) {
7390            return YAHOO.util.Dom.getY(el); 
7391        },
7392
7393        /**
7394         * Swap two nodes.  In IE, we use the native method, for others we 
7395         * emulate the IE behavior
7396         * @method swapNode
7397         * @param n1 the first node to swap
7398         * @param n2 the other node to swap
7399         * @static
7400         */
7401        swapNode: function(n1, n2) {
7402            if (n1.swapNode) {
7403                n1.swapNode(n2);
7404            } else {
7405                var p = n2.parentNode;
7406                var s = n2.nextSibling;
7407
7408                if (s == n1) {
7409                    p.insertBefore(n1, n2);
7410                } else if (n2 == n1.nextSibling) {
7411                    p.insertBefore(n2, n1);
7412                } else {
7413                    n1.parentNode.replaceChild(n2, n1);
7414                    p.insertBefore(n1, s);
7415                }
7416            }
7417        },
7418
7419        /**
7420         * Returns the current scroll position
7421         * @method getScroll
7422         * @private
7423         * @static
7424         */
7425        getScroll: function () {
7426            var t, l, dde=document.documentElement, db=document.body;
7427            if (dde && (dde.scrollTop || dde.scrollLeft)) {
7428                t = dde.scrollTop;
7429                l = dde.scrollLeft;
7430            } else if (db) {
7431                t = db.scrollTop;
7432                l = db.scrollLeft;
7433            } else {
7434                YAHOO.log("could not get scroll property", "info", "DragDropMgr");
7435            }
7436            return { top: t, left: l };
7437        },
7438
7439        /**
7440         * Returns the specified element style property
7441         * @method getStyle
7442         * @param {HTMLElement} el          the element
7443         * @param {string}      styleProp   the style property
7444         * @return {string} The value of the style property
7445         * @deprecated use YAHOO.util.Dom.getStyle
7446         * @static
7447         */
7448        getStyle: function(el, styleProp) {
7449            return YAHOO.util.Dom.getStyle(el, styleProp);
7450        },
7451
7452        /**
7453         * Gets the scrollTop
7454         * @method getScrollTop
7455         * @return {int} the document's scrollTop
7456         * @static
7457         */
7458        getScrollTop: function () { return this.getScroll().top; },
7459
7460        /**
7461         * Gets the scrollLeft
7462         * @method getScrollLeft
7463         * @return {int} the document's scrollTop
7464         * @static
7465         */
7466        getScrollLeft: function () { return this.getScroll().left; },
7467
7468        /**
7469         * Sets the x/y position of an element to the location of the
7470         * target element.
7471         * @method moveToEl
7472         * @param {HTMLElement} moveEl      The element to move
7473         * @param {HTMLElement} targetEl    The position reference element
7474         * @static
7475         */
7476        moveToEl: function (moveEl, targetEl) {
7477            var aCoord = YAHOO.util.Dom.getXY(targetEl);
7478            YAHOO.log("moveToEl: " + aCoord, "info", "DragDropMgr");
7479            YAHOO.util.Dom.setXY(moveEl, aCoord);
7480        },
7481
7482        /**
7483         * Gets the client height
7484         * @method getClientHeight
7485         * @return {int} client height in px
7486         * @deprecated use YAHOO.util.Dom.getViewportHeight instead
7487         * @static
7488         */
7489        getClientHeight: function() {
7490            return YAHOO.util.Dom.getViewportHeight();
7491        },
7492
7493        /**
7494         * Gets the client width
7495         * @method getClientWidth
7496         * @return {int} client width in px
7497         * @deprecated use YAHOO.util.Dom.getViewportWidth instead
7498         * @static
7499         */
7500        getClientWidth: function() {
7501            return YAHOO.util.Dom.getViewportWidth();
7502        },
7503
7504        /**
7505         * Numeric array sort function
7506         * @method numericSort
7507         * @static
7508         */
7509        numericSort: function(a, b) { return (a - b); },
7510
7511        /**
7512         * Internal counter
7513         * @property _timeoutCount
7514         * @private
7515         * @static
7516         */
7517        _timeoutCount: 0,
7518
7519        /**
7520         * Trying to make the load order less important.  Without this we get
7521         * an error if this file is loaded before the Event Utility.
7522         * @method _addListeners
7523         * @private
7524         * @static
7525         */
7526        _addListeners: function() {
7527            var DDM = YAHOO.util.DDM;
7528            if ( YAHOO.util.Event && document ) {
7529                DDM._onLoad();
7530            } else {
7531                if (DDM._timeoutCount > 2000) {
7532                    YAHOO.log("DragDrop requires the Event Utility", "error", "DragDropMgr");
7533                } else {
7534                    setTimeout(DDM._addListeners, 10);
7535                    if (document && document.body) {
7536                        DDM._timeoutCount += 1;
7537                    }
7538                }
7539            }
7540        },
7541
7542        /**
7543         * Recursively searches the immediate parent and all child nodes for 
7544         * the handle element in order to determine wheter or not it was 
7545         * clicked.
7546         * @method handleWasClicked
7547         * @param node the html element to inspect
7548         * @static
7549         */
7550        handleWasClicked: function(node, id) {
7551            if (this.isHandle(id, node.id)) {
7552                YAHOO.log("clicked node is a handle", "info", "DragDropMgr");
7553                return true;
7554            } else {
7555                // check to see if this is a text node child of the one we want
7556                var p = node.parentNode;
7557                // YAHOO.log("p: " + p);
7558
7559                while (p) {
7560                    if (this.isHandle(id, p.id)) {
7561                        return true;
7562                    } else {
7563                        YAHOO.log(p.id + " is not a handle", "info", "DragDropMgr");
7564                        p = p.parentNode;
7565                    }
7566                }
7567            }
7568
7569            return false;
7570        }
7571
7572    };
7573
7574}();
7575
7576// shorter alias, save a few bytes
7577YAHOO.util.DDM = YAHOO.util.DragDropMgr;
7578YAHOO.util.DDM._addListeners();
7579
7580}
7581
7582(function() {
7583
7584var Event=YAHOO.util.Event; 
7585var Dom=YAHOO.util.Dom;
7586
7587/**
7588 * Defines the interface and base operation of items that that can be 
7589 * dragged or can be drop targets.  It was designed to be extended, overriding
7590 * the event handlers for startDrag, onDrag, onDragOver, onDragOut.
7591 * Up to three html elements can be associated with a DragDrop instance:
7592 * <ul>
7593 * <li>linked element: the element that is passed into the constructor.
7594 * This is the element which defines the boundaries for interaction with 
7595 * other DragDrop objects.</li>
7596 * <li>
7596handle element(s): The drag operation only occurs if the element that 
7597 * was clicked matches a handle element.  By default this is the linked 
7598 * element, but there are times that you will want only a portion of the 
7599 * linked element to initiate the drag operation, and the setHandleElId() 
7600 * method provides a way to define this.</li>
7601 * <li>drag element: this represents an the element that would be moved along
7602 * with the cursor during a drag operation.  By default, this is the linked
7603 * element itself as in {@link YAHOO.util.DD}.  setDragElId() lets you define
7604 * a separate element that would be moved, as in {@link YAHOO.util.DDProxy}
7605 * </li>
7606 * </ul>
7607 * This class should not be instantiated until the onload event to ensure that
7608 * the associated elements are available.
7609 * The following would define a DragDrop obj that would interact with any 
7610 * other DragDrop obj in the "group1" group:
7611 * <pre>
7612 *  dd = new YAHOO.util.DragDrop("div1", "group1");
7613 * </pre>
7614 * Since none of the event handlers have been implemented, nothing would 
7615 * actually happen if you were to run the code above.  Normally you would 
7616 * override this class or one of the default implementations, but you can 
7617 * also override the methods you want on an instance of the class...
7618 * <pre>
7619 *  dd.onDragDrop = function(e, id) {
7620 *  &nbsp;&nbsp;alert("dd was dropped on " + id);
7621 *  }
7622 * </pre>
7623 * @namespace YAHOO.util
7624 * @class DragDrop
7625 * @constructor
7626 * @param {String} id of the element that is linked to this instance
7627 * @param {String} sGroup the group of related DragDrop objects
7628 * @param {object} config an object containing configurable attributes
7629 *                Valid properties for DragDrop: 
7630 *                    padding, isTarget, maintainOffset, primaryButtonOnly,
7631 */
7632YAHOO.util.DragDrop = function(id, sGroup, config) {
7633    if (id) {
7634        this.init(id, sGroup, config); 
7635    }
7636};
7637
7638YAHOO.util.DragDrop.prototype = {
7639    /**
7640     * An Object Literal containing the events that we will be using: mouseDown, b4MouseDown, mouseUp, b4StartDrag, startDrag, b4EndDrag, endDrag, mouseUp, drag, b4Drag, invalidDrop, b4DragOut, dragOut, dragEnter, b4DragOver, dragOver, b4DragDrop, dragDrop
7641     * By setting any of these to false, then event will not be fired.
7642     * @property events
7643     * @type object
7644     */
7645    events: null,
7646    /**
7647    * @method on
7648    * @description Shortcut for EventProvider.subscribe, see <a href="YAHOO.util.EventProvider.html#subscribe">YAHOO.util.EventProvider.subscribe</a>
7649    */
7650    on: function() {
7651        this.subscribe.apply(this, arguments);
7652    },
7653    /**
7654     * The id of the element associated with this object.  This is what we 
7655     * refer to as the "linked element" because the size and position of 
7656     * this element is used to determine when the drag and drop objects have 
7657     * interacted.
7658     * @property id
7659     * @type String
7660     */
7661    id: null,
7662
7663    /**
7664     * Configuration attributes passed into the constructor
7665     * @property config
7666     * @type object
7667     */
7668    config: null,
7669
7670    /**
7671     * The id of the element that will be dragged.  By default this is same 
7672     * as the linked element , but could be changed to another element. Ex: 
7673     * YAHOO.util.DDProxy
7674     * @property dragElId
7675     * @type String
7676     * @private
7677     */
7678    dragElId: null, 
7679
7680    /**
7681     * the id of the element that initiates the drag operation.  By default 
7682     * this is the linked element, but could be changed to be a child of this
7683     * element.  This lets us do things like only starting the drag when the 
7684     * header element within the linked html element is clicked.
7685     * @property handleElId
7686     * @type String
7687     * @private
7688     */
7689    handleElId: null, 
7690
7691    /**
7692     * An associative array of HTML tags that will be ignored if clicked.
7693     * @property invalidHandleTypes
7694     * @type {string: string}
7695     */
7696    invalidHandleTypes: null, 
7697
7698    /**
7699     * An associative array of ids for elements that will be ignored if clicked
7700     * @property invalidHandleIds
7701     * @type {string: string}
7702     */
7703    invalidHandleIds: null, 
7704
7705    /**
7706     * An indexted array of css class names for elements that will be ignored
7707     * if clicked.
7708     * @property invalidHandleClasses
7709     * @type string[]
7710     */
7711    invalidHandleClasses: null, 
7712
7713    /**
7714     * The linked element's absolute X position at the time the drag was 
7715     * started
7716     * @property startPageX
7717     * @type int
7718     * @private
7719     */
7720    startPageX: 0,
7721
7722    /**
7723     * The linked element's absolute X position at the time the drag was 
7724     * started
7725     * @property startPageY
7726     * @type int
7727     * @private
7728     */
7729    startPageY: 0,
7730
7731    /**
7732     * The group defines a logical collection of DragDrop objects that are 
7733     * related.  Instances only get events when interacting with other 
7734     * DragDrop object in the same group.  This lets us define multiple 
7735     * groups using a single DragDrop subclass if we want.
7736     * @property groups
7737     * @type {string: string}
7738     */
7739    groups: null,
7740
7741    /**
7742     * Individual drag/drop instances can be locked.  This will prevent 
7743     * onmousedown start drag.
7744     * @property locked
7745     * @type boolean
7746     * @private
7747     */
7748    locked: false,
7749
7750    /**
7751     * Lock this instance
7752     * @method lock
7753     */
7754    lock: function() { this.locked = true; },
7755
7756    /**
7757     * Unlock this instace
7758     * @method unlock
7759     */
7760    unlock: function() { this.locked = false; },
7761
7762    /**
7763     * By default, all instances can be a drop target.  This can be disabled by
7764     * setting isTarget to false.
7765     * @property isTarget
7766     * @type boolean
7767     */
7768    isTarget: true,
7769
7770    /**
7771     * The padding configured for this drag and drop object for calculating
7772     * the drop zone intersection with this object.
7773     * @property padding
7774     * @type int[]
7775     */
7776    padding: null,
7777    /**
7778     * If this flag is true, do not fire drop events. The element is a drag only element (for movement not dropping)
7779     * @property dragOnly
7780     * @type Boolean
7781     */
7782    dragOnly: false,
7783
7784    /**
7785     * If this flag is true, a shim will be placed over the screen/viewable area to track mouse events. Should help with dragging elements over iframes and other controls.
7786     * @property useShim
7787     * @type Boolean
7788     */
7789    useShim: false,
7790
7791    /**
7792     * Cached reference to the linked element
7793     * @property _domRef
7794     * @private
7795     */
7796    _domRef: null,
7797
7798    /**
7799     * Internal typeof flag
7800     * @property __ygDragDrop
7801     * @private
7802     */
7803    __ygDragDrop: true,
7804
7805    /**
7806     * Set to true when horizontal contraints are applied
7807     * @property constrainX
7808     * @type boolean
7809     * @private
7810     */
7811    constrainX: false,
7812
7813    /**
7814     * Set to true when vertical contraints are applied
7815     * @property constrainY
7816     * @type boolean
7817     * @private
7818     */
7819    constrainY: false,
7820
7821    /**
7822     * The left constraint
7823     * @property minX
7824     * @type int
7825     * @private
7826     */
7827    minX: 0,
7828
7829    /**
7830     * The right constraint
7831     * @property maxX
7832     * @type int
7833     * @private
7834     */
7835    maxX: 0,
7836
7837    /**
7838     * The up constraint 
7839     * @property minY
7840     * @type int
7841     * @type int
7842     * @private
7843     */
7844    minY: 0,
7845
7846    /**
7847     * The down constraint 
7848     * @property maxY
7849     * @type int
7850     * @private
7851     */
7852    maxY: 0,
7853
7854    /**
7855     * The difference between the click position and the source element's location
7856     * @property deltaX
7857     * @type int
7858     * @private
7859     */
7860    deltaX: 0,
7861
7862    /**
7863     * The difference between the click position and the source element's location
7864     * @property deltaY
7865     * @type int
7866     * @private
7867     */
7868    deltaY: 0,
7869
7870    /**
7871     * Maintain offsets when we resetconstraints.  Set to true when you want
7872     * the position of the element relative to its parent to stay the same
7873     * when the page changes
7874     *
7875     * @property maintainOffset
7876     * @type boolean
7877     */
7878    maintainOffset: false,
7879
7880    /**
7881     * Array of pixel locations the element will snap to if we specified a 
7882     * horizontal graduation/interval.  This array is generated automatically
7883     * when you define a tick interval.
7884     * @property xTicks
7885     * @type int[]
7886     */
7887    xTicks: null,
7888
7889    /**
7890     * Array of pixel locations the element will snap to if we specified a 
7891     * vertical graduation/interval.  This array is generated automatically 
7892     * when you define a tick interval.
7893     * @property yTicks
7894     * @type int[]
7895     */
7896    yTicks: null,
7897
7898    /**
7899     * By default the drag and drop instance will only respond to the primary
7900     * button click (left button for a right-handed mouse).  Set to true to
7901     * allow drag and drop to start with any mouse click that is propogated
7902     * by the browser
7903     * @property primaryButtonOnly
7904     * @type boolean
7905     */
7906    primaryButtonOnly: true,
7907
7908    /**
7909     * The availabe property is false until the linked dom element is accessible.
7910     * @property available
7911     * @type boolean
7912     */
7913    available: false,
7914
7915    /**
7916     * By default, drags can only be initiated if the mousedown occurs in the
7917     * region the linked element is.  This is done in part to work around a
7918     * bug in some browsers that mis-report the mousedown if the previous
7919     * mouseup happened outside of the window.  This property is set to true
7920     * if outer handles are defined.
7921     *
7922     * @property hasOuterHandles
7923     * @type boolean
7924     * @default false
7925     */
7926    hasOuterHandles: false,
7927
7928    /**
7929     * Property that is assigned to a drag and drop object when testing to
7930     * see if it is being targeted by another dd object.  This property
7931     * can be used in intersect mode to help determine the focus of
7932     * the mouse interaction.  DDM.getBestMatch uses this property first to
7933     * determine the closest match in INTERSECT mode when multiple targets
7934     * are part of the same interaction.
7935     * @property cursorIsOver
7936     * @type boolean
7937     */
7938    cursorIsOver: false,
7939
7940    /**
7941     * Property that is assigned to a drag and drop object when testing to
7942     * see if it is being targeted by another dd object.  This is a region
7943     * that represents the area the draggable element overlaps this target.
7944     * DDM.getBestMatch uses this property to compare the size of the overlap
7945     * to that of other targets in order to determine the closest match in
7946     * INTERSECT mode when multiple targets are part of the same interaction.
7947     * @property overlap 
7948     * @type YAHOO.util.Region
7949     */
7950    overlap: null,
7951
7952    /**
7953     * Code that executes immediately before the startDrag event
7954     * @method b4StartDrag
7955     * @private
7956     */
7957    b4StartDrag: function(x, y) { },
7958
7959    /**
7960     * Abstract method called after a drag/drop object is clicked
7961     * and the drag or mousedown time thresholds have beeen met.
7962     * @method startDrag
7963     * @param {int} X click location
7964     * @param {int} Y click location
7965     */
7966    startDrag: function(x, y) { /* override this */ },
7967
7968    /**
7969     * Code that executes immediately before the onDrag event
7970     * @method b4Drag
7971     * @private
7972     */
7973    b4Drag: function(e) { },
7974
7975    /**
7976     * Abstract method called during the onMouseMove event while dragging an 
7977     * object.
7978     * @method onDrag
7979     * @param {Event} e the mousemove event
7980     */
7981    onDrag: function(e) { /* override this */ },
7982
7983    /**
7984     * Abstract method called when this element fist begins hovering over 
7985     * another DragDrop obj
7986     * @method onDragEnter
7987     * @param {Event} e the mousemove event
7988     * @param {String|DragDrop[]} id In POINT mode, the element
7989     * id this is hovering over.  In INTERSECT mode, an array of one or more 
7990     * dragdrop items being hovered over.
7991     */
7992    onDragEnter: function(e, id) { /* override this */ },
7993
7994    /**
7995     * Code that executes immediately before the onDragOver event
7996     * @method b4DragOver
7997     * @private
7998     */
7999    b4DragOver: function(e) { },
8000
8001    /**
8002     * Abstract method called when this element is hovering over another 
8003     * DragDrop obj
8004     * @method onDragOver
8005     * @param {Event} e the mousemove event
8006     * @param {String|DragDrop[]} id In POINT mode, the element
8007     * id this is hovering over.  In INTERSECT mode, an array of dd items 
8008     * being hovered over.
8009     */
8010    onDragOver: function(e, id) { /* override this */ },
8011
8012    /**
8013     * Code that executes immediately before the onDragOut event
8014     * @method b4DragOut
8015     * @private
8016     */
8017    b4DragOut: function(e) { },
8018
8019    /**
8020     * Abstract method called when we are no longer hovering over an element
8021     * @method onDragOut
8022     * @param {Event} e the mousemove event
8023     * @param {String|DragDrop[]} id In POINT mode, the element
8024     * id this was hovering over.  In INTERSECT mode, an array of dd items 
8025     * that the mouse is no longer over.
8026     */
8027    onDragOut: function(e, id) { /* override this */ },
8028
8029    /**
8030     * Code that executes immediately before the onDragDrop event
8031     * @method b4DragDrop
8032     * @private
8033     */
8034    b4DragDrop: function(e) { },
8035
8036    /**
8037     * Abstract method called when this item is dropped on another DragDrop 
8038     * obj
8039     * @method onDragDrop
8040     * @param {Event} e the mouseup event
8041     * @param {String|DragDrop[]} id In POINT mode, the element
8042     * id this was dropped on.  In INTERSECT mode, an array of dd items this 
8043     * was dropped on.
8044     */
8045    onDragDrop: function(e, id) { /* override this */ },
8046
8047    /**
8048     * Abstract method called when this item is dropped on an area with no
8049     * drop target
8050     * @method onInvalidDrop
8051     * @param {Event} e the mouseup event
8052     */
8053    onInvalidDrop: function(e) { /* override this */ },
8054
8055    /**
8056     * Code that executes immediately before the endDrag event
8057     * @method b4EndDrag
8058     * @private
8059     */
8060    b4EndDrag: function(e) { },
8061
8062    /**
8063     * Fired when we are done dragging the object
8064     * @method endDrag
8065     * @param {Event} e the mouseup event
8066     */
8067    endDrag: function(e) { /* override this */ },
8068
8069    /**
8070     * Code executed immediately before the onMouseDown event
8071     * @method b4MouseDown
8072     * @param {Event} e the mousedown event
8073     * @private
8074     */
8075    b4MouseDown: function(e) {  },
8076
8077    /**
8078     * Event handler that fires when a drag/drop obj gets a mousedown
8079     * @method onMouseDown
8080     * @param {Event} e the mousedown event
8081     */
8082    onMouseDown: function(e) { /* override this */ },
8083
8084    /**
8085     * Event handler that fires when a drag/drop obj gets a mouseup
8086     * @method onMouseUp
8087     * @param {Event} e the mouseup event
8088     */
8089    onMouseUp: function(e) { /* override this */ },
8090   
8091    /**
8092     * Override the onAvailable method to do what is needed after the initial
8093     * position was determined.
8094     * @method onAvailable
8095     */
8096    onAvailable: function () { 
8097        //this.logger.log("onAvailable (base)"); 
8098    },
8099
8100    /**
8101     * Returns a reference to the linked element
8102     * @method getEl
8103     * @return {HTMLElement} the html element 
8104     */
8105    getEl: function() { 
8106        if (!this._domRef) {
8107            this._domRef = Dom.get(this.id); 
8108        }
8109
8110        return this._domRef;
8111    },
8112
8113    /**
8114     * Returns a reference to the actual element to drag.  By default this is
8115     * the same as the html element, but it can be assigned to another 
8116     * element. An example of this can be found in YAHOO.util.DDProxy
8117     * @method getDragEl
8118     * @return {HTMLElement} the html element 
8119     */
8120    getDragEl: function() {
8121        return Dom.get(this.dragElId);
8122    },
8123
8124    /**
8125     * Sets up the DragDrop object.  Must be called in the constructor of any
8126     * YAHOO.util.DragDrop subclass
8127     * @method init
8128     * @param id the id of the linked element
8129     * @param {String} sGroup the group of related items
8130     * @param {object} config configuration attributes
8131     */
8132    init: function(id, sGroup, config) {
8133        this.initTarget(id, sGroup, config);
8134        Event.on(this._domRef || this.id, "mousedown", 
8135                        this.handleMouseDown, this, true);
8136
8137        // Event.on(this.id, "selectstart", Event.preventDefault);
8138        for (var i in this.events) {
8139            this.createEvent(i + 'Event');
8140        }
8141        
8142    },
8143
8144    /**
8145     * Initializes Targeting functionality only... the object does not
8146     * get a mousedown handler.
8147     * @method initTarget
8148     * @param id the id of the linked element
8149     * @param {String} sGroup the group of related items
8150     * @param {object} config configuration attributes
8151     */
8152    initTarget: function(id, sGroup, config) {
8153
8154        // configuration attributes 
8155        this.config = config || {};
8156
8157        this.events = {};
8158
8159        // create a local reference to the drag and drop manager
8160        this.DDM = YAHOO.util.DDM;
8161
8162        // initialize the groups object
8163        this.groups = {};
8164
8165        // assume that we have an element reference instead of an id if the
8166        // parameter is not a string
8167        if (typeof id !== "string") {
8168            YAHOO.log("id is not a string, assuming it is an HTMLElement");
8169            this._domRef = id;
8170            id = Dom.generateId(id);
8171        }
8172
8173        // set the id
8174        this.id = id;
8175
8176        // add to an interaction group
8177        this.addToGroup((sGroup) ? sGroup : "default");
8178
8179        // We don't want to register this as the handle with the manager
8180        // so we just set the id rather than calling the setter.
8181        this.handleElId = id;
8182
8183        Event.onAvailable(id, this.handleOnAvailable, this, true);
8184
8185        // create a logger instance
8186        this.logger = (YAHOO.widget.LogWriter) ? 
8187                new YAHOO.widget.LogWriter(this.toString()) : YAHOO;
8188
8189        // the linked element is the element that gets dragged by default
8190        this.setDragElId(id); 
8191
8192        // by default, clicked anchors will not start drag operations. 
8193        // @TODO what else should be here?  Probably form fields.
8194        this.invalidHandleTypes = { A: "A" };
8195        this.invalidHandleIds = {};
8196        this.invalidHandleClasses = [];
8197
8198        this.applyConfig();
8199    },
8200
8201    /**
8202     * Applies the configuration parameters that were passed into the constructor.
8203     * This is supposed to happen at each level through the inheritance chain.  So
8204     * a DDProxy implentation will execute apply config on DDProxy, DD, and 
8205     * DragDrop in order to get all of the parameters that are available in
8206     * each object.
8207     * @method applyConfig
8208     */
8209    applyConfig: function() {
8210        this.events = {
8211            mouseDown: true,
8212            b4MouseDown: true,
8213            mouseUp: true,
8214            b4StartDrag: true,
8215            startDrag: true,
8216            b4EndDrag: true,
8217            endDrag: true,
8218            drag: true,
8219            b4Drag: true,
8220            invalidDrop: true,
8221            b4DragOut: true,
8222            dragOut: true,
8223            dragEnter: true,
8224            b4DragOver: true,
8225            dragOver: true,
8226            b4DragDrop: true,
8227            dragDrop: true
8228        };
8229        
8230        if (this.config.events) {
8231            for (var i in this.config.events) {
8232                if (this.config.events[i] === false) {
8233                    this.events[i] = false;
8234                }
8235            }
8236        }
8237
8238
8239        // configurable properties: 
8240        //    padding, isTarget, maintainOffset, primaryButtonOnly
8241        this.padding           = this.config.padding || [0, 0, 0, 0];
8242        this.isTarget          = (this.config.isTarget !== false);
8243        this.maintainOffset    = (this.config.maintainOffset);
8244        this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
8245        this.dragOnly = ((this.config.dragOnly === true) ? true : false);
8246        this.useShim = ((this.config.useShim === true) ? true : false);
8247    },
8248
8249    /**
8250     * Executed when the linked element is available
8251     * @method handleOnAvailable
8252     * @private
8253     */
8254    handleOnAvailable: function() {
8255        //this.logger.log("handleOnAvailable");
8256        this.available = true;
8257        this.resetConstraints();
8258        this.onAvailable();
8259    },
8260
8261     /**
8262     * Configures the padding for the target zone in px.  Effectively expands
8263     * (or reduces) the virtual object size for targeting calculations.  
8264     * Supports css-style shorthand; if only one parameter is passed, all sides
8265     * will have that padding, and if only two are passed, the top and bottom
8266     * will have the first param, the left and right the second.
8267     * @method setPadding
8268     * @param {int} iTop    Top pad
8269     * @param {int} iRight  Right pad
8270     * @param {int} iBot    Bot pad
8271     * @param {int} iLeft   Left pad
8272     */
8273    setPadding: function(iTop, iRight, iBot, iLeft) {
8274        // this.padding = [iLeft, iRight, iTop, iBot];
8275        if (!iRight && 0 !== iRight) {
8276            this.padding = [iTop, iTop, iTop, iTop];
8277        } else if (!iBot && 0 !== iBot) {
8278            this.padding = [iTop, iRight, iTop, iRight];
8279        } else {
8280            this.padding = [iTop, iRight, iBot, iLeft];
8281        }
8282    },
8283
8284    /**
8285     * Stores the initial placement of the linked element.
8286     * @method setInitialPosition
8287     * @param {int} diffX   the X offset, default 0
8288     * @param {int} diffY   the Y offset, default 0
8289     * @private
8290     */
8291    setInitPosition: function(diffX, diffY) {
8292        var el = this.getEl();
8293
8294        if (!this.DDM.verifyEl(el)) {
8295            if (el && el.style && (el.style.display == 'none')) {
8296                this.logger.log(this.id + " can not get initial position, element style is display: none");
8297            } else {
8298                this.logger.log(this.id + " element is broken");
8299            }
8300            return;
8301        }
8302
8303        var dx = diffX || 0;
8304        var dy = diffY || 0;
8305
8306        var p = Dom.getXY( el );
8307
8308        this.initPageX = p[0] - dx;
8309        this.initPageY = p[1] - dy;
8310
8311        this.lastPageX = p[0];
8312        this.lastPageY = p[1];
8313
8314        this.logger.log(this.id + " initial position: " + this.initPageX + 
8315                ", " + this.initPageY);
8316
8317
8318        this.setStartPosition(p);
8319    },
8320
8321    /**
8322     * Sets the start position of the element.  This is set when the obj
8323     * is initialized, the reset when a drag is started.
8324     * @method setStartPosition
8325     * @param pos current position (from previous lookup)
8326     * @private
8327     */
8328    setStartPosition: function(pos) {
8329        var p = pos || Dom.getXY(this.getEl());
8330
8331        this.deltaSetXY = null;
8332
8333        this.startPageX = p[0];
8334        this.startPageY = p[1];
8335    },
8336
8337    /**
8338     * Add this instance to a group of related drag/drop objects.  All 
8339     * instances belong to at least one group, and can belong to as many 
8340     * groups as needed.
8341     * @method addToGroup
8342     * @param sGroup {string} the name of the group
8343     */
8344    addToGroup: function(sGroup) {
8345        this.groups[sGroup] = true;
8346        this.DDM.regDragDrop(this, sGroup);
8347    },
8348
8349    /**
8350     * Remove's this instance from the supplied interaction group
8351     * @method removeFromGroup
8352     * @param {string}  sGroup  The group to drop
8353     */
8354    removeFromGroup: function(sGroup) {
8355        this.logger.log("Removing from group: " + sGroup);
8356        if (this.groups[sGroup]) {
8357            delete this.groups[sGroup];
8358        }
8359
8360        this.DDM.removeDDFromGroup(this, sGroup);
8361    },
8362
8363    /**
8364     * Allows you to specify that an element other than the linked element 
8365     * will be moved with the cursor during a drag
8366     * @method setDragElId
8367     * @param id {string} the id of the element that will be used to initiate the drag
8368     */
8369    setDragElId: function(id) {
8370        this.dragElId = id;
8371    },
8372
8373    /**
8374     * Allows you to specify a child of the linked element that should be 
8375     * used to initiate the drag operation.  An example of this would be if 
8376     * you have a content div with text and links.  Clicking anywhere in the 
8377     * content area would normally start the drag operation.  Use this method
8378     * to specify that an element inside of the content div is the element 
8379     * that starts the drag operation.
8380     * @method setHandleElId
8381     * @param id {string} the id of the element that will be used to 
8382     * initiate the drag.
8383     */
8384    setHandleElId: function(id) {
8385        if (typeof id !== "string") {
8386            YAHOO.log("id is not a string, assuming it is an HTMLElement");
8387            id = Dom.generateId(id);
8388        }
8389        this.handleElId = id;
8390        this.DDM.regHandle(this.id, id);
8391    },
8392
8393    /**
8394     * Allows you to set an element outside of the linked element as a drag 
8395     * handle
8396     * @method setOuterHandleElId
8397     * @param id the id of the element that will be used to initiate the drag
8398     */
8399    setOuterHandleElId: function(id) {
8400        if (typeof id !== "string") {
8401            YAHOO.log("id is not a string, assuming it is an HTMLElement");
8402            id = Dom.generateId(id);
8403        }
8404        this.logger.log("Adding outer handle event: " + id);
8405        Event.on(id, "mousedown", 
8406                this.handleMouseDown, this, true);
8407        this.setHandleElId(id);
8408
8409        this.hasOuterHandles = true;
8410    },
8411
8412    /**
8413     * Remove all drag and drop hooks for this element
8414     * @method unreg
8415     */
8416    unreg: function() {
8417        this.logger.log("DragDrop obj cleanup " + this.id);
8418        Event.removeListener(this.id, "mousedown", 
8419                this.handleMouseDown);
8420        this._domRef = null;
8421        this.DDM._remove(this);
8422    },
8423
8424    /**
8425     * Returns true if this instance is locked, or the drag drop mgr is locked
8426     * (meaning that all drag/drop is disabled on the page.)
8427     * @method isLocked
8428     * @return {boolean} true if this obj or all drag/drop is locked, else 
8429     * false
8430     */
8431    isLocked: function() {
8432        return (this.DDM.isLocked() || this.locked);
8433    },
8434
8435    /**
8436     * Fired when this object is clicked
8437     * @method handleMouseDown
8438     * @param {Event} e 
8439     * @param {YAHOO.util.DragDrop} oDD the clicked dd object (this dd obj)
8440     * @private
8441     */
8442    handleMouseDown: function(e, oDD) {
8443
8444        var button = e.which || e.button;
8445        this.logger.log("button: " + button);
8446
8447        if (this.primaryButtonOnly && button > 1) {
8448            this.logger.log("Mousedown was not produced by the primary button");
8449            return;
8450        }
8451
8452        if (this.isLocked()) {
8453            this.logger.log("Drag and drop is disabled, aborting");
8454            return;
8455        }
8456
8457        this.logger.log("mousedown " + this.id);
8458
8459        this.logger.log("firing onMouseDown events");
8460
8461        // firing the mousedown events prior to calculating positions
8462        var b4Return = this.b4MouseDown(e),
8463        b4Return2 = true;
8464
8465        if (this.events.b4MouseDown) {
8466            b4Return2 = this.fireEvent('b4MouseDownEvent', e);
8467        }
8468        var mDownReturn = this.onMouseDown(e),
8469            mDownReturn2 = true;
8470        if (this.events.mouseDown) {
8471            if (mDownReturn === false) {
8472                //Fixes #2528759 - Mousedown function returned false, don't fire the event and cancel everything.
8473                 mDownReturn2 = false;
8474            } else {
8475                mDownReturn2 = this.fireEvent('mouseDownEvent', e);
8476            }
8477        }
8478
8479        if ((b4Return === false) || (mDownReturn === false) || (b4Return2 === false) || (mDownReturn2 === false)) {
8480            this.logger.log('b4MouseDown or onMouseDown returned false, exiting drag');
8481            return;
8482        }
8483
8484        this.DDM.refreshCache(this.groups);
8485        // var self = this;
8486        // setTimeout( function() { self.DDM.refreshCache(self.groups); }, 0);
8487
8488        // Only process the event if we really clicked within the linked 
8489        // element.  The reason we make this check is that in the case that 
8490        // another element was moved between the clicked element and the 
8491        // cursor in the time between the mousedown and mouseup events. When 
8492        // this happens, the element gets the next mousedown event 
8493        // regardless of where on the screen it happened.  
8494        var pt = new YAHOO.util.Point(Event.getPageX(e), Event.getPageY(e));
8495        if (!this.hasOuterHandles && !this.DDM.isOverTarget(pt, this) )  {
8496                this.logger.log("Click was not over the element: " + this.id);
8497        } else {
8498            if (this.clickValidator(e)) {
8499
8500                this.logger.log("click was a valid handle");
8501
8502                // set the initial element position
8503                this.setStartPosition();
8504
8505                // start tracking mousemove distance and mousedown time to
8506                // determine when to start the actual drag
8507                this.DDM.handleMouseDown(e, this);
8508
8509                // this mousedown is mine
8510                this.DDM.stopEvent(e);
8511            } else {
8512
8513this.logger.log("clickValidator returned false, drag not initiated");
8514
8515            }
8516        }
8517    },
8518
8519    /**
8520     * @method clickValidator
8521     * @description Method validates that the clicked element
8522     * was indeed the handle or a valid child of the handle
8523     * @param {Event} e 
8524     */
8525    clickValidator: function(e) {
8526        var target = YAHOO.util.Event.getTarget(e);
8527        return ( this.isValidHandleChild(target) &&
8528                    (this.id == this.handleElId || 
8529                        this.DDM.handleWasClicked(target, this.id)) );
8530    },
8531
8532    /**
8533     * Finds the location the element should be placed if we want to move
8534     * it to where the mouse location less the click offset would place us.
8535     * @method getTargetCoord
8536     * @param {int} iPageX the X coordinate of the click
8537     * @param {int} iPageY the Y coordinate of the click
8538     * @return an object that contains the coordinates (Object.x and Object.y)
8539     * @private
8540     */
8541    getTargetCoord: function(iPageX, iPageY) {
8542
8543        // this.logger.log("getTargetCoord: " + iPageX + ", " + iPageY);
8544
8545        var x = iPageX - this.deltaX;
8546        var y = iPageY - this.deltaY;
8547
8548        if (this.constrainX) {
8549            if (x < this.minX) { x = this.minX; }
8550            if (x > this.maxX) { x = this.maxX; }
8551        }
8552
8553        if (this.constrainY) {
8554            if (y < this.minY) { y = this.minY; }
8555            if (y > this.maxY) { y = this.maxY; }
8556        }
8557
8558        x = this.getTick(x, this.xTicks);
8559        y = this.getTick(y, this.yTicks);
8560
8561        // this.logger.log("getTargetCoord " + 
8562                // " iPageX: " + iPageX +
8563                // " iPageY: " + iPageY +
8564                // " x: " + x + ", y: " + y);
8565
8566        return {x:x, y:y};
8567    },
8568
8569    /**
8570     * Allows you to specify a tag name that should not start a drag operation
8571     * when clicked.  This is designed to facilitate embedding links within a
8572     * drag handle that do something other than start the drag.
8573     * @method addInvalidHandleType
8574     * @param {string} tagName the type of element to exclude
8575     */
8576    addInvalidHandleType: function(tagName) {
8577        var type = tagName.toUpperCase();
8578        this.invalidHandleTypes[type] = type;
8579    },
8580
8581    /**
8582     * Lets you to specify an element id for a child of a drag handle
8583     * that should not initiate a drag
8584     * @method addInvalidHandleId
8585     * @param {string} id the element id of the element you wish to ignore
8586     */
8587    addInvalidHandleId: function(id) {
8588        if (typeof id !== "string") {
8589            YAHOO.log("id is not a string, assuming it is an HTMLElement");
8590            id = Dom.generateId(id);
8591        }
8592        this.invalidHandleIds[id] = id;
8593    },
8594
8595
8596    /**
8597     * Lets you specify a css class of elements that will not initiate a drag
8598     * @method addInvalidHandleClass
8599     * @param {string} cssClass the class of the elements you wish to ignore
8600     */
8601    addInvalidHandleClass: function(cssClass) {
8602        this.invalidHandleClasses.push(cssClass);
8603    },
8604
8605    /**
8606     * Unsets an excluded tag name set by addInvalidHandleType
8607     * @method removeInvalidHandleType
8608     * @param {string} tagName the type of element to unexclude
8609     */
8610    removeInvalidHandleType: function(tagName) {
8611        var type = tagName.toUpperCase();
8612        // this.invalidHandleTypes[type] = null;
8613        delete this.invalidHandleTypes[type];
8614    },
8615    
8616    /**
8617     * Unsets an invalid handle id
8618     * @method removeInvalidHandleId
8619     * @param {string} id the id of the element to re-enable
8620     */
8621    removeInvalidHandleId: function(id) {
8622        if (typeof id !== "string") {
8623            YAHOO.log("id is not a string, assuming it is an HTMLElement");
8624            id = Dom.generateId(id);
8625        }
8626        delete this.invalidHandleIds[id];
8627    },
8628
8629    /**
8630     * Unsets an invalid css class
8631     * @method removeInvalidHandleClass
8632     * @param {string} cssClass the class of the element(s) you wish to 
8633     * re-enable
8634     */
8635    removeInvalidHandleClass: function(cssClass) {
8636        for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
8637            if (this.invalidHandleClasses[i] == cssClass) {
8638                delete this.invalidHandleClasses[i];
8639            }
8640        }
8641    },
8642
8643    /**
8644     * Checks the tag exclusion list to see if this click should be ignored
8645     * @method isValidHandleChild
8646     * @param {HTMLElement} node the HTMLElement to evaluate
8647     * @return {boolean} true if this is a valid tag type, false if not
8648     */
8649    isValidHandleChild: function(node) {
8650
8651        var valid = true;
8652        // var n = (node.nodeName == "#text") ? node.parentNode : node;
8653        var nodeName;
8654        try {
8655            nodeName = node.nodeName.toUpperCase();
8656        } catch(e) {
8657            nodeName = node.nodeName;
8658        }
8659        valid = valid && !this.invalidHandleTypes[nodeName];
8660        valid = valid && !this.invalidHandleIds[node.id];
8661
8662        for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
8663            valid = !Dom.hasClass(node, this.invalidHandleClasses[i]);
8664        }
8665
8666        this.logger.log("Valid handle? ... " + valid);
8667
8668        return valid;
8669
8670    },
8671
8672    /**
8673     * Create the array of horizontal tick marks if an interval was specified
8674     * in setXConstraint().
8675     * @method setXTicks
8676     * @private
8677     */
8678    setXTicks: function(iStartX, iTickSize) {
8679        this.xTicks = [];
8680        this.xTickSize = iTickSize;
8681        
8682        var tickMap = {};
8683
8684        for (var i = this.initPageX; i >= this.minX; i = i - iTickSize) {
8685            if (!tickMap[i]) {
8686                this.xTicks[this.xTicks.length] = i;
8687                tickMap[i] = true;
8688            }
8689        }
8690
8691        for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
8692            if (!tickMap[i]) {
8693                this.xTicks[this.xTicks.length] = i;
8694                tickMap[i] = true;
8695            }
8696        }
8697
8698        this.xTicks.sort(this.DDM.numericSort) ;
8699        this.logger.log("xTicks: " + this.xTicks.join());
8700    },
8701
8702    /**
8703     * Create the array of vertical tick marks if an interval was specified in 
8704     * setYConstraint().
8705     * @method setYTicks
8706     * @private
8707     */
8708    setYTicks: function(iStartY, iTickSize) {
8709        // this.logger.log("setYTicks: " + iStartY + ", " + iTickSize
8710               // + ", " + this.initPageY + ", " + this.minY + ", " + this.maxY );
8711        this.yTicks = [];
8712        this.yTickSize = iTickSize;
8713
8714        var tickMap = {};
8715
8716        for (var i = this.initPageY; i >= this.minY; i = i - iTickSize) {
8717            if (!tickMap[i]) {
8718                this.yTicks[this.yTicks.length] = i;
8719                tickMap[i] = true;
8720            }
8721        }
8722
8723        for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
8724            if (!tickMap[i]) {
8725                this.yTicks[this.yTicks.length] = i;
8726                tickMap[i] = true;
8727            }
8728        }
8729
8730        this.yTicks.sort(this.DDM.numericSort) ;
8731        this.logger.log("yTicks: " + this.yTicks.join());
8732    },
8733
8734    /**
8735     * By default, the element can be dragged any place on the screen.  Use 
8736     * this method to limit the horizontal travel of the element.  Pass in 
8737     * 0,0 for the parameters if you want to lock the drag to the y axis.
8738     * @method setXConstraint
8739     * @param {int} iLeft the number of pixels the element can move to the left
8740     * @param {int} iRight the number of pixels the element can move to the 
8741     * right
8742     * @param {int} iTickSize optional parameter for specifying that the 
8743     * element
8744     * should move iTickSize pixels at a time.
8745     */
8746    setXConstraint: function(iLeft, iRight, iTickSize) {
8747        this.leftConstraint = parseInt(iLeft, 10);
8748        this.rightConstraint = parseInt(iRight, 10);
8749
8750        this.minX = this.initPageX - this.leftConstraint;
8751        this.maxX = this.initPageX + this.rightConstraint;
8752        if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
8753
8754        this.constrainX = true;
8755        this.logger.log("initPageX:" + this.initPageX + " minX:" + this.minX + 
8756                " maxX:" + this.maxX);
8757    },
8758
8759    /**
8760     * Clears any constraints applied to this instance.  Also clears ticks
8761     * since they can't exist independent of a constraint at this time.
8762     * @method clearConstraints
8763     */
8764    clearConstraints: function() {
8765        this.logger.log("Clearing constraints");
8766        this.constrainX = false;
8767        this.constrainY = false;
8768        this.clearTicks();
8769    },
8770
8771    /**
8772     * Clears any tick interval defined for this instance
8773     * @method clearTicks
8774     */
8775    clearTicks: function() {
8776        this.logger.log("Clearing ticks");
8777        this.xTicks = null;
8778        this.yTicks = null;
8779        this.xTickSize = 0;
8780        this.yTickSize = 0;
8781    },
8782
8783    /**
8784     * By default, the element can be dragged any place on the screen.  Set 
8785     * this to limit the vertical travel of the element.  Pass in 0,0 for the
8786     * parameters if you want to lock the drag to the x axis.
8787     * @method setYConstraint
8788     * @param {int} iUp the number of pixels the element can move up
8789     * @param {int} iDown the number of pixels the element can move down
8790     * @param {int} iTickSize optional parameter for specifying that the 
8791     * element should move iTickSize pixels at a time.
8792     */
8793    setYConstraint: function(iUp, iDown, iTickSize) {
8794        this.logger.log("setYConstraint: " + iUp + "," + iDown + "," + iTickSize);
8795        this.topConstraint = parseInt(iUp, 10);
8796        this.bottomConstraint = parseInt(iDown, 10);
8797
8798        this.minY = this.initPageY - this.topConstraint;
8799        this.maxY = this.initPageY + this.bottomConstraint;
8800        if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
8801
8802        this.constrainY = true;
8803        
8804        this.logger.log("initPageY:" + this.initPageY + " minY:" + this.minY + 
8805                " maxY:" + this.maxY);
8806    },
8807
8808    /**
8809     * resetConstraints must be called if you manually reposition a dd element.
8810     * @method resetConstraints
8811     */
8812    resetConstraints: function() {
8813
8814        //this.logger.log("resetConstraints");
8815
8816        // Maintain offsets if necessary
8817        if (this.initPageX || this.initPageX === 0) {
8818            //this.logger.log("init pagexy: " + this.initPageX + ", " + 
8819                               //this.initPageY);
8820            //this.logger.log("last pagexy: " + this.lastPageX + ", " + 
8821                               //this.lastPageY);
8822            // figure out how much this thing has moved
8823            var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0;
8824            var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
8825
8826            this.setInitPosition(dx, dy);
8827
8828        // This is the first time we have detected the element's position
8829        } else {
8830            this.setInitPosition();
8831        }
8832
8833        if (this.constrainX) {
8834            this.setXConstraint( this.leftConstraint, 
8835                                 this.rightConstraint, 
8836                                 this.xTickSize        );
8837        }
8838
8839        if (this.constrainY) {
8840            this.setYConstraint( this.topConstraint, 
8841                                 this.bottomConstraint, 
8842                                 this.yTickSize         );
8843        }
8844    },
8845
8846    /**
8847     * Normally the drag element is moved pixel by pixel, but we can specify 
8848     * that it move a number of pixels at a time.  This method resolves the 
8849     * location when we have it set up like this.
8850     * @method getTick
8851     * @param {int} val where we want to place the object
8852     * @param {int[]} tickArray sorted array of valid points
8853     * @return {int} the closest tick
8854     * @private
8855     */
8856    getTick: function(val, tickArray) {
8857
8858        if (!tickArray) {
8859            // If tick interval is not defined, it is effectively 1 pixel, 
8860            // so we return the value passed to us.
8861            return val; 
8862        } else if (tickArray[0] >= val) {
8863            // The value is lower than the first tick, so we return the first
8864            // tick.
8865            return tickArray[0];
8866        } else {
8867            for (var i=0, len=tickArray.length; i<len; ++i) {
8868                var next = i + 1;
8869                if (tickArray[next] && tickArray[next] >= val) {
8870                    var diff1 = val - tickArray[i];
8871                    var diff2 = tickArray[next] - val;
8872                    return (diff2 > diff1) ? tickArray[i] : tickArray[next];
8873                }
8874            }
8875
8876            // The value is larger than the last tick, so we return the last
8877            // tick.
8878            return tickArray[tickArray.length - 1];
8879        }
8880    },
8881
8882    /**
8883     * toString method
8884     * @method toString
8885     * @return {string} string representation of the dd obj
8886     */
8887    toString: function() {
8888        return ("DragDrop " + this.id);
8889    }
8890
8891};
8892YAHOO.augment(YAHOO.util.DragDrop, YAHOO.util.EventProvider);
8893
8894/**
8895* @event mouseDownEvent
8896* @description Provides access to the mousedown event. The mousedown does not always result in a drag operation.
8897* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8898*/
8899
8900/**
8901* @event b4MouseDownEvent
8902* @description Provides access to the mousedown event, before the mouseDownEvent gets fired. Returning false will cancel the drag.
8903* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8904*/
8905
8906/**
8907* @event mouseUpEvent
8908* @description Fired from inside DragDropMgr when the drag operation is finished.
8909* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8910*/
8911
8912/**
8913* @event b4StartDragEvent
8914* @description Fires before the startDragEvent, returning false will cancel the startDrag Event.
8915* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8916*/
8917
8918/**
8919* @event startDragEvent
8920* @description Occurs after a mouse down and the drag threshold has been met. The drag threshold default is either 3 pixels of mouse movement or 1 full second of holding the mousedown. 
8921* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8922*/
8923
8924/**
8925* @event b4EndDragEvent
8926* @description Fires before the endDragEvent. Returning false will cancel.
8927* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8928*/
8929
8930/**
8931* @event endDragEvent
8932* @description Fires on the mouseup event after a drag has been initiated (startDrag fired).
8933* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8934*/
8935
8936/**
8937* @event dragEvent
8938* @description Occurs every mousemove event while dragging.
8939* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8940*/
8941/**
8942* @event b4DragEvent
8943* @description Fires before the dragEvent.
8944* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8945*/
8946/**
8947* @event invalidDropEvent
8948* @description Fires when the dragged objects is dropped in a location that contains 
8948no drop targets.
8949* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8950*/
8951/**
8952* @event b4DragOutEvent
8953* @description Fires before the dragOutEvent
8954* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8955*/
8956/**
8957* @event dragOutEvent
8958* @description Fires when a dragged object is no longer over an object that had the onDragEnter fire. 
8959* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8960*/
8961/**
8962* @event dragEnterEvent
8963* @description Occurs when the dragged object first interacts with another targettable drag and drop object.
8964* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8965*/
8966/**
8967* @event b4DragOverEvent
8968* @description Fires before the dragOverEvent.
8969* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8970*/
8971/**
8972* @event dragOverEvent
8973* @description Fires every mousemove event while over a drag and drop object.
8974* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8975*/
8976/**
8977* @event b4DragDropEvent 
8978* @description Fires before the dragDropEvent
8979* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8980*/
8981/**
8982* @event dragDropEvent
8983* @description Fires when the dragged objects is dropped on another.
8984* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
8985*/
8986})();
8987/**
8988 * A DragDrop implementation where the linked element follows the 
8989 * mouse cursor during a drag.
8990 * @class DD
8991 * @extends YAHOO.util.DragDrop
8992 * @constructor
8993 * @param {String} id the id of the linked element 
8994 * @param {String} sGroup the group of related DragDrop items
8995 * @param {object} config an object containing configurable attributes
8996 *                Valid properties for DD: 
8997 *                    scroll
8998 */
8999YAHOO.util.DD = function(id, sGroup, config) {
9000    if (id) {
9001        this.init(id, sGroup, config);
9002    }
9003};
9004
9005YAHOO.extend(YAHOO.util.DD, YAHOO.util.DragDrop, {
9006
9007    /**
9008     * When set to true, the utility automatically tries to scroll the browser
9009     * window when a drag and drop element is dragged near the viewport boundary.
9010     * Defaults to true.
9011     * @property scroll
9012     * @type boolean
9013     */
9014    scroll: true, 
9015
9016    /**
9017     * Sets the pointer offset to the distance between the linked element's top 
9018     * left corner and the location the element was clicked
9019     * @method autoOffset
9020     * @param {int} iPageX the X coordinate of the click
9021     * @param {int} iPageY the Y coordinate of the click
9022     */
9023    autoOffset: function(iPageX, iPageY) {
9024        var x = iPageX - this.startPageX;
9025        var y = iPageY - this.startPageY;
9026        this.setDelta(x, y);
9027        // this.logger.log("autoOffset el pos: " + aCoord + ", delta: " + x + "," + y);
9028    },
9029
9030    /** 
9031     * Sets the pointer offset.  You can call this directly to force the 
9032     * offset to be in a particular location (e.g., pass in 0,0 to set it 
9033     * to the center of the object, as done in YAHOO.widget.Slider)
9034     * @method setDelta
9035     * @param {int} iDeltaX the distance from the left
9036     * @param {int} iDeltaY the distance from the top
9037     */
9038    setDelta: function(iDeltaX, iDeltaY) {
9039        this.deltaX = iDeltaX;
9040        this.deltaY = iDeltaY;
9041        this.logger.log("deltaX:" + this.deltaX + ", deltaY:" + this.deltaY);
9042    },
9043
9044    /**
9045     * Sets the drag element to the location of the mousedown or click event, 
9046     * maintaining the cursor location relative to the location on the element 
9047     * that was clicked.  Override this if you want to place the element in a 
9048     * location other than where the cursor is.
9049     * @method setDragElPos
9050     * @param {int} iPageX the X coordinate of the mousedown or drag event
9051     * @param {int} iPageY the Y coordinate of the mousedown or drag event
9052     */
9053    setDragElPos: function(iPageX, iPageY) {
9054        // the first time we do this, we are going to check to make sure
9055        // the element has css positioning
9056
9057        var el = this.getDragEl();
9058        this.alignElWithMouse(el, iPageX, iPageY);
9059    },
9060
9061    /**
9062     * Sets the element to the location of the mousedown or click event, 
9063     * maintaining the cursor location relative to the location on the element 
9064     * that was clicked.  Override this if you want to place the element in a 
9065     * location other than where the cursor is.
9066     * @method alignElWithMouse
9067     * @param {HTMLElement} el the element to move
9068     * @param {int} iPageX the X coordinate of the mousedown or drag event
9069     * @param {int} iPageY the Y coordinate of the mousedown or drag event
9070     */
9071    alignElWithMouse: function(el, iPageX, iPageY) {
9072        var oCoord = this.getTargetCoord(iPageX, iPageY);
9073        // this.logger.log("****alignElWithMouse : " + el.id + ", " + aCoord + ", " + el.style.display);
9074
9075        if (!this.deltaSetXY) {
9076            var aCoord = [oCoord.x, oCoord.y];
9077            YAHOO.util.Dom.setXY(el, aCoord);
9078
9079            var newLeft = parseInt( YAHOO.util.Dom.getStyle(el, "left"), 10 );
9080            var newTop  = parseInt( YAHOO.util.Dom.getStyle(el, "top" ), 10 );
9081
9082            this.deltaSetXY = [ newLeft - oCoord.x, newTop - oCoord.y ];
9083        } else {
9084            YAHOO.util.Dom.setStyle(el, "left", (oCoord.x + this.deltaSetXY[0]) + "px");
9085            YAHOO.util.Dom.setStyle(el, "top",  (oCoord.y + this.deltaSetXY[1]) + "px");
9086        }
9087        
9088        this.cachePosition(oCoord.x, oCoord.y);
9089        var self = this;
9090        setTimeout(function() {
9091            self.autoScroll.call(self, oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
9092        }, 0);
9093    },
9094
9095    /**
9096     * Saves the most recent position so that we can reset the constraints and
9097     * tick marks on-demand.  We need to know this so that we can calculate the
9098     * number of pixels the element is offset from its original position.
9099     * @method cachePosition
9100     * @param iPageX the current x position (optional, this just makes it so we
9101     * don't have to look it up again)
9102     * @param iPageY the current y position (optional, this just makes it so we
9103     * don't have to look it up again)
9104     */
9105    cachePosition: function(iPageX, iPageY) {
9106        if (iPageX) {
9107            this.lastPageX = iPageX;
9108            this.lastPageY = iPageY;
9109        } else {
9110            var aCoord = YAHOO.util.Dom.getXY(this.getEl());
9111            this.lastPageX = aCoord[0];
9112            this.lastPageY = aCoord[1];
9113        }
9114    },
9115
9116    /**
9117     * Auto-scroll the window if the dragged object has been moved beyond the 
9118     * visible window boundary.
9119     * @method autoScroll
9120     * @param {int} x the drag element's x position
9121     * @param {int} y the drag element's y position
9122     * @param {int} h the height of the drag element
9123     * @param {int} w the width of the drag element
9124     * @private
9125     */
9126    autoScroll: function(x, y, h, w) {
9127
9128        if (this.scroll) {
9129            // The client height
9130            var clientH = this.DDM.getClientHeight();
9131
9132            // The client width
9133            var clientW = this.DDM.getClientWidth();
9134
9135            // The amt scrolled down
9136            var st = this.DDM.getScrollTop();
9137
9138            // The amt scrolled right
9139            var sl = this.DDM.getScrollLeft();
9140
9141            // Location of the bottom of the element
9142            var bot = h + y;
9143
9144            // Location of the right of the element
9145            var right = w + x;
9146
9147            // The distance from the cursor to the bottom of the visible area, 
9148            // adjusted so that we don't scroll if the cursor is beyond the
9149            // element drag constraints
9150            var toBot = (clientH + st - y - this.deltaY);
9151
9152            // The distance from the cursor to the right of the visible area
9153            var toRight = (clientW + sl - x - this.deltaX);
9154
9155            // this.logger.log( " x: " + x + " y: " + y + " h: " + h + 
9156            // " clientH: " + clientH + " clientW: " + clientW + 
9157            // " st: " + st + " sl: " + sl + " bot: " + bot + 
9158            // " right: " + right + " toBot: " + toBot + " toRight: " + toRight);
9159
9160            // How close to the edge the cursor must be before we scroll
9161            // var thresh = (document.all) ? 100 : 40;
9162            var thresh = 40;
9163
9164            // How many pixels to scroll per autoscroll op.  This helps to reduce 
9165            // clunky scrolling. IE is more sensitive about this ... it needs this 
9166            // value to be higher.
9167            var scrAmt = (document.all) ? 80 : 30;
9168
9169            // Scroll down if we are near the bottom of the visible page and the 
9170            // obj extends below the crease
9171            if ( bot > clientH && toBot < thresh ) { 
9172                window.scrollTo(sl, st + scrAmt); 
9173            }
9174
9175            // Scroll up if the window is scrolled down and the top of the object
9176            // goes above the top border
9177            if ( y < st && st > 0 && y - st < thresh ) { 
9178                window.scrollTo(sl, st - scrAmt); 
9179            }
9180
9181            // Scroll right if the obj is beyond the right border and the cursor is
9182            // near the border.
9183            if ( right > clientW && toRight < thresh ) { 
9184                window.scrollTo(sl + scrAmt, st); 
9185            }
9186
9187            // Scroll left if the window has been scrolled to the right and the obj
9188            // extends past the left border
9189            if ( x < sl && sl > 0 && x - sl < thresh ) { 
9190                window.scrollTo(sl - scrAmt, st);
9191            }
9192        }
9193    },
9194
9195    /*
9196     * Sets up config options specific to this class. Overrides
9197     * YAHOO.util.DragDrop, but all versions of this method through the 
9198     * inheritance chain are called
9199     */
9200    applyConfig: function() {
9201        YAHOO.util.DD.superclass.applyConfig.call(this);
9202        this.scroll = (this.config.scroll !== false);
9203    },
9204
9205    /*
9206     * Event that fires prior to the onMouseDown event.  Overrides 
9207     * YAHOO.util.DragDrop.
9208     */
9209    b4MouseDown: function(e) {
9210        this.setStartPosition();
9211        // this.resetConstraints();
9212        this.autoOffset(YAHOO.util.Event.getPageX(e), 
9213                            YAHOO.util.Event.getPageY(e));
9214    },
9215
9216    /*
9217     * Event that fires prior to the onDrag event.  Overrides 
9218     * YAHOO.util.DragDrop.
9219     */
9220    b4Drag: function(e) {
9221        this.setDragElPos(YAHOO.util.Event.getPageX(e), 
9222                            YAHOO.util.Event.getPageY(e));
9223    },
9224
9225    toString: function() {
9226        return ("DD " + this.id);
9227    }
9228
9229    //////////////////////////////////////////////////////////////////////////
9230    // Debugging ygDragDrop events that can be overridden
9231    //////////////////////////////////////////////////////////////////////////
9232    /*
9233    startDrag: function(x, y) {
9234        this.logger.log(this.id.toString()  + " startDrag");
9235    },
9236
9237    onDrag: function(e) {
9238        this.logger.log(this.id.toString() + " onDrag");
9239    },
9240
9241    onDragEnter: function(e, id) {
9242        this.logger.log(this.id.toString() + " onDragEnter: " + id);
9243    },
9244
9245    onDragOver: function(e, id) {
9246        this.logger.log(this.id.toString() + " onDragOver: " + id);
9247    },
9248
9249    onDragOut: function(e, id) {
9250        this.logger.log(this.id.toString() + " onDragOut: " + id);
9251    },
9252
9253    onDragDrop: function(e, id) {
9254        this.logger.log(this.id.toString() + " onDragDrop: " + id);
9255    },
9256
9257    endDrag: function(e) {
9258        this.logger.log(this.id.toString() + " endDrag");
9259    }
9260
9261    */
9262
9263/**
9264* @event mouseDownEvent
9265* @description Provides access to the mousedown event. The mousedown does not always result in a drag operation.
9266* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9267*/
9268
9269/**
9270* @event b4MouseDownEvent
9271* @description Provides access to the mousedown event, before the mouseDownEvent gets fired. Returning false will cancel the drag.
9272* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9273*/
9274
9275/**
9276* @event mouseUpEvent
9277* @description Fired from inside DragDropMgr when the drag operation is finished.
9278* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9279*/
9280
9281/**
9282* @event b4StartDragEvent
9283* @description Fires before the startDragEvent, returning false will cancel the startDrag Event.
9284* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9285*/
9286
9287/**
9288* @event startDragEvent
9289* @description Occurs after a mouse down and the drag threshold has been met. The drag threshold default is either 3 pixels of mouse movement or 1 full second of holding the mousedown. 
9290* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9291*/
9292
9293/**
9294* @event b4EndDragEvent
9295* @description Fires before the endDragEvent. Returning false will cancel.
9296* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9297*/
9298
9299/**
9300* @event endDragEvent
9301* @description Fires on the mouseup event after a drag has been initiated (startDrag fired).
9302* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9303*/
9304
9305/**
9306* @event dragEvent
9307* @description Occurs every mousemove event while dragging.
9308* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9309*/
9310/**
9311* @event b4DragEvent
9312* @description Fires before the dragEvent.
9313* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9314*/
9315/**
9316* @event invalidDropEvent
9317* @description Fires when the dragged objects is dropped in a location that contains no drop targets.
9318* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9319*/
9320/**
9321* @event b4DragOutEvent
9322* @description Fires before the dragOutEvent
9323* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9324*/
9325/**
9326* @event dragOutEvent
9327* @description Fires when a dragged object is no longer over an object that had the onDragEnter fire. 
9328* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9329*/
9330/**
9331* @event dragEnterEvent
9332* @description Occurs when the dragged object first interacts with another targettable drag and drop object.
9333* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9334*/
9335/**
9336* @event b4DragOverEvent
9337* @description Fires before the dragOverEvent.
9338* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9339*/
9340/**
9341* @event dragOverEvent
9342* @description Fires every mousemove event while over a drag and drop object.
9343* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9344*/
9345/**
9346* @event b4DragDropEvent 
9347* @description Fires before the dragDropEvent
9348* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9349*/
9350/**
9351* @event dragDropEvent
9352* @description Fires when the dragged objects is dropped on another.
9353* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9354*/
9355});
9356/**
9357 * A DragDrop implementation that inserts an empty, bordered div into
9358 * the document that follows the cursor during drag operations.  At the time of
9359 * the click, the frame div is resized to the dimensions of the linked html
9360 * element, and moved to the exact location of the linked element.
9361 *
9362 * References to the "frame" element refer to the single proxy element that
9363 * was created to be dragged in place of all DDProxy elements on the
9364 * page.
9365 *
9366 * @class DDProxy
9367 * @extends YAHOO.util.DD
9368 * @constructor
9369 * @param {String} id the id of the linked html element
9370 * @param {String} sGroup the group of related DragDrop objects
9371 * @param {object}
9371 config an object containing configurable attributes
9372 *                Valid properties for DDProxy in addition to those in DragDrop: 
9373 *                   resizeFrame, centerFrame, dragElId
9374 */
9375YAHOO.util.DDProxy = function(id, sGroup, config) {
9376    if (id) {
9377        this.init(id, sGroup, config);
9378        this.initFrame(); 
9379    }
9380};
9381
9382/**
9383 * The default drag frame div id
9384 * @property YAHOO.util.DDProxy.dragElId
9385 * @type String
9386 * @static
9387 */
9388YAHOO.util.DDProxy.dragElId = "ygddfdiv";
9389
9390YAHOO.extend(YAHOO.util.DDProxy, YAHOO.util.DD, {
9391
9392    /**
9393     * By default we resize the drag frame to be the same size as the element
9394     * we want to drag (this is to get the frame effect).  We can turn it off
9395     * if we want a different behavior.
9396     * @property resizeFrame
9397     * @type boolean
9398     */
9399    resizeFrame: true,
9400
9401    /**
9402     * By default the frame is positioned exactly where the drag element is, so
9403     * we use the cursor offset provided by YAHOO.util.DD.  Another option that works only if
9404     * you do not have constraints on the obj is to have the drag frame centered
9405     * around the cursor.  Set centerFrame to true for this effect.
9406     * @property centerFrame
9407     * @type boolean
9408     */
9409    centerFrame: false,
9410
9411    /**
9412     * Creates the proxy element if it does not yet exist
9413     * @method createFrame
9414     */
9415    createFrame: function() {
9416        var self=this, body=document.body;
9417
9418        if (!body || !body.firstChild) {
9419            setTimeout( function() { self.createFrame(); }, 50 );
9420            return;
9421        }
9422
9423        var div=this.getDragEl(), Dom=YAHOO.util.Dom;
9424
9425        if (!div) {
9426            div    = document.createElement("div");
9427            div.id = this.dragElId;
9428            var s  = div.style;
9429
9430            s.position   = "absolute";
9431            s.visibility = "hidden";
9432            s.cursor     = "move";
9433            s.border     = "2px solid #aaa";
9434            s.zIndex     = 999;
9435            s.height     = "25px";
9436            s.width      = "25px";
9437
9438            var _data = document.createElement('div');
9439            Dom.setStyle(_data, 'height', '100%');
9440            Dom.setStyle(_data, 'width', '100%');
9441            /**
9442            * If the proxy element has no background-color, then it is considered to the "transparent" by Internet Explorer.
9443            * Since it is "transparent" then the events pass through it to the iframe below.
9444            * So creating a "fake" div inside the proxy element and giving it a background-color, then setting it to an
9445            * opacity of 0, it appears to not be there, however IE still thinks that it is so the events never pass through.
9446            */
9447            Dom.setStyle(_data, 'background-color', '#ccc');
9448            Dom.setStyle(_data, 'opacity', '0');
9449            div.appendChild(_data);
9450
9451            // appendChild can blow up IE if invoked prior to the window load event
9452            // while rendering a table.  It is possible there are other scenarios 
9453            // that would cause this to happen as well.
9454            body.insertBefore(div, body.firstChild);
9455        }
9456    },
9457
9458    /**
9459     * Initialization for the drag frame element.  Must be called in the
9460     * constructor of all subclasses
9461     * @method initFrame
9462     */
9463    initFrame: function() {
9464        this.createFrame();
9465    },
9466
9467    applyConfig: function() {
9468        //this.logger.log("DDProxy applyConfig");
9469        YAHOO.util.DDProxy.superclass.applyConfig.call(this);
9470
9471        this.resizeFrame = (this.config.resizeFrame !== false);
9472        this.centerFrame = (this.config.centerFrame);
9473        this.setDragElId(this.config.dragElId || YAHOO.util.DDProxy.dragElId);
9474    },
9475
9476    /**
9477     * Resizes the drag frame to the dimensions of the clicked object, positions 
9478     * it over the object, and finally displays it
9479     * @method showFrame
9480     * @param {int} iPageX X click position
9481     * @param {int} iPageY Y click position
9482     * @private
9483     */
9484    showFrame: function(iPageX, iPageY) {
9485        var el = this.getEl();
9486        var dragEl = this.getDragEl();
9487        var s = dragEl.style;
9488
9489        this._resizeProxy();
9490
9491        if (this.centerFrame) {
9492            this.setDelta( Math.round(parseInt(s.width,  10)/2), 
9493                           Math.round(parseInt(s.height, 10)/2) );
9494        }
9495
9496        this.setDragElPos(iPageX, iPageY);
9497
9498        YAHOO.util.Dom.setStyle(dragEl, "visibility", "visible"); 
9499    },
9500
9501    /**
9502     * The proxy is automatically resized to the dimensions of the linked
9503     * element when a drag is initiated, unless resizeFrame is set to false
9504     * @method _resizeProxy
9505     * @private
9506     */
9507    _resizeProxy: function() {
9508        if (this.resizeFrame) {
9509            var DOM    = YAHOO.util.Dom;
9510            var el     = this.getEl();
9511            var dragEl = this.getDragEl();
9512
9513            var bt = parseInt( DOM.getStyle(dragEl, "borderTopWidth"    ), 10);
9514            var br = parseInt( DOM.getStyle(dragEl, "borderRightWidth"  ), 10);
9515            var bb = parseInt( DOM.getStyle(dragEl, "borderBottomWidth" ), 10);
9516            var bl = parseInt( DOM.getStyle(dragEl, "borderLeftWidth"   ), 10);
9517
9518            if (isNaN(bt)) { bt = 0; }
9519            if (isNaN(br)) { br = 0; }
9520            if (isNaN(bb)) { bb = 0; }
9521            if (isNaN(bl)) { bl = 0; }
9522
9523            this.logger.log("proxy size: " + bt + "  " + br + " " + bb + " " + bl);
9524
9525            var newWidth  = Math.max(0, el.offsetWidth  - br - bl);                                                                                           
9526            var newHeight = Math.max(0, el.offsetHeight - bt - bb);
9527
9528            this.logger.log("Resizing proxy element");
9529
9530            DOM.setStyle( dragEl, "width",  newWidth  + "px" );
9531            DOM.setStyle( dragEl, "height", newHeight + "px" );
9532        }
9533    },
9534
9535    // overrides YAHOO.util.DragDrop
9536    b4MouseDown: function(e) {
9537        this.setStartPosition();
9538        var x = YAHOO.util.Event.getPageX(e);
9539        var y = YAHOO.util.Event.getPageY(e);
9540        this.autoOffset(x, y);
9541
9542        // This causes the autoscroll code to kick off, which means autoscroll can
9543        // happen prior to the check for a valid drag handle.
9544        // this.setDragElPos(x, y);
9545    },
9546
9547    // overrides YAHOO.util.DragDrop
9548    b4StartDrag: function(x, y) {
9549        // show the drag frame
9550        this.logger.log("start drag show frame, x: " + x + ", y: " + y);
9551        this.showFrame(x, y);
9552    },
9553
9554    // overrides YAHOO.util.DragDrop
9555    b4EndDrag: function(e) {
9556        this.logger.log(this.id + " b4EndDrag");
9557        YAHOO.util.Dom.setStyle(this.getDragEl(), "visibility", "hidden"); 
9558    },
9559
9560    // overrides YAHOO.util.DragDrop
9561    // By default we try to move the element to the last location of the frame.  
9562    // This is so that the default behavior mirrors that of YAHOO.util.DD.  
9563    endDrag: function(e) {
9564        var DOM = YAHOO.util.Dom;
9565        this.logger.log(this.id + " endDrag");
9566        var lel = this.getEl();
9567        var del = this.getDragEl();
9568
9569        // Show the drag frame briefly so we can get its position
9570        // del.style.visibility = "";
9571        DOM.setStyle(del, "visibility", ""); 
9572
9573        // Hide the linked element before the move to get around a Safari 
9574        // rendering bug.
9575        //lel.style.visibility = "hidden";
9576        DOM.setStyle(lel, "visibility", "hidden"); 
9577        YAHOO.util.DDM.moveToEl(lel, del);
9578        //del.style.visibility = "hidden";
9579        DOM.setStyle(del, "visibility", "hidden"); 
9580        //lel.style.visibility = "";
9581        DOM.setStyle(lel, "visibility", ""); 
9582    },
9583
9584    toString: function() {
9585        return ("DDProxy " + this.id);
9586    }
9587/**
9588* @event mouseDownEvent
9589* @description Provides access to the mousedown event. The mousedown does not always result in a drag operation.
9590* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9591*/
9592
9593/**
9594* @event b4MouseDownEvent
9595* @description Provides access to the mousedown event, before the mouseDownEvent gets fired. Returning false will cancel the drag.
9596* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9597*/
9598
9599/**
9600* @event mouseUpEvent
9601* @description Fired from inside DragDropMgr when the drag operation is finished.
9602* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9603*/
9604
9605/**
9606* @event b4StartDragEvent
9607* @description Fires before the startDragEvent, returning false will cancel the startDrag Event.
9608* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9609*/
9610
9611/**
9612* @event startDragEvent
9613* @description Occurs after a mouse down and the drag threshold has been met. The drag threshold default is either 3 pixels of mouse movement or 1 full second of holding the mousedown. 
9614* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9615*/
9616
9617/**
9618* @event b4EndDragEvent
9619* @description Fires before the endDragEvent. Returning false will cancel.
9620* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9621*/
9622
9623/**
9624* @event endDragEvent
9625* @description Fires on the mouseup event after a drag has been initiated (startDrag fired).
9626* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9627*/
9628
9629/**
9630* @event dragEvent
9631* @description Occurs every mousemove event while dragging.
9632* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9633*/
9634/**
9635* @event b4DragEvent
9636* @description Fires before the dragEvent.
9637* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9638*/
9639/**
9640* @event invalidDropEvent
9641* @description Fires when the dragged objects is dropped in a location that contains no drop targets.
9642* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9643*/
9644/**
9645* @event b4DragOutEvent
9646* @description Fires before the dragOutEvent
9647* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9648*/
9649/**
9650* @event dragOutEvent
9651* @description Fires when a dragged object is no longer over an object that had the onDragEnter fire. 
9652* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9653*/
9654/**
9655* @event dragEnterEvent
9656* @description Occurs when the dragged object first interacts with another targettable drag and drop object.
9657* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9658*/
9659/**
9660* @event b4DragOverEvent
9661* @description Fires before the dragOverEvent.
9662* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9663*/
9664/**
9665* @event dragOverEvent
9666* @description Fires every mousemove event while over a drag and drop object.
9667* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9668*/
9669/**
9670* @event b4DragDropEvent 
9671* @description Fires before the dragDropEvent
9672* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9673*/
9674/**
9675* @event dragDropEvent
9676* @description Fires when the dragged objects is dropped on another.
9677* @type YAHOO.util.CustomEvent See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for more information on listening for this event.
9678*/
9679
9680});
9681/**
9682 * A DragDrop implementation that does not move, but can be a drop 
9683 * target.  You would get the same result by simply omitting implementation 
9684 * for the event callbacks, but this way we reduce the processing cost of the 
9685 * event listener and the callbacks.
9686 * @class DDTarget
9687 * @extends YAHOO.util.DragDrop 
9688 * @constructor
9689 * @param {String} id the id of the element that is a drop target
9690 * @param {String} sGroup the group of related DragDrop objects
9691 * @param {object}
9691 config an object containing configurable attributes
9692 *                 Valid properties for DDTarget in addition to those in 
9693 *                 DragDrop: 
9694 *                    none
9695 */
9696YAHOO.util.DDTarget = function(id, sGroup, config) {
9697    if (id) {
9698        this.initTarget(id, sGroup, config);
9699    }
9700};
9701
9702// YAHOO.util.DDTarget.prototype = new YAHOO.util.DragDrop();
9703YAHOO.extend(YAHOO.util.DDTarget, YAHOO.util.DragDrop, {
9704    toString: function() {
9705        return ("DDTarget " + this.id);
9706    }
9707});
9708YAHOO.register("dragdrop", YAHOO.util.DragDropMgr, {version: "2.9.0", build: "2800"});
9709/*
9710Copyright (c) 2011, Yahoo! Inc. All rights reserved.
9711Code licensed under the BSD License:
9712http://developer.yahoo.com/yui/license.html
9713version: 2.9.0
9714*/
9715(function() {
9716
9717var Y = YAHOO.util;
9718
9719/*
9720Copyright (c) 2006, Yahoo! Inc. All rights reserved.
9721Code licensed under the BSD License:
9722http://developer.yahoo.net/yui/license.txt
9723*/
9724
9725/**
9726 * The animation module provides allows effects to be added to HTMLElements.
9727 * @module animation
9728 * @requires yahoo, event, dom
9729 */
9730
9731/**
9732 *
9733 * Base animation class that provides the interface for building animated effects.
9734 * <p>Usage: var myAnim = new YAHOO.util.Anim(el, { width: { from: 10, to: 100 } }, 1, YAHOO.util.Easing.easeOut);</p>
9735 * @class Anim
9736 * @namespace YAHOO.util
9737 * @requires YAHOO.util.AnimMgr
9738 * @requires YAHOO.util.Easing
9739 * @requires YAHOO.util.Dom
9740 * @requires YAHOO.util.Event
9741 * @requires YAHOO.util.CustomEvent
9742 * @constructor
9743 * @param {String | HTMLElement} el Reference to the element that will be animated
9744 * @param {Object} attributes The attribute(s) to be animated.  
9745 * Each attribute is an object with at minimum a "to" or "by" member defined.  
9746 * Additional optional members are "from" (defaults to current value), "units" (defaults to "px").  
9747 * All attribute names use camelCase.
9748 * @param {Number} duration (optional, defaults to 1 second) Length of animation (frames or seconds), defaults to time-based
9749 * @param {Function} method (optional, defaults to YAHOO.util.Easing.easeNone) Computes the values that are applied to the attributes per frame (generally a YAHOO.util.Easing method)
9750 */
9751
9752var Anim = function(el, attributes, duration, method) {
9753    if (!el) {
9754        YAHOO.log('element required to create Anim instance', 'error', 'Anim');
9755    }
9756    this.init(el, attributes, duration, method); 
9757};
9758
9759Anim.NAME = 'Anim';
9760
9761Anim.prototype = {
9762    /**
9763     * Provides a readable name for the Anim instance.
9764     * @method toString
9765     * @return {String}
9766     */
9767    toString: function() {
9768        var el = this.getEl() || {};
9769        var id = el.id || el.tagName;
9770        return (this.constructor.NAME + ': ' + id);
9771    },
9772    
9773    patterns: { // cached for performance
9774        noNegatives:        /width|height|opacity|padding/i, // keep at zero or above
9775        offsetAttribute:  /^((width|height)|(top|left))$/, // use offsetValue as default
9776        defaultUnit:        /width|height|top$|bottom$|left$|right$/i, // use 'px' by default
9777        offsetUnit:         /\d+(em|%|en|ex|pt|in|cm|mm|pc)$/i // IE may return these, so convert these to offset
9778    },
9779    
9780    /**
9781     * Returns the value computed by the animation's "method".
9782     * @method doMethod
9783     * @param {String} attr The name of the attribute.
9784     * @param {Number} start The value this attribute should start from for this animation.
9785     * @param {Number} end  The value this attribute should end at for this animation.
9786     * @return {Number} The Value to be applied to the attribute.
9787     */
9788    doMethod: function(attr, start, end) {
9789        return this.method(this.currentFrame, start, end - start, this.totalFrames);
9790    },
9791    
9792    /**
9793     * Applies a value to an attribute.
9794     * @method setAttribute
9795     * @param {String} attr The name of the attribute.
9796     * @param {Number} val The value to be applied to the attribute.
9797     * @param {String} unit The unit ('px', '%', etc.) of the value.
9798     */
9799    setAttribute: function(attr, val, unit) {
9800        var el = this.getEl();
9801        if ( this.patterns.noNegatives.test(attr) ) {
9802            val = (val > 0) ? val : 0;
9803        }
9804
9805        if (attr in el && !('style' in el && attr in el.style)) {
9806            el[attr] = val;
9807        } else {
9808            Y.Dom.setStyle(el, attr, val + unit);
9809        }
9810    },                        
9811    
9812    /**
9813     * Returns current value of the attribute.
9814     * @method getAttribute
9815     * @param {String} attr The name of the attribute.
9816     * @return {Number} val The current value of the attribute.
9817     */
9818    getAttribute: function(attr) {
9819        var el = this.getEl();
9820        var val = Y.Dom.getStyle(el, attr);
9821
9822        if (val !== 'auto' && !this.patterns.offsetUnit.test(val)) {
9823            return parseFloat(val);
9824        }
9825        
9826        var a = this.patterns.offsetAttribute.exec(attr) || [];
9827        var pos = !!( a[3] ); // top or left
9828        var box = !!( a[2] ); // width or height
9829        
9830        if ('style' in el) {
9831            // use offsets for width/height and abs pos top/left
9832            if ( box || (Y.Dom.getStyle(el, 'position') == 'absolute' && pos) ) {
9833                val = el['offset' + a[0].charAt(0).toUpperCase() + a[0].substr(1)];
9834            } else { // default to zero for other 'auto'
9835                val = 0;
9836            }
9837        } else if (attr in el) {
9838            val = el[attr];
9839        }
9840
9841        return val;
9842    },
9843    
9844    /**
9845     * Returns the unit to use when none is supplied.
9846     * @method getDefaultUnit
9847     * @param {attr} attr The name of the attribute.
9848     * @return {String} The default unit to be used.
9849     */
9850    getDefaultUnit: function(attr) {
9851         if ( this.patterns.defaultUnit.test(attr) ) {
9852            return 'px';
9853         }
9854         
9855         return '';
9856    },
9857        
9858    /**
9859     * Sets the actual values to be used during the animation.  Should only be needed for subclass use.
9860     * @method setRuntimeAttribute
9861     * @param {Object} attr The attribute object
9862     * @private 
9863     */
9864    setRuntimeAttribute: function(attr) {
9865        var start;
9866        var end;
9867        var attributes = this.attributes;
9868
9869        this.runtimeAttributes[attr] = {};
9870        
9871        var isset = function(prop) {
9872            return (typeof prop !== 'undefined');
9873        };
9874        
9875        if ( !isset(attributes[attr]['to']) && !isset(attributes[attr]['by']) ) {
9876            return false; // note return; nothing to animate to
9877        }
9878        
9879        start = ( isset(attributes[attr]['from']) ) ? attributes[attr]['from'] : this.getAttribute(attr);
9880
9881        // To beats by, per SMIL 2.1 spec
9882        if ( isset(attributes[attr]['to']) ) {
9883            end = attributes[attr]['to'];
9884        } else if ( isset(attributes[attr]['by']) ) {
9885            if (start.constructor == Array) {
9886                end = [];
9887                for (var i = 0, len = start.length; i < len; ++i) {
9888                    end[i] = start[i] + attributes[attr]['by'][i] * 1; // times 1 to cast "by" 
9889                }
9890            } else {
9891                end = start + attributes[attr]['by'] * 1;
9892            }
9893        }
9894        
9895        this.runtimeAttributes[attr].start = start;
9896        this.runtimeAttributes[attr].end = end;
9897
9898        // set units if needed
9899        this.runtimeAttributes[attr].unit = ( isset(attributes[attr].unit) ) ?
9900                attributes[attr]['unit'] : this.getDefaultUnit(attr);
9901        return true;
9902    },
9903
9904    /**
9905     * Constructor for Anim instance.
9906     * @method init
9907     * @param {String | HTMLElement} el Reference to the element that will be animated
9908     * @param {Object} attributes The attribute(s) to be animated.  
9909     * Each attribute is an object with at minimum a "to" or "by" member defined.  
9910     * Additional optional members are "from" (defaults to current value), "units" (defaults to "px").  
9911     * All attribute names use camelCase.
9912     * @param {Number} duration (optional, defaults to 1 second) Length of animation (frames or seconds), defaults to time-based
9913     * @param {Function} method (optional, defaults to YAHOO.util.Easing.easeNone) Computes the values that are applied to the attributes per frame (generally a YAHOO.util.Easing method)
9914     */ 
9915    init: function(el, attributes, duration, method) {
9916        /**
9917         * Whether or not the animation is running.
9918         * @property isAnimated
9919         * @private
9920         * @type Boolean
9921         */
9922        var isAnimated = false;
9923        
9924        /**
9925         * A Date object that is created when the animation begins.
9926         * @property startTime
9927         * @private
9928         * @type Date
9929         */
9930        var startTime = null;
9931        
9932        /**
9933         * The number of frames this animation was able to execute.
9934         * @property actualFrames
9935         * @private
9936         * @type Int
9937         */
9938        var actualFrames = 0; 
9939
9940        /**
9941         * The element to be animated.
9942         * @property el
9943         * @private
9944         * @type HTMLElement
9945         */
9946        el = Y.Dom.get(el);
9947        
9948        /**
9949         * The collection of attributes to be animated.  
9950         * Each attribute must have at least a "to" or "by" defined in order to animate.  
9951         * If "to" is supplied, the animation will end with the attribute at that value.  
9952         * If "by" is supplied, the animation will end at that value plus its starting value. 
9953         * If both are supplied, "to" is used, and "by" is ignored. 
9954         * Optional additional member include "from" (the value the attribute should start animating from, defaults to current value), and "unit" (the units to apply to the values).
9955         * @property attributes
9956         * @type Object
9957         */
9958        this.attributes = attributes || {};
9959        
9960        /**
9961         * The length of the animation.  Defaults to "1" (second).
9962         * @property duration
9963         * @type Number
9964         */
9965        this.duration = !YAHOO.lang.isUndefined(duration) ? duration : 1;
9966        
9967        /**
9968         * The method that will provide values to the attribute(s) during the animation. 
9969         * Defaults to "YAHOO.util.Easing.easeNone".
9970         * @property method
9971         * @type Function
9972         */
9973        this.method = method || Y.Easing.easeNone;
9974
9975        /**
9976         * Whether or not the duration should be treated as seconds.
9977         * Defaults to true.
9978         * @property useSeconds
9979         * @type Boolean
9980         */
9981        this.useSeconds = true; // default to seconds
9982        
9983        /**
9984         * The location of the current animation on the timeline.
9985         * In time-based animations, this is used by AnimMgr to ensure the animation finishes on time.
9986         * @property currentFrame
9987         * @type Int
9988         */
9989        this.currentFrame = 0;
9990        
9991        /**
9992         * The total number of frames to be executed.
9993         * In time-based animations, this is used by AnimMgr to ensure the animation finishes on time.
9994         * @property totalFrames
9995         * @type Int
9996         */
9997        this.totalFrames = Y.AnimMgr.fps;
9998        
9999        /**
10000         * Changes the animated element
10001         * @method setEl
10002         */
10003        this.setEl = function(element) {
10004            el = Y.Dom.get(element);
10005        };
10006        
10007        /**
10008         * Returns a reference to the animated element.
10009         * @method getEl
10010         * @return {HTMLElement}
10011         */
10012        this.getEl = function() { return el; };
10013        
10014        /**
10015         * Checks whether the element is currently animated.
10016         * @method isAnimated
10017         * @return {Boolean} current value of isAnimated.     
10018         */
10019        this.isAnimated = function() {
10020            return isAnimated;
10021        };
10022        
10023        /**
10024         * Returns the animation start time.
10025         * @method getStartTime
10026         * @return {Date} current value of startTime.      
10027         */
10028        this.getStartTime = function() {
10029            return startTime;
10030        };        
10031        
10032        this.runtimeAttributes = {};
10033        
10034        var logger = {};
10035        logger.log = function() {YAHOO.log.apply(window, arguments)};
10036        
10037        logger.log('creating new instance of ' + this);
10038        
10039        /**
10040         * Starts the animation by registering it with the animation manager. 
10041         * @method animate  
10042         */
10043        this.animate = function() {
10044            if ( this.isAnimated() ) {
10045                return false;
10046            }
10047            
10048            this.currentFrame = 0;
10049            
10050            this.totalFrames = ( this.useSeconds ) ? Math.ceil(Y.AnimMgr.fps * this.duration) : this.duration;
10051    
10052            if (this.duration === 0 && this.useSeconds) { // jump to last frame if zero second duration 
10053                this.totalFrames = 1; 
10054            }
10055            Y.AnimMgr.registerElement(this);
10056            return true;
10057        };
10058          
10059        /**
10060         * Stops the animation.  Normally called by AnimMgr when animation completes.
10061         * @method stop
10062         * @param {Boolean} finish (optional) If true, animation will jump to final frame.
10063         */ 
10064        this.stop = function(finish) {
10065            if (!this.isAnimated()) { // nothing to stop
10066                return false;
10067            }
10068
10069            if (finish) {
10070                 this.currentFrame = this.totalFrames;
10071                 this._onTween.fire();
10072            }
10073            Y.AnimMgr.stop(this);
10074        };
10075        
10076        this._handleStart = function() {            
10077            this.onStart.fire();
10078            
10079            this.runtimeAttributes = {};
10080            for (var attr in this.attributes) {
10081                if (this.attributes.hasOwnProperty(attr)) {
10082                    this.setRuntimeAttribute(attr);
10083                }
10084            }
10085            
10086            isAnimated = true;
10087            actualFrames = 0;
10088            startTime = new Date(); 
10089        };
10090        
10091        /**
10092         * Feeds the starting and ending values for each animated attribute to doMethod once per frame, then applies the resulting value to the attribute(s).
10093         * @private
10094         */
10095         
10096        this._handleTween = function() {
10097            var data = {
10098                duration: new Date() - this.getStartTime(),
10099                currentFrame: this.currentFrame
10100            };
10101            
10102            data.toString = function() {
10103                return (
10104                    'duration: ' + data.duration +
10105                    ', currentFrame: ' + data.currentFrame
10106                );
10107            };
10108            
10109            this.onTween.fire(data);
10110            
10111            var runtimeAttributes = this.runtimeAttributes;
10112            
10113            for (var attr in runtimeAttributes) {
10114                if (runtimeAttributes.hasOwnProperty(attr)) {
10115                    this.setAttribute(attr, this.doMethod(attr, runtimeAttributes[attr].start, runtimeAttributes[attr].end), runtimeAttributes[attr].unit); 
10116                }
10117            }
10118            
10119            this.afterTween.fire(data);
10120            
10121            actualFrames += 1;
10122        };
10123        
10124        this._handleComplete = function() {
10125            var actual_duration = (new Date() - startTime) / 1000 ;
10126            
10127            var data = {
10128                duration: actual_duration,
10129                frames: actualFrames,
10130                fps: actualFrames / actual_duration
10131            };
10132            
10133            data.toString = function() {
10134                return (
10135                    'duration: ' + data.duration +
10136                    ', frames: ' + data.frames +
10137                    ', fps: ' + data.fps
10138                );
10139            };
10140            
10141            isAnimated = false;
10142            actualFrames = 0;
10143            this.onComplete.fire(data);
10144        };
10145        
10146        /**
10147         * Custom event that fires after onStart, useful in subclassing
10148         * @private
10149         */    
10150        this._onStart = new Y.CustomEvent('_start', this, true);
10151
10152        /**
10153         * Custom event that fires when animation begins
10154         * Listen via subscribe method (e.g. myAnim.onStart.subscribe(someFunction)
10155         * @event onStart
10156         */    
10157        this.onStart = new Y.CustomEvent('start', this);
10158        
10159        /**
10160         * Custom event that fires between each frame
10161         * Listen via subscribe method (e.g. myAnim.onTween.subscribe(someFunction)
10162         * @event onTween
10163         */
10164        this.onTween = new Y.CustomEvent('tween', this);
10165        
10166        /**
10167         * Custom event that fires between each frame
10168         * Listen via subscribe method (e.g. myAnim.afterTween.subscribe(someFunction)
10169         * @event afterTween
10170         */
10171        this.afterTween = new Y.CustomEvent('afterTween', this);
10172        
10173        /**
10174         * Custom event that fires after onTween
10175         * @private
10176         */
10177        this._onTween = new Y.CustomEvent('_tween', this, true);
10178        
10179        /**
10180         * Custom event that fires when animation ends
10181         * Listen via subscribe method (e.g. myAnim.onComplete.subscribe(someFunction)
10182         * @event onComplete
10183         */
10184        this.onComplete = new Y.CustomEvent('complete', this);
10185        /**
10186         * Custom event that fires after onComplete
10187         * @private
10188         */
10189        this._onComplete = new Y.CustomEvent('_complete', this, true);
10190
10191        this._onStart.subscribe(this._handleStart);
10192        this._onTween.subscribe(this._handleTween);
10193        this._onComplete.subscribe(this._handleComplete);
10194    }
10195};
10196
10197    Y.Anim = Anim;
10198})();
10199/**
10200 * Handles animation queueing and threading.
10201 * Used by Anim and subclasses.
10202 * @class AnimMgr
10203 * @namespace YAHOO.util
10204 */
10205YAHOO.util.AnimMgr = new function() {
10206    /** 
10207     * Reference to the animation Interval.
10208     * @property thread
10209     * @private
10210     * @type Int
10211     */
10212    var thread = null;
10213    
10214    /** 
10215     * The current queue of registered animation objects.
10216     * @property queue
10217     * @private
10218     * @type Array
10219     */    
10220    var queue = [];
10221
10222    /** 
10223     * The number of active animations.
10224     * @property tweenCount
10225     * @private
10226     * @type Int
10227     */        
10228    var tweenCount = 0;
10229
10230    /** 
10231     * Base frame rate (frames per second). 
10232     * Arbitrarily high for better x-browser calibration (slower browsers drop more frames).
10233     * @property fps
10234     * @type Int
10235     * 
10236     */
10237    this.fps = 1000;
10238
10239    /** 
10240     * Interval delay in milliseconds, defaults to fastest possible.
10241     * @property delay
10242     * @type Int
10243     * 
10244     */
10245    this.delay = 20;
10246
10247    /**
10248     * Adds an animation instance to the animation queue.
10249     * All animation instances must be registered in order to animate.
10250     * @method registerElement
10251     * @param {object} tween The Anim instance to be be registered
10252     */
10253    this.registerElement = function(tween) {
10254        queue[queue.length] = tween;
10255        tweenCount += 1;
10256        tween._onStart.fire();
10257        this.start();
10258    };
10259    
10260    var _unregisterQueue = [];
10261    var _unregistering = false;
10262
10263    var doUnregister = function() {
10264        var next_args = _unregisterQueue.shift();
10265        unRegister.apply(YAHOO.util.AnimMgr,next_args);
10266        if (_unregisterQueue.length) {
10267            arguments.callee();
10268        }
10269    };
10270
10271    var unRegister = function(tween, index) {
10272        index = index || getIndex(tween);
10273        if (!tween.isAnimated() || index === -1) {
10274            return false;
10275        }
10276        
10277        tween._onComplete.fire();
10278        queue.splice(index, 1);
10279
10280        tweenCount -= 1;
10281        if (tweenCount <= 0) {
10282            this.stop();
10283        }
10284
10285        return true;
10286    };
10287
10288    /**
10289     * removes an animation instance from the animation queue.
10290     * All animation instances must be registered in order to animate.
10291     * @method unRegister
10292     * @param {object} tween The Anim instance to be be registered
10293     * @param {Int} index The index of the Anim instance
10294     * @private
10295     */
10296    this.unRegister = function() {
10297        _unregisterQueue.push(arguments);
10298        if (!_unregistering) {
10299            _unregistering = true;
10300            doUnregister();
10301            _unregistering = false;
10302        }
10303    }
10304
10305    /**
10306     * Starts the animation thread.
10307	* Only one thread can run at a time.
10308     * @method start
10309     */    
10310    this.start = function() {
10311        if (thread === null) {
10312            thread = setInterval(this.run, this.delay);
10313        }
10314    };
10315
10316    /**
10317     * Stops the animation thread or a specific animation instance.
10318     * @method stop
10319     * @param {object} tween A specific Anim instance to stop (optional)
10320     * If no instance given, Manager stops thread and all animations.
10321     */    
10322    this.stop = function(tween) {
10323        if (!tween) {
10324            clearInterval(thread);
10325            
10326            for (var i = 0, len = queue.length; i < len; ++i) {
10327                this.unRegister(queue[0], 0);  
10328            }
10329
10330            queue = [];
10331            thread = null;
10332            tweenCount = 0;
10333        }
10334        else {
10335            this.unRegister(tween);
10336        }
10337    };
10338    
10339    /**
10340     * Called per Interval to handle each animation frame.
10341     * @method run
10342     */    
10343    this.run = function() {
10344        for (var i = 0, len = queue.length; i < len; ++i) {
10345            var tween = queue[i];
10346            if ( !tween || !tween.isAnimated() ) { continue; }
10347
10348            if (tween.currentFrame < tween.totalFrames || tween.totalFrames === null)
10349            {
10350                tween.currentFrame += 1;
10351                
10352                if (tween.useSeconds) {
10353                    correctFrame(tween);
10354                }
10355                tween._onTween.fire();          
10356            }
10357            else { YAHOO.util.AnimMgr.stop(tween, i); }
10358        }
10359    };
10360    
10361    var getIndex = function(anim) {
10362        for (var i = 0, len = queue.length; i < len; ++i) {
10363            if (queue[i] === anim) {
10364                return i; // note return;
10365            }
10366        }
10367        return -1;
10368    };
10369    
10370    /**
10371     * On the fly frame correction to keep animation on time.
10372     * @method correctFrame
10373     * @private
10374     * @param {Object} tween The Anim instance being corrected.
10375     */
10376    var correctFrame = function(tween) {
10377        var frames = tween.totalFrames;
10378        var frame = tween.currentFrame;
10379        var expected = (tween.currentFrame * tween.duration * 1000 / tween.totalFrames);
10380        var elapsed = (new Date() - tween.getStartTime());
10381        var tweak = 0;
10382        
10383        if (elapsed < tween.duration * 1000) { // check if falling behind
10384            tweak = Math.round((elapsed / expected - 1) * tween.currentFrame);
10385        } else { // went over duration, so jump to end
10386            tweak = frames - (frame + 1); 
10387        }
10388        if (tweak > 0 && isFinite(tweak)) { // adjust if needed
10389            if (tween.currentFrame + tweak >= frames) {// dont go past last frame
10390                tweak = frames - (frame + 1);
10391            }
10392            
10393            tween.currentFrame += tweak;      
10394        }
10395    };
10396    this._queue = queue;
10397    this._getIndex = getIndex;
10398};
10399/**
10400 * Used to calculate Bezier splines for any number of control points.
10401 * @class Bezier
10402 * @namespace YAHOO.util
10403 *
10404 */
10405YAHOO.util.Bezier = new function() {
10406    /**
10407     * Get the current position of the animated element based on t.
10408     * Each point is an array of "x" and "y" values (0 = x, 1 = y)
10409     * At least 2 points are required (start and end).
10410     * First point is start. Last point is end.
10411     * Additional control points are optional.     
10412     * @method getPosition
10413     * @param {Array} points An array containing Bezier points
10414     * @param {Number} t A number between 0 and 1 which is the basis for determining current position
10415     * @return {Array} An array containing int x and y member data
10416     */
10417    this.getPosition = function(points, t) {  
10418        var n = points.length;
10419        var tmp = [];
10420
10421        for (var i = 0; i < n; ++i){
10422            tmp[i] = [points[i][0], points[i][1]]; // save input
10423        }
10424        
10425        for (var j = 1; j < n; ++j) {
10426            for (i = 0; i < n - j; ++i) {
10427                tmp[i][0] = (1 - t) * tmp[i][0] + t * tmp[parseInt(i + 1, 10)][0];
10428                tmp[i][1] = (1 - t) * tmp[i][1] + t * tmp[parseInt(i + 1, 10)][1]; 
10429            }
10430        }
10431    
10432        return [ tmp[0][0], tmp[0][1] ]; 
10433    
10434    };
10435};
10436(function() {
10437/**
10438 * Anim subclass for color transitions.
10439 * <p>Usage: <code>var myAnim = new Y.ColorAnim(el, { backgroundColor: { from: '#FF0000', to: '#FFFFFF' } }, 1, Y.Easing.easeOut);</code> Color values can be specified with either 112233, #112233, 
10440 * [255,255,255], or rgb(255,255,255)</p>
10441 * @class ColorAnim
10442 * @namespace YAHOO.util
10443 * @requires YAHOO.util.Anim
10444 * @requires YAHOO.util.AnimMgr
10445 * @requires YAHOO.util.Easing
10446 * @requires YAHOO.util.Bezier
10447 * @requires YAHOO.util.Dom
10448 * @requires YAHOO.util.Event
10449 * @constructor
10450 * @extends YAHOO.util.Anim
10451 * @param {HTMLElement | String} el Reference to the element that will be animated
10452 * @param {Object} attributes The attribute(s) to be animated.
10453 * Each attribute is an object with at minimum a "to" or "by" member defined.
10454 * Additional optional members are "from" (defaults to current value), "units" (defaults to "px").
10455 * All attribute names use camelCase.
10456 * @param {Number} duration (optional, defaults to 1 second) Length of animation (frames or seconds), defaults to time-based
10457 * @param {Function} method (optional, defaults to YAHOO.util.Easing.easeNone) Computes the values that are applied to the attributes per frame (generally a YAHOO.util.Easing method)
10458 */
10459    var ColorAnim = function(el, attributes, duration,  method) {
10460        ColorAnim.superclass.constructor.call(this, el, attributes, duration, method);
10461    };
10462    
10463    ColorAnim.NAME = 'ColorAnim';
10464
10465    ColorAnim.DEFAULT_BGCOLOR = '#fff';
10466    // shorthand
10467    var Y = YAHOO.util;
10468    YAHOO.extend(ColorAnim, Y.Anim);
10469
10470    var superclass = ColorAnim.superclass;
10471    var proto = ColorAnim.prototype;
10472    
10473    proto.patterns.color = /color$/i;
10474    proto.patterns.rgb            = /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i;
10475    proto.patterns.hex            = /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i;
10476    proto.patterns.hex3          = /^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i;
10477    proto.patterns.transparent = /^transparent|rgba\(0, 0, 0, 0\)$/; // need rgba for safari
10478    
10479    /**
10480     * Attempts to parse the given string and return a 3-tuple.
10481     * @method parseColor
10482     * @param {String} s The string to parse.
10483     * @return {Array} The 3-tuple of rgb values.
10484     */
10485    proto.parseColor = function(s) {
10486        if (s.length == 3) { return s; }
10487    
10488        var c = this.patterns.hex.exec(s);
10489        if (c && c.length == 4) {
10490            return [ parseInt(c[1], 16), parseInt(c[2], 16), parseInt(c[3], 16) ];
10491        }
10492    
10493        c = this.patterns.rgb.exec(s);
10494        if (c && c.length == 4) {
10495            return [ parseInt(c[1], 10), parseInt(c[2], 10), parseInt(c[3], 10) ];
10496        }
10497    
10498        c = this.patterns.hex3.exec(s);
10499        if (c && c.length == 4) {
10500            return [ parseInt(c[1] + c[1], 16), parseInt(c[2] + c[2], 16), parseInt(c[3] + c[3], 16) ];
10501        }
10502        
10503        return null;
10504    };
10505
10506    proto.getAttribute = function(attr) {
10507        var el = this.getEl();
10508        if (this.patterns.color.test(attr) ) {
10509            var val = YAHOO.util.Dom.getStyle(el, attr);
10510            
10511            var that = this;
10512            if (this.patterns.transparent.test(val)) { // bgcolor default
10513                var parent = YAHOO.util.Dom.getAncestorBy(el, function(node) {
10514                    return !that.patterns.transparent.test(val);
10515                });
10516
10517                if (parent) {
10518                    val = Y.Dom.getStyle(parent, attr);
10519                } else {
10520                    val = ColorAnim.DEFAULT_BGCOLOR;
10521                }
10522            }
10523        } else {
10524            val = superclass.getAttribute.call(this, attr);
10525        }
10526
10527        return val;
10528    };
10529    
10530    proto.doMethod = function(attr, start, end) {
10531        var val;
10532    
10533        if ( this.patterns.color.test(attr) ) {
10534            val = [];
10535            for (var i = 0, len = start.length; i < len; ++i) {
10536                val[i] = superclass.doMethod.call(this, attr, start[i], end[i]);
10537            }
10538            
10539            val = 'rgb('+Math.floor(val[0])+','+Math.floor(val[1])+','+Math.floor(val[2])+')';
10540        }
10541        else {
10542            val = superclass.doMethod.call(this, attr, start, end);
10543        }
10544
10545        return val;
10546    };
10547
10548    proto.setRuntimeAttribute = function(attr) {
10549        superclass.setRuntimeAttribute.call(this, attr);
10550        
10551        if ( this.patterns.color.test(attr) ) {
10552            var attributes = this.attributes;
10553            var start = this.parseColor(this.runtimeAttributes[attr].start);
10554            var end = this.parseColor(this.runtimeAttributes[attr].end);
10555            // fix colors if going "by"
10556            if ( typeof attributes[attr]['to'] === 'undefined' && typeof attributes[attr]['by'] !== 'undefined' ) {
10557                end = this.parseColor(attributes[attr].by);
10558            
10559                for (var i = 0, len = start.length; i < len; ++i) {
10560                    end[i] = start[i] + end[i];
10561                }
10562            }
10563            
10564            this.runtimeAttributes[attr].start = start;
10565            this.runtimeAttributes[attr].end = end;
10566        }
10567    };
10568
10569    Y.ColorAnim = ColorAnim;
10570})();
10571/*!
10572TERMS OF USE - EASING EQUATIONS
10573Open source under the BSD License.
10574Copyright 2001 Robert Penner All rights reserved.
10575
10576Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
10577
10578 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
10579 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
10580 * Neither the name of the author nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10581
10582THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10583*/
10584
10585/**
10586 * Singleton that determines how an animation proceeds from start to end.
10587 * @class Easing
10588 * @namespace YAHOO.util
10589*/
10590
10591YAHOO.util.Easing = {
10592
10593    /**
10594     * Uniform speed between points.
10595     * @method easeNone
10596     * @param {Number} t Time value used to compute current value
10597     * @param {Number} b Starting value
10598     * @param {Number} c Delta between start and end values
10599     * @param {Number} d Total length of animation
10600     * @return {Number} The computed value for the current animation frame
10601     */
10602    easeNone: function (t, b, c, d) {
10603    	return c*t/d + b;
10604    },
10605    
10606    /**
10607     * Begins slowly and accelerates towards end.
10608     * @method easeIn
10609     * @param {Number} t Time value used to compute current value
10610     * @param {Number} b Starting value
10611     * @param {Number} c Delta between start and end values
10612     * @param {Number} d Total length of animation
10613     * @return {Number} The computed value for the current animation frame
10614     */
10615    easeIn: function (t, b, c, d) {
10616    	return c*(t/=d)*t + b;
10617    },
10618
10619    /**
10620     * Begins quickly and decelerates towards end.
10621     * @method easeOut
10622     * @param {Number} t Time value used to compute current value
10623     * @param {Number} b Starting value
10624     * @param {Number} c Delta between start and end values
10625     * @param {Number} d Total length of animation
10626     * @return {Number} The computed value for the current animation frame
10627     */
10628    easeOut: function (t, b, c, d) {
10629    	return -c *(t/=d)*(t-2) + b;
10630    },
10631    
10632    /**
10633     * Begins slowly and decelerates towards end.
10634     * @method easeBoth
10635     * @param {Number} t Time value used to compute current value
10636     * @param {Number} b Starting value
10637     * @param {Number} c Delta between start and end values
10638     * @param {Number} d Total length of animation
10639     * @return {Number} The computed value for the current animation frame
10640     */
10641    easeBoth: function (t, b, c, d) {
10642    	if ((t/=d/2) < 1) {
10643            return c/2*t*t + b;
10644        }
10645        
10646    	return -c/2 * ((--t)*(t-2) - 1) + b;
10647    },
10648    
10649    /**
10650     * Begins slowly and accelerates towards end.
10651     * @method easeInStrong
10652     * @param {Number} t Time value used to compute current value
10653     * @param {Number} b Starting value
10654     * @param {Number} c Delta between start and end values
10655     * @param {Number} d Total length of animation
10656     * @return {Number} The computed value for the current animation frame
10657     */
10658    easeInStrong: function (t, b, c, d) {
10659    	return c*(t/=d)*t*t*t + b;
10660    },
10661    
10662    /**
10663     * Begins quickly and decelerates towards end.
10664     * @method easeOutStrong
10665     * @param {Number} t Time value used to compute current value
10666     * @param {Number} b Starting value
10667     * @param {Number} c Delta between start and end values
10668     * @param {Number} d Total length of animation
10669     * @return {Number} The computed value for the current animation frame
10670     */
10671    easeOutStrong: function (t, b, c, d) {
10672    	return -c * ((t=t/d-1)*t*t*t - 1) + b;
10673    },
10674    
10675    /**
10676     * Begins slowly and decelerates towards end.
10677     * @method easeBothStrong
10678     * @param {Number} t Time value used to compute current value
10679     * @param {Number} b Starting value
10680     * @param {Number} c Delta between start and end values
10681     * @param {Number} d Total length of animation
10682     * @return {Number} The computed value for the current animation frame
10683     */
10684    easeBothStrong: function (t, b, c, d) {
10685    	if ((t/=d/2) < 1) {
10686            return c/2*t*t*t*t + b;
10687        }
10688        
10689    	return -c/2 * ((t-=2)*t*t*t - 2) + b;
10690    },
10691
10692    /**
10693     * Snap in elastic effect.
10694     * @method elasticIn
10695     * @param {Number} t Time value used to compute current value
10696     * @param {Number} b Starting value
10697     * @param {Number} c Delta between start and end values
10698     * @param {Number} d Total length of animation
10699     * @param {Number} a Amplitude (optional)
10700     * @param {Number} p Period (optional)
10701     * @return {Number} The computed value for the current animation frame
10702     */
10703
10704    elasticIn: function (t, b, c, d, a, p) {
10705    	if (t == 0) {
10706            return b;
10707        }
10708        if ( (t /= d) == 1 ) {
10709            return b+c;
10710        }
10711        if (!p) {
10712            p=d*.3;
10713        }
10714        
10715    	if (!a || a < Math.abs(c)) {
10716            a = c; 
10717            var s = p/4;
10718        }
10719    	else {
10720            var s = p/(2*Math.PI) * Math.asin (c/a);
10721        }
10722        
10723    	return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
10724    },
10725
10726    /**
10727     * Snap out elastic effect.
10728     * @method elasticOut
10729     * @param {Number} t Time value used to compute current value
10730     * @param {Number} b Starting value
10731     * @param {Number} c Delta between start and end values
10732     * @param {Number} d Total length of animation
10733     * @param {Number} a Amplitude (optional)
10734     * @param {Number} p Period (optional)
10735     * @return {Number} The computed value for the current animation frame
10736     */
10737    elasticOut: function (t, b, c, d, a, p) {
10738    	if (t == 0) {
10739            return b;
10740        }
10741        if ( (t /= d) == 1 ) {
10742            return b+c;
10743        }
10744        if (!p) {
10745            p=d*.3;
10746        }
10747        
10748    	if (!a || a < Math.abs(c)) {
10749            a = c;
10750            var s = p / 4;
10751        }
10752    	else {
10753            var s = p/(2*Math.PI) * Math.asin (c/a);
10754        }
10755        
10756    	return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
10757    },
10758    
10759    /**
10760     * Snap both elastic effect.
10761     * @method elasticBoth
10762     * @param {Number} t Time value used to compute current value
10763     * @param {Number} b Starting value
10764     * @param {Number} c Delta between start and end values
10765     * @param {Number} d Total length of animation
10766     * @param {Number} a Amplitude (optional)
10767     * @param {Number} p Period (optional)
10768     * @return {Number} The computed value for the current animation frame
10769     */
10770    elasticBoth: function (t, b, c, d, a, p) {
10771    	if (t == 0) {
10772            return b;
10773        }
10774        
10775        if ( (t /= d/2) == 2 ) {
10776            return b+c;
10777        }
10778        
10779        if (!p) {
10780            p = d*(.3*1.5);
10781        }
10782        
10783    	if ( !a || a < Math.abs(c) ) {
10784            a = c; 
10785            var s = p/4;
10786        }
10787    	else {
10788            var s = p/(2*Math.PI) * Math.asin (c/a);
10789        }
10790        
10791    	if (t < 1) {
10792            return -.5*(a*Math.pow(2,10*(t-=1)) * 
10793                    Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
10794        }
10795    	return a*Math.pow(2,-10*(t-=1)) * 
10796                Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
10797    },
10798
10799
10800    /**
10801     * Backtracks slightly, then reverses direction and moves to end.
10802     * @method backIn
10803     * @param {Number} t Time value used to compute current value
10804     * @param {Number} b Starting value
10805     * @param {Number} c Delta between start and end values
10806     * @param {Number} d Total length of animation
10807     * @param {Number} s Overshoot (optional)
10808     * @return {Number} The computed value for the current animation frame
10809     */
10810    backIn: function (t, b, c, d, s) {
10811    	if (typeof s == 'undefined') {
10812            s = 1.70158;
10813        }
10814    	return c*(t/=d)*t*((s+1)*t - s) + b;
10815    },
10816
10817    /**
10818     * Overshoots end, then reverses and comes back to end.
10819     * @method backOut
10820     * @param {Number} t Time value used to compute current value
10821     * @param {Number} b Starting value
10822     * @param {Number} c Delta between start and end values
10823     * @param {Number} d Total length of animation
10824     * @param {Number} s Overshoot (optional)
10825     * @return {Number} The computed value for the current animation frame
10826     */
10827    backOut: function (t, b, c, d, s) {
10828    	if (typeof s == 'undefined') {
10829            s = 1.70158;
10830        }
10831    	return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
10832    },
10833    
10834    /**
10835     * Backtracks slightly, then reverses direction, overshoots end, 
10836     * then reverses and comes back to end.
10837     * @method backBoth
10838     * @param {Number} t Time value used to compute current value
10839     * @param {Number} b Starting value
10840     * @param {Number} c Delta between start and end values
10841     * @param {Number} d Total length of animation
10842     * @param {Number} s Overshoot (optional)
10843     * @return {Number} The computed value for the current animation frame
10844     */
10845    backBoth: function (t, b, c, d, s) {
10846    	if (typeof s == 'undefined') {
10847            s = 1.70158; 
10848        }
10849        
10850    	if ((t /= d/2 ) < 1) {
10851            return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
10852        }
10853    	return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
10854    },
10855
10856    /**
10857     * Bounce off of start.
10858     * @method bounceIn
10859     * @param {Number} t Time value used to compute current value
10860     * @param {Number} b Starting value
10861     * @param {Number} c Delta between start and end values
10862     * @param {Number} d Total length of animation
10863     * @return {Number} The computed value for the current animation frame
10864     */
10865    bounceIn: function (t, b, c, d) {
10866    	return c - YAHOO.util.Easing.bounceOut(d-t, 0, c, d) + b;
10867    },
10868    
10869    /**
10870     * Bounces off end.
10871     * @method bounceOut
10872     * @param {Number} t Time value used to compute current value
10873     * @param {Number} b Starting value
10874     * @param {Number} c Delta between start and end values
10875     * @param {Number} d Total length of animation
10876     * @return {Number} The computed value for the current animation frame
10877     */
10878    bounceOut: function (t, b, c, d) {
10879    	if ((t/=d) < (1/2.75)) {
10880    		return c*(7.5625*t*t) + b;
10881    	} else if (t < (2/2.75)) {
10882    		return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
10883    	} else if (t < (2.5/2.75)) {
10884    		return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
10885    	}
10886        return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
10887    },
10888    
10889    /**
10890     * Bounces off start and end.
10891     * @method bounceBoth
10892     * @param {Number} t Time value used to compute current value
10893     * @param {Number} b Starting value
10894     * @param {Number} c Delta between start and end values
10895     * @param {Number} d Total length of animation
10896     * @return {Number} The computed value for the current animation frame
10897     */
10898    bounceBoth: function (t, b, c, d) {
10899    	if (t < d/2) {
10900            return YAHOO.util.Easing.bounceIn(t*2, 0, c, d) * .5 + b;
10901        }
10902    	return YAHOO.util.Easing.bounceOut(t*2-d, 0, c, d) * .5 + c*.5 + b;
10903    }
10904};
10905
10906(function() {
10907/**
10908 * Anim subclass for moving elements along a path defined by the "points" 
10909 * member of "attributes".  All "points" are arrays with x, y coordinates.
10910 * <p>Usage: <code>var myAnim = new YAHOO.util.Motion(el, { points: { to: [800, 800] } }, 1, YAHOO.util.Easing.easeOut);</code></p>
10911 * @class Motion
10912 * @namespace YAHOO.util
10913 * @requires YAHOO.util.Anim
10914 * @requires YAHOO.util.AnimMgr
10915 * @requires YAHOO.util.Easing
10916 * @requires YAHOO.util.Bezier
10917 * @requires YAHOO.util.Dom
10918 * @requires YAHOO.util.Event
10919 * @requires YAHOO.util.CustomEvent 
10920 * @constructor
10921 * @extends YAHOO.util.ColorAnim
10922 * @param {String | HTMLElement} el Reference to the element that will be animated
10923 * @param {Object} attributes The attribute(s) to be animated.  
10924 * Each attribute is an object with at minimum a "to" or "by" member defined.  
10925 * Additional optional members are "from" (defaults to current value), "units" (defaults to "px").  
10926 * All attribute names use camelCase.
10927 * @param {Number} duration (optional, defaults to 1 second) Length of animation (frames or seconds), defaults to time-based
10928 * @param {Function} method (optional, defaults to YAHOO.util.Easing.easeNone) Computes the values that are applied to the attributes per frame (generally a YAHOO.util.Easing method)
10929 */
10930    var Motion = function(el, attributes, duration,  method) {
10931        if (el) { // dont break existing subclasses not using YAHOO.extend
10932            Motion.superclass.constructor.call(this, el, attributes, duration, method);
10933        }
10934    };
10935
10936
10937    Motion.NAME = 'Motion';
10938
10939    // shorthand
10940    var Y = YAHOO.util;
10941    YAHOO.extend(Motion, Y.ColorAnim);
10942    
10943    var superclass = Motion.superclass;
10944    var proto = Motion.prototype;
10945
10946    proto.patterns.points = /^points$/i;
10947    
10948    proto.setAttribute = function(attr, val, unit) {
10949        if (  this.patterns.points.test(attr) ) {
10950            unit = unit || 'px';
10951            superclass.setAttribute.call(this, 'left', val[0], unit);
10952            superclass.setAttribute.call(this, 'top', val[1], unit);
10953        } else {
10954            superclass.setAttribute.call(this, attr, val, unit);
10955        }
10956    };
10957
10958    proto.getAttribute = function(attr) {
10959        if (  this.patterns.points.test(attr) ) {
10960            var val = [
10961                superclass.getAttribute.call(this, 'left'),
10962                superclass.getAttribute.call(this, 'top')
10963            ];
10964        } else {
10965            val = superclass.getAttribute.call(this, attr);
10966        }
10967
10968        return val;
10969    };
10970
10971    proto.doMethod = function(attr, start, end) {
10972        var val = null;
10973
10974        if ( this.patterns.points.test(attr) ) {
10975            var t = this.method(this.currentFrame, 0, 100, this.totalFrames) / 100;				
10976            val = Y.Bezier.getPosition(this.runtimeAttributes[attr], t);
10977        } else {
10978            val = superclass.doMethod.call(this, attr, start, end);
10979        }
10980        return val;
10981    };
10982
10983    proto.setRuntimeAttribute = function(attr) {
10984        if ( this.patterns.points.test(attr) ) {
10985            var el = this.getEl();
10986            var attributes = this.attributes;
10987            var start;
10988            var control = attributes['points']['control'] || [];
10989            var end;
10990            var i, len;
10991            
10992            if (control.length > 0 && !(control[0] instanceof Array) ) { // could be single point or array of points
10993                control = [control];
10994            } else { // break reference to attributes.points.control
10995                var tmp = []; 
10996                for (i = 0, len = control.length; i< len; ++i) {
10997                    tmp[i] = control[i];
10998                }
10999                control = tmp;
11000            }
11001
11002            if (Y.Dom.getStyle(el, 'position') == 'static') { // default to relative
11003                Y.Dom.setStyle(el, 'position', 'relative');
11004            }
11005    
11006            if ( isset(attributes['points']['from']) ) {
11007                Y.Dom.setXY(el, attributes['points']['from']); // set position to from point
11008            } 
11009            else { Y.Dom.setXY( el, Y.Dom.getXY(el) ); } // set it to current position
11010            
11011            start = this.getAttribute('points'); // get actual top & left
11012            
11013            // TO beats BY, per SMIL 2.1 spec
11014            if ( isset(attributes['points']['to']) ) {
11015                end = translateValues.call(this, attributes['points']['to'], start);
11016                
11017                var pageXY = Y.Dom.getXY(this.getEl());
11018                for (i = 0, len = control.length; i < len;
11018 ++i) {
11019                    control[i] = translateValues.call(this, control[i], start);
11020                }
11021
11022                
11023            } else if ( isset(attributes['points']['by']) ) {
11024                end = [ start[0] + attributes['points']['by'][0], start[1] + attributes['points']['by'][1] ];
11025                
11026                for (i = 0, len = control.length; i < len; ++i) {
11027                    control[i] = [ start[0] + control[i][0], start[1] + control[i][1] ];
11028                }
11029            }
11030
11031            this.runtimeAttributes[attr] = [start];
11032            
11033            if (control.length > 0) {
11034                this.runtimeAttributes[attr] = this.runtimeAttributes[attr].concat(control); 
11035            }
11036
11037            this.runtimeAttributes[attr][this.runtimeAttributes[attr].length] = end;
11038        }
11039        else {
11040            superclass.setRuntimeAttribute.call(this, attr);
11041        }
11042    };
11043    
11044    var translateValues = function(val, start) {
11045        var pageXY = Y.Dom.getXY(this.getEl());
11046        val = [ val[0] - pageXY[0] + start[0], val[1] - pageXY[1] + start[1] ];
11047
11048        return val; 
11049    };
11050    
11051    var isset = function(prop) {
11052        return (typeof prop !== 'undefined');
11053    };
11054
11055    Y.Motion = Motion;
11056})();
11057(function() {
11058/**
11059 * Anim subclass for scrolling elements to a position defined by the "scroll"
11060 * member of "attributes".  All "scroll" members are arrays with x, y scroll positions.
11061 * <p>Usage: <code>var myAnim = new YAHOO.util.Scroll(el, { scroll: { to: [0, 800] } }, 1, YAHOO.util.Easing.easeOut);</code></p>
11062 * @class Scroll
11063 * @namespace YAHOO.util
11064 * @requires YAHOO.util.Anim
11065 * @requires YAHOO.util.AnimMgr
11066 * @requires YAHOO.util.Easing
11067 * @requires YAHOO.util.Bezier
11068 * @requires YAHOO.util.Dom
11069 * @requires YAHOO.util.Event
11070 * @requires YAHOO.util.CustomEvent 
11071 * @extends YAHOO.util.ColorAnim
11072 * @constructor
11073 * @param {String or HTMLElement} el Reference to the element that will be animated
11074 * @param {Object} attributes The attribute(s) to be animated.  
11075 * Each attribute is an object with at minimum a "to" or "by" member defined.  
11076 * Additional optional members are "from" (defaults to current value), "units" (defaults to "px").  
11077 * All attribute names use camelCase.
11078 * @param {Number} duration (optional, defaults to 1 second) Length of animation (frames or seconds), defaults to time-based
11079 * @param {Function} method (optional, defaults to YAHOO.util.Easing.easeNone) Computes the values that are applied to the attributes per frame (generally a YAHOO.util.Easing method)
11080 */
11081    var Scroll = function(el, attributes, duration,  method) {
11082        if (el) { // dont break existing subclasses not using YAHOO.extend
11083            Scroll.superclass.constructor.call(this, el, attributes, duration, method);
11084        }
11085    };
11086
11087    Scroll.NAME = 'Scroll';
11088
11089    // shorthand
11090    var Y = YAHOO.util;
11091    YAHOO.extend(Scroll, Y.ColorAnim);
11092    
11093    var superclass = Scroll.superclass;
11094    var proto = Scroll.prototype;
11095
11096    proto.doMethod = function(attr, start, end) {
11097        var val = null;
11098    
11099        if (attr == 'scroll') {
11100            val = [
11101                this.method(this.currentFrame, start[0], end[0] - start[0], this.totalFrames),
11102                this.method(this.currentFrame, start[1], end[1] - start[1], this.totalFrames)
11103            ];
11104            
11105        } else {
11106            val = superclass.doMethod.call(this, attr, start, end);
11107        }
11108        return val;
11109    };
11110
11111    proto.getAttribute = function(attr) {
11112        var val = null;
11113        var el = this.getEl();
11114        
11115        if (attr == 'scroll') {
11116            val = [ el.scrollLeft, el.scrollTop ];
11117        } else {
11118            val = superclass.getAttribute.call(this, attr);
11119        }
11120        
11121        return val;
11122    };
11123
11124    proto.setAttribute = function(attr, val, unit) {
11125        var el = this.getEl();
11126        
11127        if (attr == 'scroll') {
11128            el.scrollLeft = val[0];
11129            el.scrollTop = val[1];
11130        } else {
11131            superclass.setAttribute.call(this, attr, val, unit);
11132        }
11133    };
11134
11135    Y.Scroll = Scroll;
11136})();
11137YAHOO.register("animation", YAHOO.util.Anim, {version: "2.9.0", build: "2800"});
11138/*
11139Copyright (c) 2011, Yahoo! Inc. All rights reserved.
11140Code licensed under the BSD License:
11141http://developer.yahoo.com/yui/license.html
11142version: 2.9.0
11143*/
11144/****************************************************************************/
11145/****************************************************************************/
11146/****************************************************************************/
11147
11148/**
11149 * The LogMsg class defines a single log message.
11150 *
11151 * @class LogMsg
11152 * @constructor
11153 * @param oConfigs {Object} Object literal of configuration params.
11154 */
11155YAHOO.widget.LogMsg = function(oConfigs) {
11156    // Parse configs
11157    /**
11158     * Log message.
11159     *
11160     * @property msg
11161     * @type String
11162     */
11163    this.msg =
11164    /**
11165     * Log timestamp.
11166     *
11167     * @property time
11168     * @type Date
11169     */
11170    this.time =
11171
11172    /**
11173     * Log category.
11174     *
11175     * @property category
11176     * @type String
11177     */
11178    this.category =
11179
11180    /**
11181     * Log source. The first word passed in as the source argument.
11182     *
11183     * @property source
11184     * @type String
11185     */
11186    this.source =
11187
11188    /**
11189     * Log source detail. The remainder of the string passed in as the source argument, not
11190     * including the first word (if any).
11191     *
11192     * @property sourceDetail
11193     * @type String
11194     */
11195    this.sourceDetail = null;
11196
11197    if (oConfigs && (oConfigs.constructor == Object)) {
11198        for(var param in oConfigs) {
11199            if (oConfigs.hasOwnProperty(param)) {
11200                this[param] = oConfigs[param];
11201            }
11202        }
11203    }
11204};
11205/****************************************************************************/
11206/****************************************************************************/
11207/****************************************************************************/
11208
11209/**
11210 * The LogWriter class provides a mechanism to log messages through
11211 * YAHOO.widget.Logger from a named source.
11212 *
11213 * @class LogWriter
11214 * @constructor
11215 * @param sSource {String} Source of LogWriter instance.
11216 */
11217YAHOO.widget.LogWriter = function(sSource) {
11218    if(!sSource) {
11219        YAHOO.log("Could not instantiate LogWriter due to invalid source.",
11220            "error", "LogWriter");
11221        return;
11222    }
11223    this._source = sSource;
11224 };
11225
11226/////////////////////////////////////////////////////////////////////////////
11227//
11228// Public methods
11229//
11230/////////////////////////////////////////////////////////////////////////////
11231
11232 /**
11233 * Public accessor to the unique name of the LogWriter instance.
11234 *
11235 * @method toString
11236 * @return {String} Unique name of the LogWriter instance.
11237 */
11238YAHOO.widget.LogWriter.prototype.toString = function() {
11239    return "LogWriter " + this._sSource;
11240};
11241
11242/**
11243 * Logs a message attached to the source of the LogWriter.
11244 * Note: the LogReader adds the message and category to the DOM as HTML.
11245 *
11246 * @method log
11247 * @param sMsg {HTML} The log message.
11248 * @param sCategory {HTML} Category name.
11249 */
11250YAHOO.widget.LogWriter.prototype.log = function(sMsg, sCategory) {
11251    YAHOO.widget.Logger.log(sMsg, sCategory, this._source);
11252};
11253
11254/**
11255 * Public accessor to get the source name.
11256 *
11257 * @method getSource
11258 * @return {String} The LogWriter source.
11259 */
11260YAHOO.widget.LogWriter.prototype.getSource = function() {
11261    return this._source;
11262};
11263
11264/**
11265 * Public accessor to set the source name.
11266 *
11267 * @method setSource
11268 * @param sSource {String} Source of LogWriter instance.
11269 */
11270YAHOO.widget.LogWriter.prototype.setSource = function(sSource) {
11271    if(!sSource) {
11272        YAHOO.log("Could not set source due to invalid source.", "error", this.toString());
11273        return;
11274    }
11275    else {
11276        this._source = sSource;
11277    }
11278};
11279
11280/////////////////////////////////////////////////////////////////////////////
11281//
11282// Private member variables
11283//
11284/////////////////////////////////////////////////////////////////////////////
11285
11286/**
11287 * Source of the LogWriter instance.
11288 *
11289 * @property _source
11290 * @type String
11291 * @private
11292 */
11293YAHOO.widget.LogWriter.prototype._source = null;
11294
11295
11296
11297 /**
11298 * The Logger widget provides a simple way to read or write log messages in
11299 * JavaScript code. Integration with the YUI Library's debug builds allow
11300 * implementers to access under-the-hood events, errors, and debugging messages.
11301 * Output may be read through a LogReader console and/or output to a browser
11302 * console.
11303 *
11304 * @module logger
11305 * @requires yahoo, event, dom
11306 * @optional dragdrop
11307 * @namespace YAHOO.widget
11308 * @title Logger Widget
11309 */
11310
11311/****************************************************************************/
11312/****************************************************************************/
11313/****************************************************************************/
11314
11315// Define once
11316if(!YAHOO.widget.Logger) {
11317    /**
11318     * The singleton Logger class provides core log management functionality. Saves
11319     * logs written through the global YAHOO.log function or written by a LogWriter
11320     * instance. Provides access to logs for reading by a LogReader instance or
11321     * native browser console such as the Firebug extension to Firefox or Safari's
11322     * JavaScript console through integration with the console.log() method.
11323     *
11324     * @class Logger
11325     * @static
11326     */
11327    YAHOO.widget.Logger = {
11328        // Initialize properties
11329        loggerEnabled: true,
11330        _browserConsoleEnabled: false,
11331        categories: ["info","warn","error","time","window"],
11332        sources: ["global"],
11333        _stack: [], // holds all log msgs
11334        maxStackEntries: 2500,
11335        _startTime: new Date().getTime(), // static start timestamp
11336        _lastTime: null, // timestamp of last logged message
11337        _windowErrorsHandled: false,
11338        _origOnWindowError: null
11339    };
11340
11341    /////////////////////////////////////////////////////////////////////////////
11342    //
11343    // Public properties
11344    //
11345    /////////////////////////////////////////////////////////////////////////////
11346    /**
11347     * True if Logger is enabled, false otherwise.
11348     *
11349     * @property loggerEnabled
11350     * @type Boolean
11351     * @static
11352     * @default true
11353     */
11354
11355    /**
11356     * Array of categories.
11357     *
11358     * @property categories
11359     * @type String[]
11360     * @static
11361     * @default ["info","warn","error","time","window"]
11362     */
11363
11364    /**
11365     * Array of sources.
11366     *
11367     * @property sources
11368     * @type String[]
11369     * @static
11370     * @default ["global"]
11371     */
11372
11373    /**
11374     * Upper limit on size of internal stack.
11375     *
11376     * @property maxStackEntries
11377     * @type Number
11378     * @static
11379     * @default 2500
11380     */
11381
11382    /////////////////////////////////////////////////////////////////////////////
11383    //
11384    // Private properties
11385    //
11386    /////////////////////////////////////////////////////////////////////////////
11387    /**
11388     * Internal property to track whether output to browser console is enabled.
11389     *
11390     * @property _browserConsoleEnabled
11391     * @type Boolean
11392     * @static
11393     * @default false
11394     * @private
11395     */
11396
11397    /**
11398     * Array to hold all log messages.
11399     *
11400     * @property _stack
11401     * @type Array
11402     * @static
11403     * @private
11404     */
11405    /**
11406     * Static timestamp of Logger initialization.
11407     *
11408     * @property _startTime
11409     * @type Date
11410     * @static
11411     * @private
11412     */
11413    /**
11414     * Timestamp of last logged message.
11415     *
11416     * @property _lastTime
11417     * @type Date
11418     * @static
11419     * @private
11420     */
11421    /////////////////////////////////////////////////////////////////////////////
11422    //
11423    // Public methods
11424    //
11425    /////////////////////////////////////////////////////////////////////////////
11426    /**
11427     * Saves a log message to the stack and fires newLogEvent. If the log message is
11428     * assigned to an unknown category, creates a new category. If the log message is
11429     * from an unknown source, creates a new source.  If browser console is enabled,
11430     * outputs the log message to browser console.
11431     * Note: the LogReader adds the message, category, and source to the DOM
11432     * as HTML.
11433     *
11434     * @method log
11435     * @param sMsg {HTML} The log message.
11436     * @param sCategory {HTML} Category of log message, or null.
11437     * @param sSource {HTML} Source of LogWriter, or null if global.
11438     */
11439    YAHOO.widget.Logger.log = function(sMsg, sCategory, sSource) {
11440        if(this.loggerEnabled) {
11441            if(!sCategory) {
11442                sCategory = "info"; // default category
11443            }
11444            else {
11445                sCategory = sCategory.toLocaleLowerCase();
11446                if(this._isNewCategory(sCategory)) {
11447                    this._createNewCategory(sCategory);
11448                }
11449            }
11450            var sClass = "global"; // default source
11451            var sDetail = null;
11452            if(sSource) {
11453                var spaceIndex = sSource.indexOf(" ");
11454                if(spaceIndex > 0) {
11455                    // Substring until first space
11456                    sClass = sSource.substring(0,spaceIndex);
11457                    // The rest of the source
11458                    sDetail = sSource.substring(spaceIndex,sSource.length);
11459                }
11460                else {
11461                    sClass = sSource;
11462                }
11463                if(this._isNewSource(sClass)) {
11464                    this._createNewSource(sClass);
11465                }
11466            }
11467
11468            var timestamp = new Date();
11469            var logEntry = new YAHOO.widget.LogMsg({
11470                msg: sMsg,
11471                time: timestamp,
11472                category: sCategory,
11473                source: sClass,
11474                sourceDetail: sDetail
11475            });
11476
11477            var stack = this._stack;
11478            var maxStackEntries = this.maxStackEntries;
11479            if(maxStackEntries && !isNaN(maxStackEntries) &&
11480                (stack.length >= maxStackEntries)) {
11481                stack.shift();
11482            }
11483            stack.push(logEntry);
11484            this.newLogEvent.fire(logEntry);
11485
11486            if(this._browserConsoleEnabled) {
11487                this._printToBrowserConsole(logEntry);
11488            }
11489            return true;
11490        }
11491        else {
11492            return false;
11493        }
11494    };
11495
11496    /**
11497     * Resets internal stack and startTime, enables Logger, and fires logResetEvent.
11498     *
11499     * @method reset
11500     */
11501    YAHOO.widget.Logger.reset = function() {
11502        this._stack = [];
11503        this._startTime = new Date().getTime();
11504        this.loggerEnabled = true;
11505        this.log("Logger reset");
11506        this.logResetEvent.fire();
11507    };
11508
11509    /**
11510     * Public accessor to internal stack of log message objects.
11511     *
11512     * @method getStack
11513     * @return {Object[]} Array of log message objects.
11514     */
11515    YAHOO.widget.Logger.getStack = function() {
11516        return this._stack;
11517    };
11518
11519    /**
11520     * Public accessor to internal start time.
11521     *
11522     * @method getStartTime
11523     * @return {Date} Internal date of when Logger singleton was initialized.
11524     */
11525    YAHOO.widget.Logger.getStartTime = function() {
11526        return this._startTime;
11527    };
11528
11529    /**
11530     * Disables output to the browser's global console.log() function, which is used
11531     * by the Firebug extension to Firefox as well as Safari.
11532     *
11533     * @method disableBrowserConsole
11534     */
11535    YAHOO.widget.Logger.disableBrowserConsole = function() {
11536        YAHOO.log("Logger output to the function console.log() has been disabled.");
11537        this._browserConsoleEnabled = false;
11538    };
11539
11540    /**
11541     * Enables output to the browser's global console.log() function, which is used
11542     * by the Firebug extension to Firefox as well as Safari.
11543     *
11544     * @method enableBrowserConsole
11545     */
11546    YAHOO.widget.Logger.enableBrowserConsole = function() {
11547        this._browserConsoleEnabled = true;
11548        YAHOO.log("Logger output to the function console.log() has been enabled.");
11549    };
11550
11551    /**
11552     * Surpresses native JavaScript errors and outputs to console. By default,
11553     * Logger does not handle JavaScript window error events.
11554     * NB: Not all browsers support the window.onerror event.
11555     *
11556     * @method handleWindowErrors
11557     */
11558    YAHOO.widget.Logger.handleWindowErrors = function() {
11559        if(!YAHOO.widget.Logger._windowErrorsHandled) {
11560            // Save any previously defined handler to call
11561            if(window.error) {
11562                YAHOO.widget.Logger._origOnWindowError = window.onerror;
11563            }
11564            window.onerror = YAHOO.widget.Logger._onWindowError;
11565            YAHOO.widget.Logger._windowErrorsHandled = true;
11566            YAHOO.log("Logger handling of window.onerror has been enabled.");
11567        }
11568        else {
11569            YAHOO.log("Logger handling of window.onerror had already been enabled.");
11570        }
11571    };
11572
11573    /**
11574     * Unsurpresses native JavaScript errors. By default,
11575     * Logger does not handle JavaScript window error events.
11576     * NB: Not all browsers support the window.onerror event.
11577     *
11578     * @method unhandleWindowErrors
11579     */
11580    YAHOO.widget.Logger.unhandleWindowErrors = function() {
11581        if(YAHOO.widget.Logger._windowErrorsHandled) {
11582            // Revert to any previously defined handler to call
11583            if(YAHOO.widget.Logger._origOnWindowError) {
11584                window.onerror = YAHOO.widget.Logger._origOnWindowError;
11585                YAHOO.widget.Logger._origOnWindowError = null;
11586            }
11587            else {
11588                window.onerror = null;
11589            }
11590            YAHOO.widget.Logger._windowErrorsHandled = false;
11591            YAHOO.log("Logger handling of window.onerror has been disabled.");
11592        }
11593        else {
11594            YAHOO.log("Logger handling of window.onerror had already been disabled.");
11595        }
11596    };
11597    
11598    /////////////////////////////////////////////////////////////////////////////
11599    //
11600    // Public events
11601    //
11602    /////////////////////////////////////////////////////////////////////////////
11603
11604     /**
11605     * Fired when a new category has been created.
11606     *
11607     * @event categoryCreateEvent
11608     * @param sCategory {String} Category name.
11609     */
11610    YAHOO.widget.Logger.categoryCreateEvent =
11611        new YAHOO.util.CustomEvent("categoryCreate", this, true);
11612
11613     /**
11614     * Fired when a new source has been named.
11615     *
11616     * @event sourceCreateEvent
11617     * @param sSource {String} Source name.
11618     */
11619    YAHOO.widget.Logger.sourceCreateEvent =
11620        new YAHOO.util.CustomEvent("sourceCreate", this, true);
11621
11622     /**
11623     * Fired when a new log message has been created.
11624     *
11625     * @event newLogEvent
11626     * @param sMsg {String} Log message.
11627     */
11628    YAHOO.widget.Logger.newLogEvent = new YAHOO.util.CustomEvent("newLog", this, true);
11629
11630    /**
11631     * Fired when the Logger has been reset has been created.
11632     *
11633     * @event logResetEvent
11634     */
11635    YAHOO.widget.Logger.logResetEvent = new YAHOO.util.CustomEvent("logReset", this, true);
11636
11637    /////////////////////////////////////////////////////////////////////////////
11638    //
11639    // Private methods
11640    //
11641    /////////////////////////////////////////////////////////////////////////////
11642
11643    /**
11644     * Creates a new category of log messages and fires categoryCreateEvent.
11645     *
11646     * @method _createNewCategory
11647     * @param sCategory {String} Category name.
11648     * @private
11649     */
11650    YAHOO.widget.Logger._createNewCategory = function(sCategory) {
11651        this.categories.push(sCategory);
11652        this.categoryCreateEvent.fire(sCategory);
11653    };
11654
11655    /**
11656     * Checks to see if a category has already been created.
11657     *
11658     * @method _isNewCategory
11659     * @param sCategory {String} Category name.
11660     * @return {Boolean} Returns true if category is unknown, else returns false.
11661     * @private
11662     */
11663    YAHOO.widget.Logger._isNewCategory = function(sCategory) {
11664        for(var i=0; i < this.categories.length; i++) {
11665            if(sCategory == this.categories[i]) {
11666                return false;
11667            }
11668        }
11669        return true;
11670    };
11671
11672    /**
11673     * Creates a new source of log messages and fires sourceCreateEvent.
11674     *
11675     * @method _createNewSource
11676     * @param sSource {String} Source name.
11677     * @private
11678     */
11679    YAHOO.widget.Logger._createNewSource = function(sSource) {
11680        this.sources.push(sSource);
11681        this.sourceCreateEvent.fire(sSource);
11682    };
11683
11684    /**
11685     * Checks to see if a source already exists.
11686     *
11687     * @method _isNewSource
11688     * @param sSource {String} Source name.
11689     * @return {Boolean} Returns true if source is unknown, else returns false.
11690     * @private
11691     */
11692    YAHOO.widget.Logger._isNewSource = function(sSource) {
11693        if(sSource) {
11694            for(var i=0; i < this.sources.length; i++) {
11695                if(sSource == this.sources[i]) {
11696                    return false;
11697                }
11698            }
11699            return true;
11700        }
11701    };
11702
11703    /**
11704     * Outputs a log message to global console.log() function.
11705     *
11706     * @method _printToBrowserConsole
11707     * @param oEntry {Object} Log entry object.
11708     * @private
11709     */
11710    YAHOO.widget.Logger._printToBrowserConsole = function(oEntry) {
11711        if ((window.console && console.log) ||
11712            (window.opera && opera.postError)) {
11713            var category = oEntry.category;
11714            var label = oEntry.category.substring(0,4).toUpperCase();
11715
11716            var time = oEntry.time;
11717            var localTime;
11718            if (time.toLocaleTimeString) {
11719                localTime  = time.toLocaleTimeString();
11720            }
11721            else {
11722                localTime = time.toString();
11723            }
11724
11725            var msecs = time.getTime();
11726            var elapsedTime = (YAHOO.widget.Logger._lastTime) ?
11727                (msecs - YAHOO.widget.Logger._lastTime) : 0;
11728            YAHOO.widget.Logger._lastTime = msecs;
11729
11730            var output =
11731                localTime + " (" +
11732                elapsedTime + "ms): " +
11733                oEntry.source + ": ";
11734
11735            if (window.console) {
11736                console.log(output, oEntry.msg);
11737            } else {
11738                opera.postError(output + oEntry.msg);
11739            }
11740        }
11741    };
11742
11743    /////////////////////////////////////////////////////////////////////////////
11744    //
11745    // Private event handlers
11746    //
11747    /////////////////////////////////////////////////////////////////////////////
11748
11749    /**
11750     * Handles logging of messages due to window error events.
11751     *
11752     * @method _onWindowError
11753     * @param sMsg {String} The error message.
11754     * @param sUrl {String} URL of the error.
11755     * @param sLine {String} Line number of the error.
11756     * @private
11757     */
11758    YAHOO.widget.Logger._onWindowError = function(sMsg,sUrl,sLine) {
11759        // Logger is not in scope of this event handler
11760        try {
11761            YAHOO.widget.Logger.log(sMsg+' ('+sUrl+', line '+sLine+')', "window");
11762            if(YAHOO.widget.Logger._origOnWindowError) {
11763                YAHOO.widget.Logger._origOnWindowError();
11764            }
11765        }
11766        catch(e) {
11767            return false;
11768        }
11769    };
11770
11771    /////////////////////////////////////////////////////////////////////////////
11772    //
11773    // First log
11774    //
11775    /////////////////////////////////////////////////////////////////////////////
11776
11777    YAHOO.widget.Logger.log("Logger initialized");
11778}
11779
11780/****************************************************************************/
11781/****************************************************************************/
11782/****************************************************************************/
11783(function () {
11784var Logger = YAHOO.widget.Logger,
11785    u      = YAHOO.util,
11786    Dom    = u.Dom,
11787    Event  = u.Event,
11788    d      = document;
11789
11790function make(el,props) {
11791    el = d.createElement(el);
11792    if (props) {
11793        for (var p in props) {
11794            if (props.hasOwnProperty(p)) {
11795                el[p] = props[p];
11796            }
11797        }
11798    }
11799    return el;
11800}
11801
11802/**
11803 * The LogReader class provides UI to read messages logged to YAHOO.widget.Logger.
11804 *
11805 * @class LogReader
11806 * @constructor
11807 * @param elContainer {HTMLElement} (optional) DOM element reference of an existing DIV.
11808 * @param elContainer {String} (optional) String ID of an existing DIV.
11809 * @param oConfigs {Object} (optional) Object literal of configuration params.
11810 */
11811function LogReader(elContainer, oConfigs) {
11812    this._sName = LogReader._index;
11813    LogReader._index++;
11814    
11815    this._init.apply(this,arguments);
11816
11817    /**
11818     * Render the LogReader immediately upon instantiation.  If set to false,
11819     * you must call myLogReader.render() to generate the UI.
11820     * 
11821     * @property autoRender
11822     * @type {Boolean}
11823     * @default true
11824     */
11825    if (this.autoRender !== false) {
11826        this.render();
11827    }
11828}
11829
11830/////////////////////////////////////////////////////////////////////////////
11831//
11832// Static member variables
11833//
11834/////////////////////////////////////////////////////////////////////////////
11835YAHOO.lang.augmentObject(LogReader, {
11836    /**
11837     * Internal class member to index multiple LogReader instances.
11838     *
11839     * @property _memberName
11840     * @static
11841     * @type Number
11842     * @default 0
11843     * @private
11844     */
11845    _index : 0,
11846
11847    /**
11848     * Node template for the log entries
11849     * @property ENTRY_TEMPLATE
11850     * @static
11851     * @type {HTMLElement}
11852     * @default <code>pre</code> element with class yui-log-entry
11853     */
11854    ENTRY_TEMPLATE : (function () {
11855        return make('pre',{ className: 'yui-log-entry' });
11856    })(),
11857
11858    /**
11859     * Template used for innerHTML of verbose entry output.
11860     * @property VERBOSE_TEMPLATE
11861     * @static
11862     * @default "&lt;p>&lt;span class='{category}'>{label}&lt;/span>{totalTime}ms (+{elapsedTime}) {localTime}:&lt;/p>&lt;p>{sourceAndDetail}&lt;/p>&lt;p>{message}&lt;/p>"
11863     */
11864    VERBOSE_TEMPLATE : "<p><span class='{category}'>{label}</span> {totalTime}ms (+{elapsedTime}) {localTime}:</p><p>{sourceAndDetail}</p><p>{message}</p>",
11865
11866    /**
11867     * Template used for innerHTML of compact entry output.
11868     * @property BASIC_TEMPLATE
11869     * @static
11870     * @default "&lt;p>&lt;span class='{category}'>{label}&lt;/span>{totalTime}ms (+{elapsedTime}) {localTime}: {sourceAndDetail}: {message}&lt;/p>"
11871     */
11872    BASIC_TEMPLATE : "<p><span class='{category}'>{label}</span> {totalTime}ms (+{elapsedTime}) {localTime}: {sourceAndDetail}: {message}</p>"
11873});
11874
11875/////////////////////////////////////////////////////////////////////////////
11876//
11877// Public member variables
11878//
11879/////////////////////////////////////////////////////////////////////////////
11880
11881LogReader.prototype = {
11882    /**
11883     * Whether or not LogReader is enabled to output log messages.
11884     *
11885     * @property logReaderEnabled
11886     * @type Boolean
11887     * @default true
11888     */
11889    logReaderEnabled : true,
11890
11891    /**
11892     * Public member to access CSS width of the LogReader container.
11893     *
11894     * @property width
11895     * @type String
11896     */
11897    width : null,
11898
11899    /**
11900     * Public member to access CSS height of the LogReader container.
11901     *
11902     * @property height
11903     * @type String
11904     */
11905    height : null,
11906
11907    /**
11908     * Public member to access CSS top position of the LogReader container.
11909     *
11910     * @property top
11911     * @type String
11912     */
11913    top : null,
11914
11915    /**
11916     * Public member to access CSS left position of the LogReader container.
11917     *
11918     * @property left
11919     * @type String
11920     */
11921    left : null,
11922
11923    /**
11924     * Public member to access CSS right position of the LogReader container.
11925     *
11926     * @property right
11927     * @type String
11928     */
11929    right : null,
11930
11931    /**
11932     * Public member to access CSS bottom position of the LogReader container.
11933     *
11934     * @property bottom
11935     * @type String
11936     */
11937    bottom : null,
11938
11939    /**
11940     * Public member to access CSS font size of the LogReader container.
11941     *
11942     * @property fontSize
11943     * @type String
11944     */
11945    fontSize : null,
11946
11947    /**
11948     * Whether or not the footer UI is enabled for the LogReader.
11949     *
11950     * @property footerEnabled
11951     * @type Boolean
11952     * @default true
11953     */
11954    footerEnabled : true,
11955
11956    /**
11957     * Whether or not output is verbose (more readable). Setting to true will make
11958     * output more compact (less readable).
11959     *
11960     * @property verboseOutput
11961     * @type Boolean
11962     * @default true
11963     */
11964    verboseOutput : true,
11965
11966    /**
11967     * Custom output format for log messages.  Defaults to null, which falls
11968     * back to verboseOutput param deciding between LogReader.VERBOSE_TEMPLATE
11969     * and LogReader.BASIC_TEMPLATE.  Use bracketed place holders to mark where
11970     * message info should go.  Available place holder names include:
11971     * <ul>
11972     *  <li>category</li>
11973     *  <li>label</li>
11974     *  <li>sourceAndDetail</li>
11975     *  <li>message</li>
11976     *  <li>localTime</li>
11977     *  <li>elapsedTime</li>
11978     *  <li>totalTime</li>
11979     * </ul>
11980     *
11981     * @property entryFormat
11982     * @type String
11983     * @default null
11984     */
11985    entryFormat : null,
11986
11987    /**
11988     * Whether or not newest message is printed on top.
11989     *
11990     * @property newestOnTop
11991     * @type Boolean
11992     */
11993    newestOnTop : true,
11994
11995    /**
11996     * Output timeout buffer in milliseconds.
11997     *
11998     * @property outputBuffer
11999     * @type Number
12000     * @default 100
12001     */
12002    outputBuffer : 100,
12003
12004    /**
12005     * Maximum number of messages a LogReader console will display.
12006     *
12007     * @property thresholdMax
12008     * @type Number
12009     * @default 500
12010     */
12011    thresholdMax : 500,
12012
12013    /**
12014     * When a LogReader console reaches its thresholdMax, it will clear out messages
12015     * and print out the latest thresholdMin number of messages.
12016     *
12017     * @property thresholdMin
12018     * @type Number
12019     * @default 100
12020     */
12021    thresholdMin : 100,
12022
12023    /**
12024     * True when LogReader is in a collapsed state, false otherwise.
12025     *
12026     * @property isCollapsed
12027     * @type Boolean
12028     * @default false
12029     */
12030    isCollapsed : false,
12031
12032    /**
12033     * True when LogReader is in a paused state, false otherwise.
12034     *
12035     * @property isPaused
12036     * @type Boolean
12037     * @default false
12038     */
12039    isPaused : false,
12040
12041    /**
12042     * Enables draggable LogReader if DragDrop Utility is present.
12043     *
12044     * @property draggable
12045     * @type Boolean
12046     * @default true
12047     */
12048    draggable : true,
12049
12050    /////////////////////////////////////////////////////////////////////////////
12051    //
12052    // Public methods
12053    //
12054    /////////////////////////////////////////////////////////////////////////////
12055
12056     /**
12057     * Public accessor to the unique name of the LogReader instance.
12058     *
12059     * @method toString
12060     * @return {String} Unique name of the LogReader instance.
12061     */
12062    toString : function() {
12063        return "LogReader instance" + this._sName;
12064    },
12065    /**
12066     * Pauses output of log messages. While paused, log messages are not lost, but
12067     * get saved to a buffer and then output upon resume of LogReader.
12068     *
12069     * @method pause
12070     */
12071    pause : function() {
12072        this.isPaused = true;
12073        this._timeout = null;
12074        this.logReaderEnabled = false;
12075        if (this._btnPause) {
12076            this._btnPause.value = "Resume";
12077        }
12078    },
12079
12080    /**
12081     * Resumes output of log messages, including outputting any log messages that
12082     * have been saved to buffer while paused.
12083     *
12084     * @method resume
12085     */
12086    resume : function() {
12087        this.isPaused = false;
12088        this.logReaderEnabled = true;
12089        this._printBuffer();
12090        if (this._btnPause) {
12091            this._btnPause.value = "Pause";
12092        }
12093    },
12094
12095    /**
12096     * Adds the UI to the DOM, attaches event listeners, and bootstraps initial
12097     * UI state.
12098     *
12099     * @method render
12100     */
12101    render : function () {
12102        if (this.rendered) {
12103            return;
12104        }
12105
12106        this._initContainerEl();
12107        
12108        this._initHeaderEl();
12109        this._initConsoleEl();
12110        this._initFooterEl();
12111
12112        this._initCategories();
12113        this._initSources();
12114
12115        this._initDragDrop();
12116
12117        // Subscribe to Logger custom events
12118        Logger.newLogEvent.subscribe(this._onNewLog, this);
12119        Logger.logResetEvent.subscribe(this._onReset, this);
12120
12121        Logger.categoryCreateEvent.subscribe(this._onCategoryCreate, this);
12122        Logger.sourceCreateEvent.subscribe(this._onSourceCreate, this);
12123
12124        this.rendered = true;
12125
12126        this._filterLogs();
12127    },
12128
12129    /**
12130     * Removes the UI from the DOM entirely and detaches all event listeners.
12131     * Implementers should note that Logger will still accumulate messages.
12132     *
12133     * @method destroy
12134     */
12135    destroy : function () {
12136        Event.purgeElement(this._elContainer,true);
12137        this._elContainer.innerHTML = '';
12138        this._elContainer.parentNode.removeChild(this._elContainer);
12139
12140        this.rendered = false;
12141    },
12142
12143    /**
12144     * Hides UI of LogReader. Logging functionality is not disrupted.
12145     *
12146     * @method hide
12147     */
12148    hide : function() {
12149        this._elContainer.style.display = "none";
12150    },
12151
12152    /**
12153     * Shows UI of LogReader. Logging functionality is not disrupted.
12154     *
12155     * @method show
12156     */
12157    show : function() {
12158        this._elContainer.style.display = "block";
12159    },
12160
12161    /**
12162     * Collapses UI of LogReader. Logging functionality is not disrupted.
12163     *
12164     * @method collapse
12165     */
12166    collapse : function() {
12167        this._elConsole.style.display = "none";
12168        if(this._elFt) {
12169            this._elFt.style.display = "none";
12170        }
12171        this._btnCollapse.value = "Expand";
12172        this.isCollapsed = true;
12173    },
12174
12175    /**
12176     * Expands UI of LogReader. Logging functionality is not disrupted.
12177     *
12178     * @method expand
12179     */
12180    expand : function() {
12181        this._elConsole.style.display = "block";
12182        if(this._elFt) {
12183            this._elFt.style.display = "block";
12184        }
12185        this._btnCollapse.value = "Collapse";
12186        this.isCollapsed = false;
12187    },
12188
12189    /**
12190     * Returns related checkbox element for given filter (i.e., category or source).
12191     *
12192     * @method getCheckbox
12193     * @param {String} Category or source name.
12194     * @return {Array} Array of all filter checkboxes.
12195     */
12196    getCheckbox : function(filter) {
12197        return this._filterCheckboxes[filter];
12198    },
12199
12200    /**
12201     * Returns array of enabled categories.
12202     *
12203     * @method getCategories
12204     * @return {String[]} Array of enabled categories.
12205     */
12206    getCategories : function() {
12207        return this._categoryFilters;
12208    },
12209
12210    /**
12211     * Shows log messages associated with given category.
12212     *
12213     * @method showCategory
12214     * @param {String} Category name.
12215     */
12216    showCategory : function(sCategory) {
12217        var filtersArray = this._categoryFilters;
12218        // Don't do anything if category is already enabled
12219        // Use Array.indexOf if available...
12220        if(filtersArray.indexOf) {
12221             if(filtersArray.indexOf(sCategory) >  -1) {
12222                return;
12223            }
12224        }
12225        // ...or do it the old-fashioned way
12226        else {
12227            for(var i=0; i<filtersArray.length; i++) {
12228               if(filtersArray[i] === sCategory){
12229                    return;
12230                }
12231            }
12232        }
12233
12234        this._categoryFilters.push(sCategory);
12235        this._filterLogs();
12236        var elCheckbox = this.getCheckbox(sCategory);
12237        if(elCheckbox) {
12238            elCheckbox.checked = true;
12239        }
12240    },
12241
12242    /**
12243     * Hides log messages associated with given category.
12244     *
12245     * @method hideCategory
12246     * @param {String} Category name.
12247     */
12248    hideCategory : function(sCategory) {
12249        var filtersArray = this._categoryFilters;
12250        for(var i=0; i<filtersArray.length; i++) {
12251            if(sCategory == filtersArray[i]) {
12252                filtersArray.splice(i, 1);
12253                break;
12254            }
12255        }
12256        this._filterLogs();
12257        var elCheckbox = this.getCheckbox(sCategory);
12258        if(elCheckbox) {
12259            elCheckbox.checked = false;
12260        }
12261    },
12262
12263    /**
12264     * Returns array of enabled sources.
12265     *
12266     * @method getSources
12267     * @return {Array} Array of enabled sources.
12268     */
12269    getSources : function() {
12270        return this._sourceFilters;
12271    },
12272
12273    /**
12274     * Shows log messages associated with given source.
12275     *
12276     * @method showSource
12277     * @param {String} Source name.
12278     */
12279    showSource : function(sSource) {
12280        var filtersArray = this._sourceFilters;
12281        // Don't do anything if category is already enabled
12282        // Use Array.indexOf if available...
12283        if(filtersArray.indexOf) {
12284             if(filtersArray.indexOf(sSource) >  -1) {
12285                return;
12286            }
12287        }
12288        // ...or do it the old-fashioned way
12289        else {
12290            for(var i=0; i<filtersArray.length; i++) {
12291               if(sSource == filtersArray[i]){
12292                    return;
12293                }
12294            }
12295        }
12296        filtersArray.push(sSource);
12297        this._filterLogs();
12298        var elCheckbox = this.getCheckbox(sSource);
12299        if(elCheckbox) {
12300            elCheckbox.checked = true;
12301        }
12302    },
12303
12304    /**
12305     * Hides log messages associated with given source.
12306     *
12307     * @method hideSource
12308     * @param {String} Source name.
12309     */
12310    hideSource : function(sSource) {
12311        var filtersArray = this._sourceFilters;
12312        for(var i=0; i<filtersArray.length; i++) {
12313            if(sSource == filtersArray[i]) {
12314                filtersArray.splice(i, 1);
12315                break;
12316            }
12317        }
12318        this._filterLogs();
12319        var elCheckbox = this.getCheckbox(sSource);
12320        if(elCheckbox) {
12321            elCheckbox.checked = false;
12322        }
12323    },
12324
12325    /**
12326     * Does not delete any log messages, but clears all printed log messages from
12327     * the console. Log messages will be printed out again if user re-filters. The
12328     * static method YAHOO.widget.Logger.reset() should be called in order to
12329     * actually delete log messages.
12330     *
12331     * @method clearConsole
12332     */
12333    clearConsole : function() {
12334        // Clear the buffer of any pending messages
12335        this._timeout = null;
12336        this._buffer = [];
12337        this._consoleMsgCount = 0;
12338
12339        var elConsole = this._elConsole;
12340        elConsole.innerHTML = '';
12341    },
12342
12343    /**
12344     * Updates title to given string.
12345     *
12346     * @method setTitle
12347     * @param sTitle {String} New title.
12348     */
12349    setTitle : function(sTitle) {
12350        this._title.innerHTML = this.html2Text(sTitle);
12351    },
12352
12353    /**
12354     * Gets timestamp of the last log.
12355     *
12356     * @method getLastTime
12357     * @return {Date} Timestamp of the last log.
12358     */
12359    getLastTime : function() {
12360        return this._lastTime;
12361    },
12362
12363    formatMsg : function (entry) {
12364        var entryFormat = this.entryFormat || (this.verboseOutput ?
12365                          LogReader.VERBOSE_TEMPLATE : LogReader.BASIC_TEMPLATE),
12366            info        = {
12367                category : entry.category,
12368
12369                // Label for color-coded display
12370                label : entry.category.substring(0,4).toUpperCase(),
12371
12372                sourceAndDetail : entry.sourceDetail ?
12373                                  entry.source + " " + entry.sourceDetail :
12374                                  entry.source,
12375
12376                // Escape HTML entities in the log message itself for output
12377                // to console
12378                message : this.html2Text(entry.msg || entry.message || '')
12379            };
12380
12381        // Add time info
12382        if (entry.time && entry.time.getTime) {
12383            info.localTime = entry.time.toLocaleTimeString ?
12384                             entry.time.toLocaleTimeString() :
12385                             entry.time.toString();
12386
12387            // Calculate the elapsed time to be from the last item that
12388            // passed through the filter, not the absolute previous item
12389            // in the stack
12390            info.elapsedTime = entry.time.getTime() - this.getLastTime();
12391
12392            info.totalTime = entry.time.getTime() - Logger.getStartTime();
12393        }
12394
12395        var msg = LogReader.ENTRY_TEMPLATE.cloneNode(true);
12396        if (this.verboseOutput) {
12397            msg.className += ' yui-log-verbose';
12398        }
12399
12400        // Bug 2061169: Workaround for YAHOO.lang.substitute()
12401        msg.innerHTML = entryFormat.replace(/\{(\w+)\}/g,
12402            function (x, placeholder) {
12403                return (placeholder in info) ? info[placeholder] : '';
12404            });
12405
12406        return msg;
12407    },
12408
12409    /**
12410     * Converts input chars "<", ">", and "&" to HTML entities.
12411     *
12412     * @method html2Text
12413     * @param sHtml {String} String to convert.
12414     * @private
12415     */
12416    html2Text : function(sHtml) {
12417        if(sHtml) {
12418            sHtml += "";
12419            return sHtml.replace(/&/g, "&#38;").
12420                         replace(/</g, "&#60;").
12421                         replace(/>/g, "&#62;");
12422        }
12423        return "";
12424    },
12425
12426/////////////////////////////////////////////////////////////////////////////
12427//
12428// Private member variables
12429//
12430/////////////////////////////////////////////////////////////////////////////
12431
12432    /**
12433     * Name of LogReader instance.
12434     *
12435     * @property _sName
12436     * @type String
12437     * @private
12438     */
12439    _sName : null,
12440
12441    //TODO: remove
12442    /**
12443     * A class member shared by all LogReaders if a container needs to be
12444     * created during instantiation. Will be null if a container element never needs to
12445     * be created on the fly, such as when the implementer passes in their own element.
12446     *
12447     * @property _elDefaultContainer
12448     * @type HTMLElement
12449     * @private
12450     */
12451    //YAHOO.widget.LogReader._elDefaultContainer = null;
12452
12453    /**
12454     * Buffer of log message objects for batch output.
12455     *
12456     * @property _buffer
12457     * @type Object[]
12458     * @private
12459     */
12460    _buffer : null,
12461
12462    /**
12463     * Number of log messages output to console.
12464     *
12465     * @property _consoleMsgCount
12466     * @type Number
12467     * @default 0
12468     * @private
12469     */
12470    _consoleMsgCount : 0,
12471
12472    /**
12473     * Date of last output log message.
12474     *
12475     * @property _lastTime
12476     * @type Date
12477     * @private
12478     */
12479    _lastTime : null,
12480
12481    /**
12482     * Batched output timeout ID.
12483     *
12484     * @property _timeout
12485     * @type Number
12486     * @private
12487     */
12488    _timeout : null,
12489
12490    /**
12491     * Hash of filters and their related checkbox elements.
12492     *
12493     * @property _filterCheckboxes
12494     * @type Object
12495     * @private
12496     */
12497    _filterCheckboxes : null,
12498
12499    /**
12500     * Array of filters for log message categories.
12501     *
12502     * @property _categoryFilters
12503     * @type String[]
12504     * @private
12505     */
12506    _categoryFilters : null,
12507
12508    /**
12509     * Array of filters for log message sources.
12510     *
12511     * @property _sourceFilters
12512     * @type String[]
12513     * @private
12514     */
12515    _sourceFilters : null,
12516
12517    /**
12518     * LogReader container element.
12519     *
12520     * @property _elContainer
12521     * @type HTMLElement
12522     * @private
12523     */
12524    _elContainer : null,
12525
12526    /**
12527     * LogReader header element.
12528     *
12529     * @property _elHd
12530     * @type HTMLElement
12531     * @private
12532     */
12533    _elHd : null,
12534
12535    /**
12536     * LogReader collapse element.
12537     *
12538     * @property _elCollapse
12539     * @type HTMLElement
12540     * @private
12541     */
12542    _elCollapse : null,
12543
12544    /**
12545     * LogReader collapse button element.
12546     *
12547     * @property _btnCollapse
12548     * @type HTMLElement
12549     * @private
12550     */
12551    _btnCollapse : null,
12552
12553    /**
12554     * LogReader title header element.
12555     *
12556     * @property _title
12557     * @type HTMLElement
12558     * @private
12559     */
12560    _title : null,
12561
12562    /**
12563     * LogReader console element.
12564     *
12565     * @property _elConsole
12566     * @type HTMLElement
12567     * @private
12568     */
12569    _elConsole : null,
12570
12571    /**
12572     * LogReader footer element.
12573     *
12574     * @property _elFt
12575     * @type HTMLElement
12576     * @private
12577     */
12578    _elFt : null,
12579
12580    /**
12581     * LogReader buttons container element.
12582     *
12583     * @property _elBtns
12584     * @type HTMLElement
12585     * @private
12586     */
12587    _elBtns : null,
12588
12589    /**
12590     * Container element for LogReader category filter checkboxes.
12591     *
12592     * @property _elCategoryFilters
12593     * @type HTMLElement
12594     * @private
12595     */
12596    _elCategoryFilters : null,
12597
12598    /**
12599     * Container element for LogReader source filter checkboxes.
12600     *
12601     * @property _elSourceFilters
12602     * @type HTMLElement
12603     * @private
12604     */
12605    _elSourceFilters : null,
12606
12607    /**
12608     * LogReader pause button element.
12609     *
12610     * @property _btnPause
12611     * @type HTMLElement
12612     * @private
12613     */
12614    _btnPause : null,
12615
12616    /**
12617     * Clear button element.
12618     *
12619     * @property _btnClear
12620     * @type HTMLElement
12621     * @private
12622     */
12623    _btnClear : null,
12624
12625    /////////////////////////////////////////////////////////////////////////////
12626    //
12627    // Private methods
12628    //
12629    /////////////////////////////////////////////////////////////////////////////
12630
12631    /**
12632     * Initializes the instance's message buffer, start time, etc
12633     *
12634     * @method _init
12635     * @param container {String|HTMLElement} (optional) the render target
12636     * @param config {Object} (optional) instance configuration
12637     * @protected
12638     */
12639    _init : function (container, config) {
12640        // Internal vars
12641        this._buffer = []; // output buffer
12642        this._filterCheckboxes = {}; // pointers to checkboxes
12643        this._lastTime = Logger.getStartTime(); // timestamp of last log message to console
12644
12645        // Parse config vars here
12646        if (config && (config.constructor == Object)) {
12647            for(var param in config) {
12648                if (config.hasOwnProperty(param)) {
12649                    this[param] = config[param];
12650                }
12651            }
12652        }
12653
12654        this._elContainer = Dom.get(container);
12655
12656        YAHOO.log("LogReader initialized", null, this.toString());
12657    },
12658
12659    /**
12660     * Initializes the primary container element.
12661     *
12662     * @method _initContainerEl
12663     * @private
12664     */
12665    _initContainerEl : function() {
12666
12667        // Default the container if unset or not a div
12668        if(!this._elContainer || !/div$/i.test(this._elContainer.tagName)) {
12669            this._elContainer = d.body.insertBefore(make("div"),d.body.firstChild);
12670            // Only position absolutely if an in-DOM element is not supplied
12671            Dom.addClass(this._elContainer,"yui-log-container");
12672        }
12673
12674        Dom.addClass(this._elContainer,"yui-log");
12675
12676        // If implementer has provided container values, trust and set those
12677        var style = this._elContainer.style,
12678            styleProps = ['width','right','top','fontSize'],
12679            prop,i;
12680
12681        for (i = styleProps.length - 1; i >= 0; --i) {
12682            prop = styleProps[i];
12683            if (this[prop]){ 
12684                style[prop] = this[prop];
12685            }
12686        }
12687
12688        if(this.left) {
12689            style.left  = this.left;
12690            style.right = "auto";
12691        }
12692        if(this.bottom) {
12693            style.bottom = this.bottom;
12694            style.top    = "auto";
12695        }
12696
12697        // Opera needs a little prodding to reflow sometimes
12698        if (YAHOO.env.ua.opera) {
12699            d.body.style += '';
12700        }
12701
12702    },
12703
12704    /**
12705     * Initializes the header element.
12706     *
12707     * @method _initHeaderEl
12708     * @private
12709     */
12710    _initHeaderEl : function() {
12711        // Destroy header if present
12712        if(this._elHd) {
12713            // Unhook DOM events
12714            Event.purgeElement(this._elHd, true);
12715
12716            // Remove DOM elements
12717            this._elHd.innerHTML = "";
12718        }
12719        
12720        // Create header
12721        // TODO: refactor this into an innerHTML
12722        this._elHd = make("div",{
12723            className: "yui-log-hd"
12724        });
12725        Dom.generateId(this._elHd, 'yui-log-hd' + this._sName);
12726
12727        this._elCollapse = make("div",{ className: 'yui-log-btns' });
12728
12729        this._btnCollapse = make("input",{
12730            type: 'button',
12731            className: 'yui-log-button',
12732            value: 'Collapse'
12733        });
12734        Event.on(this._btnCollapse,'click',this._onClickCollapseBtn,this);
12735
12736
12737        this._title = make("h4",{ innerHTML : "Logger Console" });
12738
12739        this._elCollapse.appendChild(this._btnCollapse);
12740        this._elHd.appendChild(this._elCollapse);
12741        this._elHd.appendChild(this._title);
12742        this._elContainer.appendChild(this._elHd);
12743    },
12744
12745    /**
12746     * Initializes the console element.
12747     *
12748     * @method _initConsoleEl
12749     * @private
12750     */
12751    _initConsoleEl : function() {
12752        // Destroy console
12753        if(this._elConsole) {
12754            // Unhook DOM events
12755            Event.purgeElement(this._elConsole, true);
12756
12757            // Remove DOM elements
12758            this._elConsole.innerHTML = "";
12759        }
12760
12761        // Ceate console
12762        this._elConsole = make("div", { className: "yui-log-bd" });
12763
12764        // If implementer has provided console, trust and set those
12765        if(this.height) {
12766            this._elConsole.style.height = this.height;
12767        }
12768
12769        this._elContainer.appendChild(this._elConsole);
12770    },
12771
12772    /**
12773     * Initializes the footer element.
12774     *
12775     * @method _initFooterEl
12776     * @private
12777     */
12778    _initFooterEl : function() {
12779        // Don't create footer elements if footer is disabled
12780        if(this.footerEnabled) {
12781            // Destroy console
12782            if(this._elFt) {
12783                // Unhook DOM events
12784                Event.purgeElement(this._elFt, true);
12785
12786                // Remove DOM elements
12787                this._elFt.innerHTML = "";
12788            }
12789
12790            // TODO: use innerHTML
12791            this._elFt = make("div",{ className: "yui-log-ft" });
12792            this._elBtns = make("div", { className: "yui-log-btns" });
12793            this._btnPause = make("input", {
12794                type: "button",
12795                className: "yui-log-button",
12796                value: "Pause"
12797            });
12798
12799            Event.on(this._btnPause,'click',this._onClickPauseBtn,this);
12800
12801            this._btnClear = make("input", {
12802                type: "button",
12803                className: "yui-log-button",
12804                value: "Clear"
12805            });
12806
12807            Event.on(this._btnClear,'click',this._onClickClearBtn,this);
12808
12809            this._elCategoryFilters = make("div", { className: "yui-log-categoryfilters" });
12810            this._elSourceFilters = make("div", { className: "yui-log-sourcefilters" });
12811
12812            this._elBtns.appendChild(this._btnPause);
12813            this._elBtns.appendChild(this._btnClear);
12814            this._elFt.appendChild(this._elBtns);
12815            this._elFt.appendChild(this._elCategoryFilters);
12816            this._elFt.appendChild(this._elSourceFilters);
12817            this._elContainer.appendChild(this._elFt);
12818        }
12819    },
12820
12821    /**
12822     * Initializes Drag and Drop on the header element.
12823     *
12824     * @method _initDragDrop
12825     * @private
12826     */
12827    _initDragDrop : function() {
12828        // If Drag and Drop utility is available...
12829        // ...and draggable is true...
12830        // ...then make the header draggable
12831        if(u.DD && this.draggable && this._elHd) {
12832            var ylog_dd = new u.DD(this._elContainer);
12833            ylog_dd.setHandleElId(this._elHd.id);
12834            //TODO: use class name
12835            this._elHd.style.cursor = "move";
12836        }
12837    },
12838
12839    /**
12840     * Initializes category filters.
12841     *
12842     * @method _initCategories
12843     * @private
12844     */
12845    _initCategories : function() {
12846        // Initialize category filters
12847        this._categoryFilters = [];
12848        var aInitialCategories = Logger.categories;
12849
12850        for(var j=0; j < aInitialCategories.length; j++) {
12851            var sCategory = aInitialCategories[j];
12852
12853            // Add category to the internal array of filters
12854            this._categoryFilters.push(sCategory);
12855
12856            // Add checkbox element if UI is enabled
12857            if(this._elCategoryFilters) {
12858                this._createCategoryCheckbox(sCategory);
12859            }
12860        }
12861    },
12862
12863    /**
12864     * Initializes source filters.
12865     *
12866     * @method _initSources
12867     * @private
12868     */
12869    _initSources : function() {
12870        // Initialize source filters
12871        this._sourceFilters = [];
12872        var aInitialSources = Logger.sources;
12873
12874        for(var j=0; j < aInitialSources.length; j++) {
12875            var sSource = aInitialSources[j];
12876
12877            // Add source to the internal array of filters
12878            this._sourceFilters.push(sSource);
12879
12880            // Add checkbox element if UI is enabled
12881            if(this._elSourceFilters) {
12882                this._createSourceCheckbox(sSource);
12883            }
12884        }
12885    },
12886
12887    /**
12888     * Creates the UI for a category filter in the LogReader footer element.
12889     *
12890     * @method _createCategoryCheckbox
12891     * @param sCategory {String} Category name.
12892     * @private
12893     */
12894    _createCategoryCheckbox : function(sCategory) {
12895        if(this._elFt) {
12896            var filter = make("span",{ className: "yui-log-filtergrp" }),
12897                checkid = Dom.generateId(null, "yui-log-filter-" + sCategory + this._sName),
12898                check  = make("input", {
12899                    id: checkid,
12900                    className: "yui-log-filter-" + sCategory,
12901                    type: "checkbox",
12902                    category: sCategory
12903                }),
12904                label  = make("label", {
12905                    htmlFor: checkid,
12906                    className: sCategory,
12907                    innerHTML: sCategory
12908                });
12909            
12910
12911            // Subscribe to the click event
12912            Event.on(check,'click',this._onCheckCategory,this);
12913
12914            this._filterCheckboxes[sCategory] = check;
12915
12916            // Append el at the end so IE 5.5 can set "type" attribute
12917            // and THEN set checked property
12918            filter.appendChild(check);
12919            filter.appendChild(label);
12920            this._elCategoryFilters.appendChild(filter);
12921            check.checked = true;
12922        }
12923    },
12924
12925    /**
12926     * Creates a checkbox in the LogReader footer element to filter by source.
12927     *
12928     * @method _createSourceCheckbox
12929     * @param sSource {String} Source name.
12930     * @private
12931     */
12932    _createSourceCheckbox : function(sSource) {
12933        if(this._elFt) {
12934            var filter = make("span",{ className: "yui-log-filtergrp" }),
12935                checkid = Dom.generateId(null, "yui-log-filter-" + sSource + this._sName),
12936                check  = make("input", {
12937                    id: checkid,
12938                    className: "yui-log-filter-" + sSource,
12939                    type: "checkbox",
12940                    source: sSource
12941                }),
12942                label  = make("label", {
12943                    htmlFor: checkid,
12944                    className: sSource,
12945                    innerHTML: sSource
12946                });
12947            
12948
12949            // Subscribe to the click event
12950            Event.on(check,'click',this._onCheckSource,this);
12951
12952            this._filterCheckboxes[sSource] = check;
12953
12954            // Append el at the end so IE 5.5 can set "type" attribute
12955            // and THEN set checked property
12956            filter.appendChild(check);
12957            filter.appendChild(label);
12958            this._elSourceFilters.appendChild(filter);
12959            check.checked = true;
12960        }
12961    },
12962
12963    /**
12964     * Reprints all log messages in the stack through filters.
12965     *
12966     * @method _filterLogs
12967     * @private
12968     */
12969    _filterLogs : function() {
12970        // Reprint stack with new filters
12971        if (this._elConsole !== null) {
12972            this.clearConsole();
12973            this._printToConsole(Logger.getStack());
12974        }
12975    },
12976
12977    /**
12978     * Sends buffer of log messages to output and clears buffer.
12979     *
12980     * @method _printBuffer
12981     * @private
12982     */
12983    _printBuffer : function() {
12984        this._timeout = null;
12985
12986        if(this._elConsole !== null) {
12987            var thresholdMax = this.thresholdMax;
12988            thresholdMax = (thresholdMax && !isNaN(thresholdMax)) ? thresholdMax : 500;
12989            if(this._consoleMsgCount < thresholdMax) {
12990                var entries = [];
12991                for (var i=0; i<this._buffer.length; i++) {
12992                    entries[i] = this._buffer[i];
12993                }
12994                this._buffer = [];
12995                this._printToConsole(entries);
12996            }
12997            else {
12998                this._filterLogs();
12999            }
13000            
13001            if(!this.newestOnTop) {
13002                this._elConsole.scrollTop = this._elConsole.scrollHeight;
13003            }
13004        }
13005    },
13006
13007    /**
13008     * Cycles through an array of log messages, and outputs each one to the console
13009     * if its category has not been filtered out.
13010     *
13011     * @method _printToConsole
13012     * @param aEntries {Object[]} Array of LogMsg objects to output to console.
13013     * @private
13014     */
13015    _printToConsole : function(aEntries) {
13016        // Manage the number of messages displayed in the console
13017        var entriesLen         = aEntries.length,
13018            df                 = d.createDocumentFragment(),
13019            msgHTML            = [],
13020            thresholdMin       = this.thresholdMin,
13021            sourceFiltersLen   = this._sourceFilters.length,
13022            categoryFiltersLen = this._categoryFilters.length,
13023            entriesStartIndex,
13024            i, j, msg, before;
13025
13026        if(isNaN(thresholdMin) || (thresholdMin > this.thresholdMax)) {
13027            thresholdMin = 0;
13028        }
13029        entriesStartIndex = (entriesLen > thresholdMin) ? (entriesLen - thresholdMin) : 0;
13030        
13031        // Iterate through all log entries 
13032        for(i=entriesStartIndex; i<entriesLen; i++) {
13033            // Print only the ones that filter through
13034            var okToPrint = false,
13035                okToFilterCats = false,
13036                entry = aEntries[i],
13037                source = entry.source,
13038                category = entry.category;
13039
13040            for(j=0; j<sourceFiltersLen; j++) {
13041                if(source == this._sourceFilters[j]) {
13042                    okToFilterCats = true;
13043                    break;
13044                }
13045            }
13046            if(okToFilterCats) {
13047                for(j=0; j<categoryFiltersLen; j++) {
13048                    if(category == this._categoryFilters[j]) {
13049                        okToPrint = true;
13050                        break;
13051                    }
13052                }
13053            }
13054            if(okToPrint) {
13055                // Start from 0ms elapsed time
13056                if (this._consoleMsgCount === 0) {
13057                    this._lastTime = entry.time.getTime();
13058                }
13059
13060                msg = this.formatMsg(entry);
13061                if (typeof msg === 'string') {
13062                    msgHTML[msgHTML.length] = msg;
13063                } else {
13064                    df.insertBefore(msg, this.newestOnTop ?
13065                        df.firstChild || null : null);
13066                }
13067                this._consoleMsgCount++;
13068                this._lastTime = entry.time.getTime();
13069            }
13070        }
13071
13072        if (msgHTML.length) {
13073            msgHTML.splice(0,0,this._elConsole.innerHTML);
13074            this._elConsole.innerHTML = this.newestOnTop ?
13075                                            msgHTML.reverse().join('') :
13076                                            msgHTML.join('');
13077        } else if (df.firstChild) {
13078            this._elConsole.insertBefore(df, this.newestOnTop ?
13079                        this._elConsole.firstChild || null : null);
13080        }
13081    },
13082
13083/////////////////////////////////////////////////////////////////////////////
13084//
13085// Private event handlers
13086//
13087/////////////////////////////////////////////////////////////////////////////
13088
13089    /**
13090     * Handles Logger's categoryCreateEvent.
13091     *
13092     * @method _onCategoryCreate
13093     * @param sType {String} The event.
13094     * @param aArgs {Object[]} Data passed from event firer.
13095     * @param oSelf {Object} The LogReader instance.
13096     * @private
13097     */
13098    _onCategoryCreate : function(sType, aArgs, oSelf) {
13099        var category = aArgs[0];
13100        
13101        // Add category to the internal array of filters
13102        oSelf._categoryFilters.push(category);
13103
13104        if(oSelf._elFt) {
13105            oSelf._createCategoryCheckbox(category);
13106        }
13107    },
13108
13109    /**
13110     * Handles Logger's sourceCreateEvent.
13111     *
13112     * @method _onSourceCreate
13113     * @param sType {String} The event.
13114     * @param aArgs {Object[]} Data passed from event firer.
13115     * @param oSelf {Object} The LogReader instance.
13116     * @private
13117     */
13118    _onSourceCreate : function(sType, aArgs, oSelf) {
13119        var source = aArgs[0];
13120        
13121        // Add source to the internal array of filters
13122        oSelf._sourceFilters.push(source);
13123
13124        if(oSelf._elFt) {
13125            oSelf._createSourceCheckbox(source);
13126        }
13127    },
13128
13129    /**
13130     * Handles check events on the category filter checkboxes.
13131     *
13132     * @method _onCheckCategory
13133     * @param v {HTMLEvent} The click event.
13134     * @param oSelf {Object} The LogReader instance.
13135     * @private
13136     */
13137    _onCheckCategory : function(v, oSelf) {
13138        var category = this.category;
13139        if(!this.checked) {
13140            oSelf.hideCategory(category);
13141        }
13142        else {
13143            oSelf.showCategory(category);
13144        }
13145    },
13146
13147    /**
13148     * Handles check events on the category filter checkboxes.
13149     *
13150     * @method _onCheckSource
13151     * @param v {HTMLEvent} The click event.
13152     * @param oSelf {Object} The LogReader instance.
13153     * @private
13154     */
13155    _onCheckSource : function(v, oSelf) {
13156        var source = this.source;
13157        if(!this.checked) {
13158            oSelf.hideSource(source);
13159        }
13160        else {
13161            oSelf.showSource(source);
13162        }
13163    },
13164
13165    /**
13166     * Handles click events on the collapse button.
13167     *
13168     * @method _onClickCollapseBtn
13169     * @param v {HTMLEvent} The click event.
13170     * @param oSelf {Object} The LogReader instance
13171     * @private
13172     */
13173    _onClickCollapseBtn : function(v, oSelf) {
13174        if(!oSelf.isCollapsed) {
13175            oSelf.collapse();
13176        }
13177        else {
13178            oSelf.expand();
13179        }
13180    },
13181
13182    /**
13183     * Handles click events on the pause button.
13184     *
13185     * @method _onClickPauseBtn
13186     * @param v {HTMLEvent} The click event.
13187     * @param oSelf {Object} The LogReader instance.
13188     * @private
13189     */
13190    _onClickPauseBtn : function(v, oSelf) {
13191        if(!oSelf.isPaused) {
13192            oSelf.pause();
13193        }
13194        else {
13195            oSelf.resume();
13196        }
13197    },
13198
13199    /**
13200     * Handles click events on the clear button.
13201     *
13202     * @method _onClickClearBtn
13203     * @param v {HTMLEvent} The click event.
13204     * @param oSelf {Object} The LogReader instance.
13205     * @private
13206     */
13207    _onClickClearBtn : function(v, oSelf) {
13208        oSelf.clearConsole();
13209    },
13210
13211    /**
13212     * Handles Logger's newLogEvent.
13213     *
13214     * @method _onNewLog
13215     * @param sType {String} The event.
13216     * @param aArgs {Object[]} Data passed from event firer.
13217     * @param oSelf {Object} The LogReader instance.
13218     * @private
13219     */
13220    _onNewLog : function(sType, aArgs, oSelf) {
13221        var logEntry = aArgs[0];
13222        oSelf._buffer.push(logEntry);
13223
13224        if (oSelf.logReaderEnabled === true && oSelf._timeout === null) {
13225            oSelf._timeout = setTimeout(function(){oSelf._printBuffer();}, oSelf.outputBuffer);
13226        }
13227    },
13228
13229    /**
13230     * Handles Logger's resetEvent.
13231     *
13232     * @method _onReset
13233     * @param sType {String} The event.
13234     * @param aArgs {Object[]} Data passed from event firer.
13235     * @param oSelf {Object} The LogReader instance.
13236     * @private
13237     */
13238    _onReset : function(sType, aArgs, oSelf) {
13239        oSelf._filterLogs();
13240    }
13241};
13242
13243YAHOO.widget.LogReader = LogReader;
13244
13245})();
13246YAHOO.register("logger", YAHOO.widget.Logger, {version: "2.9.0", build: "2800"});
13247/*
13248Copyright (c) 2011, Yahoo! Inc. All rights reserved.
13249Code licensed under the BSD License:
13250http://developer.yahoo.com/yui/license.html
13251version: 2.9.0
13252*/
13253/**
13254 * The Connection Manager provides a simplified interface to the XMLHttpRequest
13255 * object.  It handles cross-browser instantiantion of XMLHttpRequest, negotiates the
13256 * interactive states and server response, returning the results to a pre-defined
13257 * callback you create.
13258 *
13259 * @namespace YAHOO.util
13260 * @module connection
13261 * @requires yahoo
13262 * @requires event
13263 */
13264
13265/**
13266 * The Connection Manager singleton provides methods for creating and managing
13267 * asynchronous transactions.
13268 *
13269 * @class YAHOO.util.Connect
13270 */
13271
13272YAHOO.util.Connect =
13273{
13274  /**
13275   * @description Array of MSFT ActiveX ids for XMLHttpRequest.
13276   * @property _msxml_progid
13277   * @private
13278   * @static
13279   * @type array
13280   */
13281    _msxml_progid:[
13282        'Microsoft.XMLHTTP',
13283        'MSXML2.XMLHTTP.3.0',
13284        'MSXML2.XMLHTTP'
13285        ],
13286
13287  /**
13288   * @description Object literal of HTTP header(s)
13289   * @property _http_header
13290   * @private
13291   * @static
13292   * @type object
13293   */
13294    _http_headers:{},
13295
13296  /**
13297   * @description Determines if HTTP headers are set.
13298   * @property _has_http_headers
13299   * @private
13300   * @static
13301   * @type boolean
13302   */
13303    _has_http_headers:false,
13304
13305 /**
13306  * @description Determines if a default header of
13307  * Content-Type of 'application/x-www-form-urlencoded'
13308  * will be added to any client HTTP headers sent for POST
13309  * transactions.
13310  * @property _use_default_post_header
13311  * @private
13312  * @static
13313  * @type boolean
13314  */
13315    _use_default_post_header:true,
13316
13317 /**
13318  * @description The default header used for POST transactions.
13319  * @property _default_post_header
13320  * @private
13321  * @static
13322  * @type boolean
13323  */
13324    _default_post_header:'application/x-www-form-urlencoded; charset=UTF-8',
13325
13326 /**
13327  * @description The default header used for transactions involving the
13328  * use of HTML forms.
13329  * @property _default_form_header
13330  * @private
13331  * @static
13332  * @type boolean
13333  */
13334    _default_form_header:'application/x-www-form-urlencoded',
13335
13336 /**
13337  * @description Determines if a default header of
13338  * 'X-Requested-With: XMLHttpRequest'
13339  * will be added to each transaction.
13340  * @property _use_default_xhr_header
13341  * @private
13342  * @static
13343  * @type boolean
13344  */
13345    _use_default_xhr_header:true,
13346
13347 /**
13348  * @description The default header value for the label
13349  * "X-Requested-With".  This is sent with each
13350  * transaction, by default, to identify the
13351  * request as being made by YUI Connection Manager.
13352  * @property _default_xhr_header
13353  * @private
13354  * @static
13355  * @type boolean
13356  */
13357    _default_xhr_header:'XMLHttpRequest',
13358
13359 /**
13360  * @description Determines if custom, default headers
13361  * are set for each transaction.
13362  * @property _has_default_header
13363  * @private
13364  * @static
13365  * @type boolean
13366  */
13367    _has_default_headers:true,
13368
13369 /**
13370   * @description Property modified by setForm() to determine if the data
13371   * should be submitted as an HTML form.
13372   * @property _isFormSubmit
13373   * @private
13374   * @static
13375   * @type boolean
13376   */
13377	_isFormSubmit:false,
13378
13379 /**
13380  * @description Determines if custom, default headers
13381  * are set for each transaction.
13382  * @property _has_default_header
13383  * @private
13384  * @static
13385  * @type boolean
13386  */
13387    _default_headers:{},
13388
13389 /**
13390  * @description Collection of polling references to the polling mechanism in handleReadyState.
13391  * @property _poll
13392  * @private
13393  * @static
13394  * @type object
13395  */
13396    _poll:{},
13397
13398 /**
13399  * @description Queue of timeout values for each transaction callback with a defined timeout value.
13400  * @property _timeOut
13401  * @private
13402  * @static
13403  * @type object
13404  */
13405    _timeOut:{},
13406
13407  /**
13408   * @description The polling frequency, in milliseconds, for HandleReadyState.
13409   * when attempting to determine a transaction's XHR readyState.
13410   * The default is 50 milliseconds.
13411   * @property _polling_interval
13412   * @private
13413   * @static
13414   * @type int
13415   */
13416     _polling_interval:50,
13417
13418  /**
13419   * @description A transaction counter that increments the transaction id for each transaction.
13420   * @property _transaction_id
13421   * @private
13422   * @static
13423   * @type int
13424   */
13425     _transaction_id:0,
13426
13427  /**
13428   * @description Custom event that fires at the start of a transaction
13429   * @property startEvent
13430   * @private
13431   * @static
13432   * @type CustomEvent
13433   */
13434    startEvent: new YAHOO.util.CustomEvent('start'),
13435
13436  /**
13437   * @description Custom event that fires when a transaction response has completed.
13438   * @property completeEvent
13439   * @private
13440   * @static
13441   * @type CustomEvent
13442   */
13443    completeEvent: new YAHOO.util.CustomEvent('complete'),
13444
13445  /**
13446   * @description Custom event that fires when handleTransactionResponse() determines a
13447   * response in the HTTP 2xx range.
13448   * @property successEvent
13449   * @private
13450   * @static
13451   * @type CustomEvent
13452   */
13453    successEvent: new YAHOO.util.CustomEvent('success'),
13454
13455  /**
13456   * @description Custom event that fires when handleTransactionResponse() determines a
13457   * response in the HTTP 4xx/5xx range.
13458   * @property failureEvent
13459   * @private
13460   * @static
13461   * @type CustomEvent
13462   */
13463    failureEvent: new YAHOO.util.CustomEvent('failure'),
13464
13465  /**
13466   * @description Custom event that fires when a transaction is successfully aborted.
13467   * @property abortEvent
13468   * @private
13469   * @static
13470   * @type CustomEvent
13471   */
13472    abortEvent: new YAHOO.util.CustomEvent('abort'),
13473
13474  /**
13475   * @description A reference table that maps callback custom events members to its specific
13476   * event name.
13477   * @property _customEvents
13478   * @private
13479   * @static
13480   * @type object
13481   */
13482    _customEvents:
13483    {
13484        onStart:['startEvent', 'start'],
13485        onComplete:['completeEvent', 'complete'],
13486        onSuccess:['successEvent', 'success'],
13487        onFailure:['failureEvent', 'failure'],
13488        onUpload:['uploadEvent', 'upload'],
13489        onAbort:['abortEvent', 'abort']
13490    },
13491
13492  /**
13493   * @description Member to add an ActiveX id to the existing xml_progid array.
13494   * In the event(unlikely) a new ActiveX id is introduced, it can be added
13495   * without internal code modifications.
13496   * @method setProgId
13497   * @public
13498   * @static
13499   * @param {string} id The ActiveX id to be added to initialize the XHR object.
13500   * @return void
13501   */
13502    setProgId:function(id)
13503    {
13504        this._msxml_progid.unshift(id);
13505        YAHOO.log('ActiveX Program Id  ' + id + ' added to _msxml_progid.', 'info', 'Connection');
13506    },
13507
13508  /**
13509   * @description Member to override the default POST header.
13510   * @method setDefaultPostHeader
13511   * @public
13512   * @static
13513   * @param {boolean} b Set and use default header - true or false .
13514   * @return void
13515   */
13516    setDefaultPostHeader:function(b)
13517    {
13518        if(typeof b == 'string'){
13519            this._default_post_header = b;
13520			this._use_default_post_header = true;
13521
13522            YAHOO.log('Default POST header set to  ' + b, 'info', 'Connection');
13523        }
13524        else if(typeof b == 'boolean'){
13525            this._use_default_post_header = b;
13526        }
13527    },
13528
13529  /**
13530   * @description Member to override the default transaction header..
13531   * @method setDefaultXhrHeader
13532   * @public
13533   * @static
13534   * @param {boolean} b Set and use default header - true or false .
13535   * @return void
13536   */
13537    setDefaultXhrHeader:function(b)
13538    {
13539        if(typeof b == 'string'){
13540            this._default_xhr_header = b;
13541            YAHOO.log('Default XHR header set to  ' + b, 'info', 'Connection');
13542        }
13543        else{
13544            this._use_default_xhr_header = b;
13545        }
13546    },
13547
13548  /**
13549   * @description Member to modify the default polling interval.
13550   * @method setPollingInterval
13551   * @public
13552   * @static
13553   * @param {int} i The polling interval in milliseconds.
13554   * @return void
13555   */
13556    setPollingInterval:function(i)
13557    {
13558        if(typeof i == 'number' && isFinite(i)){
13559            this._polling_interval = i;
13560            YAHOO.log('Default polling interval set to ' + i +'ms', 'info', 'Connection');
13561        }
13562    },
13563
13564  /**
13565   * @description Instantiates a XMLHttpRequest object and returns an object with two properties:
13566   * the XMLHttpRequest instance and the transaction id.
13567   * @method createXhrObject
13568   * @private
13569   * @static
13570   * @param {int} transactionId Property containing the transaction id for this transaction.
13571   * @return object
13572   */
13573    createXhrObject:function(transactionId)
13574    {
13575        var obj,http,i;
13576        try
13577        {
13578            // Instantiates XMLHttpRequest in non-IE browsers and assigns to http.
13579            http = new XMLHttpRequest();
13580            //  Object literal with http and tId properties
13581            obj = { conn:http, tId:transactionId, xhr: true };
13582            YAHOO.log('XHR object created for transaction ' + transactionId, 'info', 'Connection');
13583        }
13584        catch(e)
13585        {
13586            for(i=0; i<this._msxml_progid.length; ++i){
13587                try
13588                {
13589                    // Instantiates XMLHttpRequest for IE and assign to http
13590                    http = new ActiveXObject(this._msxml_progid[i]);
13591                    //  Object literal with conn and tId properties
13592                    obj = { conn:http, tId:transactionId, xhr: true };
13593                    YAHOO.log('ActiveX XHR object created for transaction ' + transactionId, 'info', 'Connection');
13594                    break;
13595                }
13596                catch(e1){}
13597            }
13598        }
13599        finally
13600        {
13601            return obj;
13602        }
13603    },
13604
13605  /**
13606   * @description This method is called by asyncRequest to create a
13607   * valid connection object for the transaction.  It also passes a
13608   * transaction id and increments the transaction id counter.
13609   * @method getConnectionObject
13610   * @private
13611   * @static
13612   * @return {object}
13613   */
13614    getConnectionObject:function(t)
13615    {
13616        var o, tId = this._transaction_id;
13617
13618        try
13619        {
13620            if(!t){
13621                o = this.createXhrObject(tId);
13622            }
13623            else{
13624                o = {tId:tId};
13625                if(t==='xdr'){
13626                    o.conn = this._transport;
13627                    o.xdr = true;
13628                }
13629                else if(t==='upload'){
13630                    o.upload = true;
13631                }
13632            }
13633
13634            if(o){
13635                this._transaction_id++;
13636            }
13637        }
13638        catch(e){}
13639        return o;
13640    },
13641
13642  /**
13643   * @description Method for initiating an asynchronous request via the XHR object.
13644   * @method asyncRequest
13645   * @public
13646   * @static
13647   * @param {string} method HTTP transaction method
13648   * @param {string} uri Fully qualified path of resource
13649   * @param {callback} callback User-defined callback function or object
13650   * @param {string} postData POST body
13651   * @return {object} Returns the connection object
13652   */
13653    asyncRequest:function(method, uri, callback, postData)
13654    {
13655        var args = callback&&callback.argument?callback.argument:null,
13656            YCM = this,
13657            o, t;
13658
13659        if(this._isFileUpload){
13660            t = 'upload';
13661        }
13662        else if(callback && callback.xdr){
13663            t = 'xdr';
13664        }
13665
13666        o = this.getConnectionObject(t);
13667        if(!o){
13668            YAHOO.log('Unable to create connection object.', 'error', 'Connection');
13669            return null;
13670        }
13671        else{
13672
13673            // Intialize any transaction-specific custom events, if provided.
13674            if(callback && callback.customevents){
13675                this.initCustomEvents(o, callback);
13676            }
13677
13678            if(this._isFormSubmit){
13679                if(this._isFileUpload){
13680                    window.setTimeout(function(){YCM.uploadFile(o, callback, uri, postData);}, 10);
13681                    return o;
13682                }
13683
13684                // If the specified HTTP method is GET, setForm() will return an
13685                // encoded string that is concatenated to the uri to
13686                // create a querystring.
13687                if(method.toUpperCase() == 'GET'){
13688                    if(this._sFormData.length !== 0){
13689                        // If the URI already contains a querystring, append an ampersand
13690                        // and then concatenate _sFormData to the URI.
13691                        uri += ((uri.indexOf('?') == -1)?'?':'&') + this._sFormData;
13692                    }
13693                }
13694                else if(method.toUpperCase() == 'POST'){
13695                    // If POST data exist in addition to the HTML form data,
13696                    // it will be concatenated to the form data.
13697                    postData = postData?this._sFormData + "&" + postData:this._sFormData;
13698                }
13699            }
13700
13701            if(method.toUpperCase() == 'GET' && (callback && callback.cache === false)){
13702                // If callback.cache is defined and set to false, a
13703                // timestamp value will be added to the querystring.
13704                uri += ((uri.indexOf('?') == -1)?'?':'&') + "rnd=" + new Date().valueOf().toString();
13705            }
13706
13707            // Each transaction will automatically include a custom header of
13708            // "X-Requested-With: XMLHttpRequest" to identify the request as
13709            // having originated from Connection Manager.
13710            if(this._use_default_xhr_header){
13711                if(!this._default_headers['X-Requested-With']){
13712                    this.initHeader('X-Requested-With', this._default_xhr_header, true);
13713                    YAHOO.log('Initialize transaction header X-Request-Header to XMLHttpRequest.', 'info', 'Connection');
13714                }
13715            }
13716
13717            //If the transaction method is POST and the POST header value is set to true
13718            //or a custom value, initalize the Content-Type header to this value.
13719            if((method.toUpperCase() === 'POST' && this._use_default_post_header) && this._isFormSubmit === false){
13720                this.initHeader('Content-Type', this._default_post_header);
13721                YAHOO.log('Initialize header Content-Type to application/x-www-form-urlencoded; UTF-8 for POST transaction.', 'info', 'Connection');
13722            }
13723
13724            if(o.xdr){
13725                this.xdr(o, method, uri, callback, postData);
13726                return o;
13727            }
13728
13729            o.conn.open(method, uri, true);
13730            //Initialize all default and custom HTTP headers,
13731            if(this._has_default_headers || this._has_http_headers){
13732                this.setHeader(o);
13733            }
13734
13735            this.handleReadyState(o, callback);
13736            o.conn.send(postData || '');
13737            YAHOO.log('Transaction ' + o.tId + ' sent.', 'info', 'Connection');
13738
13739            // Reset the HTML form data and state properties as
13740            // soon as the data are submitted.
13741            if(this._isFormSubmit === true){
13742                this.resetFormState();
13743            }
13744
13745            // Fire global custom event -- startEvent
13746            this.startEvent.fire(o, args);
13747
13748            if(o.startEvent){
13749                // Fire transaction custom event -- startEvent
13750                o.startEvent.fire(o, args);
13751            }
13752
13753            return o;
13754        }
13755    },
13756
13757  /**
13758   * @description This method creates and subscribes custom events,
13759   * specific to each transaction
13760   * @method initCustomEvents
13761   * @private
13762   * @static
13763   * @param {object} o The connection object
13764   * @param {callback} callback The user-defined callback object
13765   * @return {void}
13766   */
13767    initCustomEvents:function(o, callback)
13768    {
13769        var prop;
13770        // Enumerate through callback.customevents members and bind/subscribe
13771        // events that match in the _customEvents table.
13772        for(prop in callback.customevents){
13773            if(this._customEvents[prop][0]){
13774                // Create the custom event
13775                o[this._customEvents[prop][0]] = new YAHOO.util.CustomEvent(this._customEvents[prop][1], (callback.scope)?callback.scope:null);
13776                YAHOO.log('Transaction-specific Custom Event ' + o[this._customEvents[prop][1]] + ' created.', 'info', 'Connection');
13777
13778                // Subscribe the custom event
13779                o[this._customEvents[prop][0]].subscribe(callback.customevents[prop]);
13780                YAHOO.log('Transaction-specific Custom Event ' + o[this._customEvents[prop][1]] + ' subscribed.', 'info', 'Connection');
13781            }
13782        }
13783    },
13784
13785  /**
13786   * @description This method serves as a timer that polls the XHR object's readyState
13787   * property during a transaction, instead of binding a callback to the
13788   * onreadystatechange event.  Upon readyState 4, handleTransactionResponse
13789   * will process the response, and the timer will be cleared.
13790   * @method handleReadyState
13791   * @private
13792   * @static
13793   * @param {object} o The connection object
13794   * @param {callback} callback The user-defined callback object
13795   * @return {void}
13796   */
13797
13798    handleReadyState:function(o, callback)
13799
13800    {
13801        var oConn = this,
13802            args = (callback && callback.argument)?callback.argument:null;
13803
13804        if(callback && callback.timeout){
13805            this._timeOut[o.tId] = window.setTimeout(function(){ oConn.abort(o, callback, true); }, callback.timeout);
13806        }
13807
13808        this._poll[o.tId] = window.setInterval(
13809            function(){
13810                if(o.conn && o.conn.readyState === 4){
13811
13812                    // Clear the polling interval for the transaction
13813                    // and remove the reference from _poll.
13814                    window.clearInterval(oConn._poll[o.tId]);
13815                    delete oConn._poll[o.tId];
13816
13817                    if(callback && callback.timeout){
13818                        window.clearTimeout(oConn._timeOut[o.tId]);
13819                        delete oConn._timeOut[o.tId];
13820                    }
13821
13822                    // Fire global custom event -- completeEvent
13823                    oConn.completeEvent.fire(o, args);
13824
13825                    if(o.completeEvent){
13826                        // Fire transaction custom event -- completeEvent
13827                        o.completeEvent.fire(o, args);
13828                    }
13829
13830                    oConn.handleTransactionResponse(o, callback);
13831                }
13832            }
13833        ,this._polling_interval);
13834    },
13835
13836  /**
13837   * @description This method attempts to interpret the server response and
13838   * determine whether the transaction was successful, or if an error or
13839   * exception was encountered.
13840   * @method handleTransactionResponse
13841   * @private
13842   * @static
13843   * @param {object} o The connection object
13844   * @param {object} callback The user-defined callback object
13845   * @param {boolean} isAbort Determines if the transaction was terminated via abort().
13846   * @return {void}
13847   */
13848    handleTransactionResponse:function(o, callback, isAbort)
13849    {
13850        var httpStatus, responseObject,
13851            args = (callback && callback.argument)?callback.argument:null,
13852            xdrS = (o.r && o.r.statusText === 'xdr:success')?true:false,
13853            xdrF = (o.r && o.r.statusText === 'xdr:failure')?true:false,
13854            xdrA = isAbort;
13855
13856        try
13857        {
13858            if((o.conn.status !== undefined && o.conn.status !== 0) || xdrS){
13859                // XDR requests will not have HTTP status defined. The
13860                // statusText property will define the response status
13861                // set by the Flash transport.
13862                httpStatus = o.conn.status;
13863            }
13864            else if(xdrF && !xdrA){
13865                // Set XDR transaction failure to a status of 0, which
13866                // resolves as an HTTP failure, instead of an exception.
13867                httpStatus = 0;
13868            }
13869            else{
13870                httpStatus = 13030;
13871            }
13872        }
13873        catch(e){
13874
13875             // 13030 is a custom code to indicate the condition -- in Mozilla/FF --
13876             // when the XHR object's status and statusText properties are
13877             // unavailable, and a query attempt throws an exception.
13878            httpStatus = 13030;
13879        }
13880
13881        if((httpStatus >= 200 && httpStatus < 300) || httpStatus === 1223 || xdrS){
13882            responseObject = o.xdr ? o.r : this.createResponseObject(o, args);
13883            if(callback && callback.success){
13884                if(!callback.scope){
13885                    callback.success(responseObject);
13886                    YAHOO.log('Success callback. HTTP code is ' + httpStatus, 'info', 'Connection');
13887                }
13888                else{
13889                    // If a scope property is defined, the callback will be fired from
13890                    // the context of the object.
13891                    callback.success.apply(callback.scope, [responseObject]);
13892                    YAHOO.log('Success callback with scope. HTTP code is ' + httpStatus, 'info', 'Connection');
13893                }
13894            }
13895
13896            // Fire global custom event -- successEvent
13897            this.successEvent.fire(responseObject);
13898
13899            if(o.successEvent){
13900                // Fire transaction custom event -- successEvent
13901                o.successEvent.fire(responseObject);
13902            }
13903        }
13904        else{
13905            switch(httpStatus){
13906                // The following cases are wininet.dll error codes that may be encountered.
13907                case 12002: // Server timeout
13908                case 12029: // 12029 to 12031 correspond to dropped connections.
13909                case 12030:
13910                case 12031:
13911                case 12152: // Connection closed by server.
13912                case 13030: // See above comments for variable status.
13913                    // XDR transactions will not resolve to this case, since the
13914                    // response object is already built in the xdr response.
13915                    responseObject = this.createExceptionObject(o.tId, args, (isAbort?isAbort:false));
13916                    if(callback && callback.failure){
13917                        if(!callback.scope){
13918                            callback.failure(responseObject);
13919                            YAHOO.log('Failure callback. Exception detected. Status code is ' + httpStatus, 'warn', 'Connection');
13920                        }
13921                        else{
13922                            callback.failure.apply(callback.scope, [responseObject]);
13923                            YAHOO.log('Failure callback with scope. Exception detected. Status code is ' + httpStatus, 'warn', 'Connection');
13924                        }
13925                    }
13926
13927                    break;
13928                default:
13929                    responseObject = (o.xdr) ? o.response : this.createResponseObject(o, args);
13930                    if(callback && callback.failure){
13931                        if(!callback.scope){
13932                            callback.failure(responseObject);
13933                            YAHOO.log('Failure callback. HTTP status code is ' + httpStatus, 'warn', 'Connection');
13934                        }
13935                        else{
13936                            callback.failure.apply(callback.scope, [responseObject]);
13937                            YAHOO.log('Failure callback with scope. HTTP status code is ' + httpStatus, 'warn', 'Connection');
13938                        }
13939                    }
13940            }
13941
13942            // Fire global custom event -- failureEvent
13943            this.failureEvent.fire(responseObject);
13944
13945            if(o.failureEvent){
13946                // Fire transaction custom event -- failureEvent
13947                o.failureEvent.fire(responseObject);
13948            }
13949
13950        }
13951
13952        this.releaseObject(o);
13953        responseObject = null;
13954    },
13955
13956  /**
13957   * @description This method evaluates the server response, creates and returns the results via
13958   * its properties.  Success and failure cases will differ in the response
13959   * object's property values.
13960   * @method createResponseObject
13961   * @private
13962   * @static
13963   * @param {object} o The connection object
13964   * @param {callbackArg} callbackArg The user-defined argument or arguments to be passed to the callback
13965   * @return {object}
13966   */
13967    createResponseObject:function(o, callbackArg)
13968    {
13969        var obj = {}, headerObj = {},
13970            i, headerStr, header, delimitPos;
13971
13972        try
13973        {
13974            headerStr = o.conn.getAllResponseHeaders();
13975            header = headerStr.split('\n');
13976            for(i=0; i<header.length; i++){
13977                delimitPos = header[i].indexOf(':');
13978                if(delimitPos != -1){
13979                    headerObj[header[i].substring(0,delimitPos)] = YAHOO.lang.trim(header[i].substring(delimitPos+2));
13980                }
13981            }
13982        }
13983        catch(e){}
13984
13985        obj.tId = o.tId;
13986        // Normalize IE's response to HTTP 204 when Win error 1223.
13987        obj.status = (o.conn.status == 1223)?204:o.conn.status;
13988        // Normalize IE's statusText to "No Content" instead of "Unknown".
13989        obj.statusText = (o.conn.status == 1223)?"No Content":o.conn.statusText;
13990        obj.getResponseHeader = headerObj;
13991        obj.getAllResponseHeaders = headerStr;
13992        obj.responseText = o.conn.responseText;
13993        obj.responseXML = o.conn.responseXML;
13994
13995        if(callbackArg){
13996            obj.argument = callbackArg;
13997        }
13998
13999        return obj;
14000    },
14001
14002  /**
14003   * @description If a transaction cannot be completed due to dropped or closed connections,
14004   * there may be not be enough information to build a full response object.
14005   * The failure callback will be fired and this specific condition can be identified
14006   * by a status property value of 0.
14007   *
14008   * If an abort was successful, the status property will report a value of -1.
14009   *
14010   * @method createExceptionObject
14011   * @private
14012   * @static
14013   * @param {int} tId The Transaction Id
14014   * @param {callbackArg} callbackArg The user-defined argument or arguments to be passed to the callback
14015   * @param {boolean} isAbort Determines if the exception case is caused by a transaction abort
14016   * @return {object}
14017   */
14018    createExceptionObject:function(tId, callbackArg, isAbort)
14019    {
14020        var COMM_CODE = 0,
14021            COMM_ERROR = 'communication failure',
14022            ABORT_CODE = -1,
14023            ABORT_ERROR = 'transaction aborted',
14024            obj = {};
14025
14026        obj.tId = tId;
14027        if(isAbort){
14028            obj.status = ABORT_CODE;
14029            obj.statusText = ABORT_ERROR;
14030        }
14031        else{
14032            obj.status = COMM_CODE;
14033            obj.statusText = COMM_ERROR;
14034        }
14035
14036        if(callbackArg){
14037            obj.argument = callbackArg;
14038        }
14039
14040        return obj;
14041    },
14042
14043  /**
14044   * @description Method that initializes the custom HTTP headers for the each transaction.
14045   * @method initHeader
14046   * @public
14047   * @static
14048   * @param {string} label The HTTP header label
14049   * @param {string} value The HTTP header value
14050   * @param {string} isDefault Determines if the specific header is a default header
14051   * automatically sent with each transaction.
14052   * @return {void}
14053   */
14054    initHeader:function(label, value, isDefault)
14055    {
14056        var headerObj = (isDefault)?this._default_headers:this._http_headers;
14057
14058        headerObj[label] = value;
14059        if(isDefault){
14060            this._has_default_headers = true;
14061        }
14062        else{
14063            this._has_http_headers = true;
14064        }
14065    },
14066
14067
14068  /**
14069   * @description Accessor that sets the HTTP headers for each transaction.
14070   * @method setHeader
14071   * @private
14072   * @static
14073   * @param {object} o The connection object for the transaction.
14074   * @return {void}
14075   */
14076    setHeader:function(o)
14077    {
14078        var prop;
14079        if(this._has_default_headers){
14080            for(prop in this._default_headers){
14081                if(YAHOO.lang.hasOwnProperty(this._default_headers, prop)){
14082                    o.conn.setRequestHeader(prop, this._default_headers[prop]);
14083                    YAHOO.log('Default HTTP header ' + prop + ' set with value of ' + this._default_headers[prop], 'info', 'Connection');
14084                }
14085            }
14086        }
14087
14088        if(this._has_http_headers){
14089            for(prop in this._http_headers){
14090                if(YAHOO.lang.hasOwnProperty(this._http_headers, prop)){
14091                    o.conn.setRequestHeader(prop, this._http_headers[prop]);
14092                    YAHOO.log('HTTP header ' + prop + ' set with value of ' + this._http_headers[prop], 'info', 'Connection');
14093                }
14094            }
14095
14096            this._http_headers = {};
14097            this._has_http_headers = false;
14098        }
14099    },
14100
14101  /**
14102   * @description Resets the default HTTP headers object
14103   * @method resetDefaultHeaders
14104   * @public
14105   * @static
14106   * @return {void}
14107   */
14108    resetDefaultHeaders:function(){
14109        this._default_headers = {};
14110        this._has_default_headers = false;
14111    },
14112
14113  /**
14114   * @description Method to terminate a transaction, if it has not reached readyState 4.
14115   * @method abort
14116   * @public
14117   * @static
14118   * @param {object} o The connection object returned by asyncRequest.
14119   * @param {object} callback  User-defined callback object.
14120   * @param {string} isTimeout boolean to indicate if abort resulted from a callback timeout.
14121   * @return {boolean}
14122   */
14123    abort:function(o, callback, isTimeout)
14124    {
14125        var abortStatus,
14126            args = (callback && callback.argument)?callback.argument:null;
14127            o = o || {};
14128
14129        if(o.conn){
14130            if(o.xhr){
14131                if(this.isCallInProgress(o)){
14132                    // Issue abort request
14133                    o.conn.abort();
14134
14135                    window.clearInterval(this._poll[o.tId]);
14136                    delete this._poll[o.tId];
14137
14138                    if(isTimeout){
14139                        window.clearTimeout(this._timeOut[o.tId]);
14140                        delete this._timeOut[o.tId];
14141                    }
14142
14143                    abortStatus = true;
14144                }
14145            }
14146            else if(o.xdr){
14147                o.conn.abort(o.tId);
14148                abortStatus = true;
14149            }
14150        }
14151        else if(o.upload){
14152            var frameId = 'yuiIO' + o.tId;
14153            var io = document.getElementById(frameId);
14154
14155            if(io){
14156                // Remove all listeners on the iframe prior to
14157                // its destruction.
14158                YAHOO.util.Event.removeListener(io, "load");
14159                // Destroy the iframe facilitating the transaction.
14160                document.body.removeChild(io);
14161                YAHOO.log('File upload iframe destroyed. Id is:' + frameId, 'info', 'Connection');
14162
14163                if(isTimeout){
14164                    window.clearTimeout(this._timeOut[o.tId]);
14165                    delete this._timeOut[o.tId];
14166                }
14167
14168                abortStatus = true;
14169            }
14170        }
14171        else{
14172            abortStatus = false;
14173        }
14174
14175        if(abortStatus === true){
14176            // Fire global custom event -- abortEvent
14177            this.abortEvent.fire(o, args);
14178
14179            if(o.abortEvent){
14180                // Fire transaction custom event -- abortEvent
14181                o.abortEvent.fire(o, args);
14182            }
14183
14184            this.handleTransactionResponse(o, callback, true);
14185            YAHOO.log('Transaction ' + o.tId + ' aborted.', 'info', 'Connection');
14186        }
14187
14188        return abortStatus;
14189    },
14190
14191  /**
14192   * @description Determines if the transaction is still being processed.
14193   * @method isCallInProgress
14194   * @public
14195   * @static
14196   * @param {object} o The connection object returned by asyncRequest
14197   * @return {boolean}
14198   */
14199    isCallInProgress:function(o)
14200    {
14201        o = o || {};
14202        // if the XHR object assigned to the transaction has not been dereferenced,
14203        // then check its readyState status.  Otherwise, return false.
14204        if(o.xhr && o.conn){
14205            return o.conn.readyState !== 4 && o.conn.readyState !== 0;
14206        }
14207        else if(o.xdr && o.conn){
14208            return o.conn.isCallInProgress(o.tId);
14209        }
14210        else if(o.upload === true){
14211            return document.getElementById('yuiIO' + o.tId)?true:false;
14212        }
14213        else{
14214            return false;
14215        }
14216    },
14217
14218  /**
14219   * @description Dereference the XHR instance and the connection object after the transaction is completed.
14220   * @method releaseObject
14221   * @private
14222   * @static
14223   * @param {object} o The connection object
14224   * @return {void}
14225   */
14226    releaseObject:function(o)
14227    {
14228        if(o && o.conn){
14229            //dereference the XHR instance.
14230            o.conn = null;
14231
14232            YAHOO.log('Connection object for transaction ' + o.tId + ' destroyed.', 'info', 'Connection');
14233
14234            //dereference the connection object.
14235            o = null;
14236        }
14237    }
14238};
14239
14240/**
14241  * @for YAHOO.util.Connect
14242  */
14243(function() {
14244	var YCM = YAHOO.util.Connect, _fn = {};
14245
14246   /**
14247    * @description This method creates and instantiates the Flash transport.
14248    * @method _swf
14249    * @private
14250    * @static
14251    * @param {string} URI to connection.swf.
14252    * @return {void}
14253    */
14254	function _swf(uri) {
14255		var o = '<object id="YUIConnectionSwf" type="application/x-shockwave-flash" data="' +
14256				uri + '" width="0" height="0">' +
14257				'<param name="movie" value="' + uri + '">' +
14258				'<param name="allowScriptAccess" value="always">' +
14259				'</object>',
14260		    c = document.createElement('div');
14261
14262		document.body.appendChild(c);
14263		c.innerHTML = o;
14264	}
14265
14266   /**
14267    * @description This method calls the public method on the
14268    * Flash transport to start the XDR transaction.  It is analogous
14269    * to Connection Manager's asyncRequest method.
14270    * @method xdr
14271    * @private
14272    * @static
14273    * @param {object} The transaction object.
14274    * @param {string} HTTP request method.
14275    * @param {string} URI for the transaction.
14276    * @param {object} The transaction's callback object.
14277    * @param {object} The JSON object used as HTTP POST data.
14278    * @return {void}
14279    */
14280	function _xdr(o, m, u, c, d) {
14281		_fn[parseInt(o.tId)] = { 'o':o, 'c':c };
14282		if (d) {
14283			c.method = m;
14284			c.data = d;
14285		}
14286
14287		o.conn.send(u, c, o.tId);
14288	}
14289
14290   /**
14291    * @description This method instantiates the Flash transport and
14292    * establishes a static reference to it, used for all XDR requests.
14293    * @method transport
14294    * @public
14295    * @static
14296    * @param {string} URI to connection.swf.
14297    * @return {void}
14298    */
14299	function _init(uri) {
14300		_swf(uri);
14301		YCM._transport = document.getElementById('YUIConnectionSwf');
14302	}
14303
14304	function _xdrReady() {
14305		YCM.xdrReadyEvent.fire();
14306	}
14307
14308   /**
14309    * @description This method fires the global and transaction start
14310    * events.
14311    * @method _xdrStart
14312    * @private
14313    * @static
14314    * @param {object} The transaction object.
14315    * @param {string} The transaction's callback object.
14316    * @return {void}
14317    */
14318	function _xdrStart(o, cb) {
14319		if (o) {
14320			// Fire global custom event -- startEvent
14321			YCM.startEvent.fire(o, cb.argument);
14322
14323			if(o.startEvent){
14324				// Fire transaction custom event -- startEvent
14325				o.startEvent.fire(o, cb.argument);
14326			}
14327		}
14328	}
14329
14330   /**
14331    * @description This method is the initial response handler
14332    * for XDR transactions.  The Flash transport calls this
14333    * function and sends the response payload.
14334    * @method handleXdrResponse
14335    * @private
14336    * @static
14337    * @param {object} The response object sent from the Flash transport.
14338    * @return {void}
14339    */
14340	function _handleXdrResponse(r) {
14341		var o = _fn[r.tId].o,
14342			cb = _fn[r.tId].c;
14343
14344		if (r.statusText === 'xdr:start') {
14345			_xdrStart(o, cb);
14346			return;
14347		}
14348
14349		r.responseText = decodeURI(r.responseText);
14350		o.r = r;
14351		if (cb.argument) {
14352			o.r.argument = cb.argument;
14353		}
14354
14355		this.handleTransactionResponse(o, cb, r.statusText === 'xdr:abort' ? true : false);
14356		delete _fn[r.tId];
14357	}
14358
14359	// Bind the functions to Connection Manager as static fields.
14360	YCM.xdr = _xdr;
14361	YCM.swf = _swf;
14362	YCM.transport = _init;
14363	YCM.xdrReadyEvent = new YAHOO.util.CustomEvent('xdrReady');
14364	YCM.xdrReady = _xdrReady;
14365	YCM.handleXdrResponse = _handleXdrResponse;
14366})();
14367
14368/**
14369  * @for YAHOO.util.Connect
14370  */
14371(function(){
14372	var YCM = YAHOO.util.Connect,
14373		YE = YAHOO.util.Event,
14374		dM = document.documentMode ? document.documentMode : false;
14375
14376   /**
14377	* @description Property modified by setForm() to determine if a file(s)
14378	* upload is expected.
14379	* @property _isFileUpload
14380	* @private
14381	* @static
14382	* @type boolean
14383	*/
14384	YCM._isFileUpload = false;
14385
14386   /**
14387	* @description Property modified by setForm() to set a reference to the HTML
14388	* form node if the desired action is file upload.
14389	* @property _formNode
14390	* @private
14391	* @static
14392	* @type object
14393	*/
14394	YCM._formNode = null;
14395
14396   /**
14397	* @description Property modified by setForm() to set the HTML form data
14398	* for each transaction.
14399	* @property _sFormData
14400	* @private
14401	* @static
14402	* @type string
14403	*/
14404	YCM._sFormData = null;
14405
14406   /**
14407	* @description Tracks the name-value pair of the "clicked" submit button if multiple submit
14408	* buttons are present in an HTML form; and, if YAHOO.util.Event is available.
14409	* @property _submitElementValue
14410	* @private
14411	* @static
14412	* @type string
14413	*/
14414	YCM._submitElementValue = null;
14415
14416   /**
14417    * @description Custom event that fires when handleTransactionResponse() determines a
14418    * response in the HTTP 4xx/5xx range.
14419    * @property failureEvent
14420    * @private
14421    * @static
14422    * @type CustomEvent
14423    */
14424	YCM.uploadEvent = new YAHOO.util.CustomEvent('upload');
14425
14426   /**
14427	* @description Determines whether YAHOO.util.Event is available and returns true or false.
14428	* If true, an event listener is bound at the document level to trap click events that
14429	* resolve to a target type of "Submit".  This listener will enable setForm() to determine
14430	* the clicked "Submit" value in a multi-Submit button, HTML form.
14431	* @property _hasSubmitListener
14432	* @private
14433	* @static
14434	*/
14435	YCM._hasSubmitListener = function() {
14436		if(YE){
14437			YE.addListener(
14438				document,
14439				'click',
14440				function(e){
14441					var obj = YE.getTarget(e),
14442						name = obj.nodeName.toLowerCase();
14443
14444					if((name === 'input' || name === 'button') && (obj.type && obj.type.toLowerCase() == 'submit')){
14445						YCM._submitElementValue = encodeURIComponent(obj.name) + "=" + encodeURIComponent(obj.value);
14446					}
14447				});
14448			return true;
14449		}
14450		return false;
14451	}();
14452
14453  /**
14454   * @description This method assembles the form label and value pairs and
14455   * constructs an encoded string.
14456   * asyncRequest() will automatically initialize the transaction with a
14457   * a HTTP header Content-Type of application/x-www-form-urlencoded.
14458   * @method setForm
14459   * @public
14460   * @static
14461   * @param {string || object} form id or name attribute, or form object.
14462   * @param {boolean} optional enable file upload.
14463   * @param {boolean} optional enable file upload over SSL in IE only.
14464   * @return {string} string of the HTML form field name and value pairs..
14465   */
14466	function _setForm(formId, isUpload, secureUri)
14467	{
14468		var oForm, oElement, oName, oValue, oDisabled,
14469			hasSubmit = false,
14470			data = [], item = 0,
14471			i,len,j,jlen,opt;
14472
14473		this.resetFormState();
14474
14475		if(typeof formId == 'string'){
14476			// Determine if the argument is a form id or a form name.
14477			// Note form name usage is deprecated by supported
14478			// here for legacy reasons.
14479			oForm = (document.getElementById(formId) || document.forms[formId]);
14480		}
14481		else if(typeof formId == 'object'){
14482			// Treat argument as an HTML form object.
14483			oForm = formId;
14484		}
14485		else{
14486			YAHOO.log('Unable to create form object ' + formId, 'warn', 'Connection');
14487			return;
14488		}
14489
14490		// If the isUpload argument is true, setForm will call createFrame to initialize
14491		// an iframe as the form target.
14492		//
14493		// The argument secureURI is also required by IE in SSL environments
14494		// where the secureURI string is a fully qualified HTTP path, used to set the source
14495		// of the iframe, to a stub resource in the same domain.
14496		if(isUpload){
14497
14498			// Create iframe in preparation for file upload.
14499			this.createFrame(secureUri?secureUri:null);
14500
14501			// Set form reference and file upload properties to true.
14502			this._isFormSubmit = true;
14503			this._isFileUpload = true;
14504			this._formNode = oForm;
14505
14506			return;
14507		}
14508
14509		// Iterate over the form elements collection to construct the
14510		// label-value pairs.
14511		for (i=0,len=oForm.elements.length; i<len; ++i){
14512			oElement  = oForm.elements[i];
14513			oDisabled = oElement.disabled;
14514			oName     = oElement.name;
14515
14516			// Do not submit fields that are disabled or
14517			// do not have a name attribute value.
14518			if(!oDisabled && oName)
14519			{
14520				oName  = encodeURIComponent(oName)+'=';
14521				oValue = encodeURIComponent(oElement.value);
14522
14523				switch(oElement.type)
14524				{
14525					// Safari, Opera, FF all default opt.value from .text if
14526					// value attribute not specified in markup
14527					case 'select-one':
14528						if (oElement.selectedIndex > -1) {
14529							opt = oElement.options[oElement.selectedIndex];
14530							data[item++] = oName + encodeURIComponent(
14531								(opt.attributes.value && opt.attributes.value.specified) ? opt.value : opt.text);
14532						}
14533						break;
14534					case 'select-multiple':
14535						if (oElement.selectedIndex > -1) {
14536							for(j=oElement.selectedIndex, jlen=oElement.options.length; j<jlen; ++j){
14537								opt = oElement.options[j];
14538								if (opt.selected) {
14539									data[item++] = oName + encodeURIComponent(
14540										(opt.attributes.value && opt.attributes.value.specified) ? opt.value : opt.text);
14541								}
14542							}
14543						}
14544						break;
14545					case 'radio':
14546					case 'checkbox':
14547						if(oElement.checked){
14548							data[item++] = oName + oValue;
14549						}
14550						break;
14551					case 'file':
14552						// stub case as XMLHttpRequest will only send the file path as a string.
14553					case undefined:
14554						// stub case for fieldset element which returns undefined.
14555					case 'reset':
14556						// stub case for input type reset button.
14557					case 'button':
14558						// stub case for input type button elements.
14559						break;
14560					case 'submit':
14561						if(hasSubmit === false){
14562							if(this._hasSubmitListener && this._submitElementValue){
14563								data[item++] = this._submitElementValue;
14564							}
14565							hasSubmit = true;
14566						}
14567						break;
14568					default:
14569						data[item++] = oName + oValue;
14570				}
14571			}
14572		}
14573
14574		this._isFormSubmit = true;
14575		this._sFormData = data.join('&');
14576
14577		YAHOO.log('Form initialized for transaction. HTML form POST message is: ' + this._sFormData, 'info', 'Connection');
14578
14579		this.initHeader('Content-Type', this._default_form_header);
14580		YAHOO.log('Initialize header Content-Type to application/x-www-form-urlencoded for setForm() transaction.', 'info', 'Connection');
14581
14582		return this._sFormData;
14583	}
14584
14585   /**
14586    * @description Resets HTML form properties when an HTML form or HTML form
14587    * with file upload transaction is sent.
14588    * @method resetFormState
14589    * @private
14590    * @static
14591    * @return {void}
14592    */
14593	function _resetFormState(){
14594		this._isFormSubmit = false;
14595		this._isFileUpload = false;
14596		this._formNode = null;
14597		this._sFormData = "";
14598	}
14599
14600
14601   /**
14602    * @description Creates an iframe to be used for form file uploads.  It is remove from the
14603    * document upon completion of the upload transaction.
14604    * @method createFrame
14605    * @private
14606    * @static
14607    * @param {string} optional qualified path of iframe resource for SSL in IE.
14608    * @return {void}
14609    */
14610	function _createFrame(secureUri){
14611
14612		// IE does not allow the setting of id and name attributes as object
14613		// properties via createElement().  A different iframe creation
14614		// pattern is required for IE.
14615		var frameId = 'yuiIO' + this._transaction_id,
14616			ie9 = (dM === 9) ? true : false,
14617			io;
14618
14619		if(YAHOO.env.ua.ie && !ie9){
14620			io = document.createElement('<iframe id="' + frameId + '" name="' + frameId + '" />');
14621
14622			// IE will throw a security exception in an SSL environment if the
14623			// iframe source is undefined.
14624			if(typeof secureUri == 'boolean'){
14625				io.src = 'javascript:false';
14626			}
14627		}
14628		else{
14629			io = document.createElement('iframe');
14630			io.id = frameId;
14631			io.name = frameId;
14632		}
14633
14634		io.style.position = 'absolute';
14635		io.style.top = '-1000px';
14636		io.style.left = '-1000px';
14637
14638		document.body.appendChild(io);
14639		YAHOO.log('File upload iframe created. Id is:' + frameId, 'info', 'Connection');
14640	}
14641
14642   /**
14643    * @description Parses the POST data and creates hidden form elements
14644    * for each key-value, and appends them to the HTML form object.
14645    * @method appendPostData
14646    * @private
14647    * @static
14648    * @param {string} postData The HTTP POST data
14649    * @return {array} formElements Collection of hidden fields.
14650    */
14651	function _appendPostData(postData){
14652		var formElements = [],
14653			postMessage = postData.split('&'),
14654			i, delimitPos;
14655
14656		for(i=0; i < postMessage.length; i++){
14657			delimitPos = postMessage[i].indexOf('=');
14658			if(delimitPos != -1){
14659				formElements[i] = document.createElement('input');
14660				formElements[i].type = 'hidden';
14661				formElements[i].name = decodeURIComponent(postMessage[i].substring(0,delimitPos));
14662				formElements[i].value = decodeURIComponent(postMessage[i].substring(delimitPos+1));
14663				this._formNode.appendChild(formElements[i]);
14664			}
14665		}
14666
14667		return formElements;
14668	}
14669
14670   /**
14671    * @description Uploads HTML form, inclusive of files/attachments, using the
14672    * iframe created in createFrame to facilitate the transaction.
14673    * @method uploadFile
14674    * @private
14675    * @static
14676    * @param {int} id The transaction id.
14677    * @param {object} callback User-defined callback object.
14678    * @param {string} uri Fully qualified path of resource.
14679    * @param {string} postData POST data to be submitted in addition to HTML form.
14680    * @return {void}
14681    */
14682	function _uploadFile(o, callback, uri, postData){
14683		// Each iframe has an id prefix of "yuiIO" followed
14684		// by the unique transaction id.
14685		var frameId = 'yuiIO' + o.tId,
14686		    uploadEncoding = 'multipart/form-data',
14687		    io = document.getElementById(frameId),
14688		    ie8 = (dM >= 8) ? true : false,
14689		    oConn = this,
14690			args = (callback && callback.argument)?callback.argument:null,
14691            oElements,i,prop,obj, rawFormAttributes, uploadCallback;
14692
14693		// Track original HTML form attribute values.
14694		rawFormAttributes = {
14695			action:this._formNode.getAttribute('action'),
14696			method:this._formNode.getAttribute('method'),
14697			target:this._formNode.getAttribute('target')
14698		};
14699
14700		// Initialize the HTML form properties in case they are
14701		// not defined in the HTML form.
14702		this._formNode.setAttribute('action', uri);
14703		this._formNode.setAttribute('method', 'POST');
14704		this._formNode.setAttribute('target', frameId);
14705
14706		if(YAHOO.env.ua.ie && !ie8){
14707			// IE does not respect property enctype for HTML forms.
14708			// Instead it uses the property - "encoding".
14709			this._formNode.setAttribute('encoding', uploadEncoding);
14710		}
14711		else{
14712			this._formNode.setAttribute('enctype', uploadEncoding);
14713		}
14714
14715		if(postData){
14716			oElements = this.appendPostData(postData);
14717		}
14718
14719		// Start file upload.
14720		this._formNode.submit();
14721
14722		// Fire global custom event -- startEvent
14723		this.startEvent.fire(o, args);
14724
14725		if(o.startEvent){
14726			// Fire transaction custom event -- startEvent
14727			o.startEvent.fire(o, args);
14728		}
14729
14730		// Start polling if a callback is present and the timeout
14731		// property has been defined.
14732		if(callback && callback.timeout){
14733			this._timeOut[o.tId] = window.setTimeout(function(){ oConn.abort(o, callback, true); }, callback.timeout);
14734		}
14735
14736		// Remove HTML elements created by appendPostData
14737		if(oElements && oElements.length > 0){
14738			for(i=0; i < oElements.length; i++){
14739				this._formNode.removeChild(oElements[i]);
14740			}
14741		}
14742
14743		// Restore HTML form attributes to their original
14744		// values prior to file upload.
14745		for(prop in rawFormAttributes){
14746			if(YAHOO.lang.hasOwnProperty(rawFormAttributes, prop)){
14747				if(rawFormAttributes[prop]){
14748					this._formNode.setAttribute(prop, rawFormAttributes[prop]);
14749				}
14750				else{
14751					this._formNode.removeAttribute(prop);
14752				}
14753			}
14754		}
14755
14756		// Reset HTML form state properties.
14757		this.resetFormState();
14758
14759		// Create the upload callback handler that fires when the iframe
14760		// receives the load event.  Subsequently, the event handler is detached
14761		// and the iframe removed from the document.
14762		uploadCallback = function() {
14763			var body, pre, text;
14764
14765			if(callback && callback.timeout){
14766				window.clearTimeout(oConn._timeOut[o.tId]);
14767				delete oConn._timeOut[o.tId];
14768			}
14769
14770			// Fire global custom event -- completeEvent
14771			oConn.completeEvent.fire(o, args);
14772
14773			if(o.completeEvent){
14774				// Fire transaction custom event -- completeEvent
14775				o.completeEvent.fire(o, args);
14776			}
14777
14778			obj = {
14779			    tId : o.tId,
14780			    argument : args
14781            };
14782
14783			try
14784			{
14785				body = io.contentWindow.document.getElementsByTagName('body')[0];
14786				pre = io.contentWindow.document.getElementsByTagName('pre')[0];
14787
14788				if (body) {
14789					if (pre) {
14790						text = pre.textContent?pre.textContent:pre.innerText;
14791					}
14792					else {
14793						text = body.textContent?body.textContent:body.innerText;
14794					}
14795				}
14796				obj.responseText = text;
14797				// responseText and responseXML will be populated with the same data from the iframe.
14798				// Since the HTTP headers cannot be read from the iframe
14799				obj.responseXML = io.contentWindow.document.XMLDocument?io.contentWindow.document.XMLDocument:io.contentWindow.document;
14800			}
14801			catch(e){}
14802
14803			if(callback && callback.upload){
14804				if(!callback.scope){
14805					callback.upload(obj);
14806					YAHOO.log('Upload callback.', 'info', 'Connection');
14807				}
14808				else{
14809					callback.upload.apply(callback.scope, [obj]);
14810					YAHOO.log('Upload callback with scope.', 'info', 'Connection');
14811				}
14812			}
14813
14814			// Fire global custom event -- uploadEvent
14815			oConn.uploadEvent.fire(obj);
14816
14817			if(o.uploadEvent){
14818				// Fire transaction custom event -- uploadEvent
14819				o.uploadEvent.fire(obj);
14820			}
14821
14822			YE.removeListener(io, "load", uploadCallback);
14823
14824			setTimeout(
14825				function(){
14826					document.body.removeChild(io);
14827					oConn.releaseObject(o);
14828					YAHOO.log('File upload iframe destroyed. Id is:' + frameId, 'info', 'Connection');
14829				}, 100);
14830		};
14831
14832		// Bind the onload handler to the iframe to detect the file upload response.
14833		YE.addListener(io, "load", uploadCallback);
14834	}
14835
14836	YCM.setForm = _setForm;
14837	YCM.resetFormState = _resetFormState;
14838	YCM.createFrame = _createFrame;
14839	YCM.appendPostData = _appendPostData;
14840	YCM.uploadFile = _uploadFile;
14841})();
14842
14843YAHOO.register("connection", YAHOO.util.Connect, {version: "2.9.0", build: "2800"});
14844/*
14845Copyright (c) 2011, Yahoo! Inc. All rights reserved.
14846Code licensed under the BSD License:
14847http://developer.yahoo.com/yui/license.html
14848version: 2.9.0
14849*/
14850/**
14851 * Provides Attribute configurations.
14852 * @namespace YAHOO.util
14853 * @class Attribute
14854 * @constructor
14855 * @param hash {Object} The intial Attribute.
14856 * @param {YAHOO.util.AttributeProvider} The owner of the Attribute instance.
14857 */
14858
14859YAHOO.util.Attribute = function(hash, owner) {
14860    if (owner) { 
14861        this.owner = owner;
14862        this.configure(hash, true);
14863    }
14864};
14865
14866YAHOO.util.Attribute.INVALID_VALUE = {};
14867
14868YAHOO.util.Attribute.prototype = {
14869    /**
14870     * The name of the attribute.
14871     * @property name
14872     * @type String
14873     */
14874    name: undefined,
14875    
14876    /**
14877     * The value of the attribute.
14878     * @property value
14879     * @type String
14880     */
14881    value: null,
14882    
14883    /**
14884     * The owner of the attribute.
14885     * @property owner
14886     * @type YAHOO.util.AttributeProvider
14887     */
14888    owner: null,
14889    
14890    /**
14891     * Whether or not the attribute is read only.
14892     * @property readOnly
14893     * @type Boolean
14894     */
14895    readOnly: false,
14896    
14897    /**
14898     * Whether or not the attribute can only be written once.
14899     * @property writeOnce
14900     * @type Boolean
14901     */
14902    writeOnce: false,
14903
14904    /**
14905     * The attribute's initial configuration.
14906     * @private
14907     * @property _initialConfig
14908     * @type Object
14909     */
14910    _initialConfig: null,
14911    
14912    /**
14913     * Whether or not the attribute's value has been set.
14914     * @private
14915     * @property _written
14916     * @type Boolean
14917     */
14918    _written: false,
14919    
14920    /**
14921     * A function to call when setting the attribute's value.
14922     * The method receives the new value as the first arg and the attribute name as the 2nd
14923     * @property method
14924     * @type Function
14925     */
14926    method: null,
14927    
14928    /**
14929     * The function to use when setting the attribute's value.
14930     * The setter receives the new value as the first arg and the attribute name as the 2nd
14931     * The return value of the setter replaces the value passed to set(). 
14932     * @property setter
14933     * @type Function
14934     */
14935    setter: null,
14936    
14937    /**
14938     * The function to use when getting the attribute's value.
14939     * The getter receives the new value as the first arg and the attribute name as the 2nd
14940     * The return value of the getter will be used as the return from get().
14941     * @property getter
14942     * @type Function
14943     */
14944    getter: null,
14945
14946    /**
14947     * The validator to use when setting the attribute's value.
14948     * @property validator
14949     * @type Function
14950     * @return Boolean
14951     */
14952    validator: null,
14953    
14954    /**
14955     * Retrieves the current value of the attribute.
14956     * @method getValue
14957     * @return {any} The current value of the attribute.
14958     */
14959    getValue: function() {
14960        var val = this.value;
14961
14962        if (this.getter) {
14963            val = this.getter.call(this.owner, this.name, val);
14964        }
14965
14966        return val;
14967    },
14968    
14969    /**
14970     * Sets the value of the attribute and fires beforeChange and change events.
14971     * @method setValue
14972     * @param {Any} value The value to apply to the attribute.
14973     * @param {Boolean} silent If true the change events will not be fired.
14974     * @return {Boolean} Whether or not the value was set.
14975     */
14976    setValue: function(value, silent) {
14977        var beforeRetVal,
14978            owner = this.owner,
14979            name = this.name,
14980            invalidValue = YAHOO.util.Attribute.INVALID_VALUE,
14981        
14982            event = {
14983                type: name, 
14984                prevValue: this.getValue(),
14985                newValue: value
14986        };
14987        
14988        if (this.readOnly || ( this.writeOnce && this._written) ) {
14989            YAHOO.log( 'setValue ' + name + ', ' +  value +
14990                    ' failed: read only', 'error', 'Attribute');
14991            return false; // write not allowed
14992        }
14993        
14994        if (this.validator && !this.validator.call(owner, value) ) {
14995            YAHOO.log( 'setValue ' + name + ', ' + value +
14996                    ' validation failed', 'error', 'Attribute');
14997            return false; // invalid value
14998        }
14999
15000        if (!silent) {
15001            beforeRetVal = owner.fireBeforeChangeEvent(event);
15002            if (beforeRetVal === false) {
15003                YAHOO.log('setValue ' + name + 
15004                        ' cancelled by beforeChange event', 'info', 'Attribute');
15005                return false;
15006            }
15007        }
15008
15009        if (this.setter) {
15010            value = this.setter.call(owner, value, this.name);
15011            if (value === undefined) {
15012                YAHOO.log('setter for ' + this.name + ' returned undefined', 'warn', 'Attribute');
15013            }
15014
15015            if (value === invalidValue) {
15016                return false;
15017            }
15018        }
15019        
15020        if (this.method) {
15021            if (this.method.call(owner, value, this.name) === invalidValue) {
15022                return false; 
15023            }
15024        }
15025        
15026        this.value = value; // TODO: set before calling setter/method?
15027        this._written = true;
15028        
15029        event.type = name;
15030        
15031        if (!silent) {
15032            this.owner.fireChangeEvent(event);
15033        }
15034        
15035        return true;
15036    },
15037    
15038    /**
15039     * Allows for configuring the Attribute's properties.
15040     * @method configure
15041     * @param {Object} map A key-value map of Attribute properties.
15042     * @param {Boolean} init Whether or not this should become the initial config.
15043     */
15044    configure: function(map, init) {
15045        map = map || {};
15046
15047        if (init) {
15048            this._written = false; // reset writeOnce
15049        }
15050
15051        this._initialConfig = this._initialConfig || {};
15052        
15053        for (var key in map) {
15054            if ( map.hasOwnProperty(key) ) {
15055                this[key] = map[key];
15056                if (init) {
15057                    this._initialConfig[key] = map[key];
15058                }
15059            }
15060        }
15061    },
15062    
15063    /**
15064     * Resets the value to the initial config value.
15065     * @method resetValue
15066     * @return {Boolean} Whether or not the value was set.
15067     */
15068    resetValue: function() {
15069        return this.setValue(this._initialConfig.value);
15070    },
15071    
15072    /**
15073     * Resets the attribute config to the initial config state.
15074     * @method resetConfig
15075     */
15076    resetConfig: function() {
15077        this.configure(this._initialConfig, true);
15078    },
15079    
15080    /**
15081     * Resets the value to the current value.
15082     * Useful when values may have gotten out of sync with actual properties.
15083     * @method refresh
15084     * @return {Boolean} Whether or not the value was set.
15085     */
15086    refresh: function(silent) {
15087        this.setValue(this.value, silent);
15088    }
15089};
15090
15091(function() {
15092    var Lang = YAHOO.util.Lang;
15093
15094    /*
15095    Copyright (c) 2006, Yahoo! Inc. All rights reserved.
15096    Code licensed under the BSD License:
15097    http://developer.yahoo.net/yui/license.txt
15098    */
15099    
15100    /**
15101     * Provides and manages YAHOO.util.Attribute instances
15102     * @namespace YAHOO.util
15103     * @class AttributeProvider
15104     * @uses YAHOO.util.EventProvider
15105     */
15106    YAHOO.util.AttributeProvider = function() {};
15107
15108    YAHOO.util.AttributeProvider.prototype = {
15109        
15110        /**
15111         * A key-value map of Attribute configurations
15112         * @property _configs
15113         * @protected (may be used by subclasses and augmentors)
15114         * @private
15115         * @type {Object}
15116         */
15117        _configs: null,
15118        /**
15119         * Returns the current value of the attribute.
15120         * @method get
15121         * @param {String} key The attribute whose value will be returned.
15122         * @return {Any} The current value of the attribute.
15123         */
15124        get: function(key){
15125            this._configs = this._configs || {};
15126            var config = this._configs[key];
15127            
15128            if (!config || !this._configs.hasOwnProperty(key)) {
15129                YAHOO.log(key + ' not found', 'error', 'AttributeProvider');
15130                return null;
15131            }
15132            
15133            return config.getValue();
15134        },
15135        
15136        /**
15137         * Sets the value of a config.
15138         * @method set
15139         * @param {String} key The name of the attribute
15140         * @param {Any} value The value to apply to the attribute
15141         * @param {Boolean} silent Whether or not to suppress change events
15142         * @return {Boolean} Whether or not the value was set.
15143         */
15144        set: function(key, value, silent){
15145            this._configs = this._configs || {};
15146            var config = this._configs[key];
15147            
15148            if (!config) {
15149                YAHOO.log('set failed: ' + key + ' not found',
15150                        'error', 'AttributeProvider');
15151                return false;
15152            }
15153            
15154            return config.setValue(value, silent);
15155        },
15156    
15157        /**
15158         * Returns an array of attribute names.
15159         * @method getAttributeKeys
15160         * @return {Array} An array of attribute names.
15161         */
15162        getAttributeKeys: function(){
15163            this._configs = this._configs;
15164            var keys = [], key;
15165
15166            for (key in this._configs) {
15167                if ( Lang.hasOwnProperty(this._configs, key) && 
15168                        !Lang.isUndefined(this._configs[key]) ) {
15169                    keys[keys.length] = key;
15170                }
15171            }
15172            
15173            return keys;
15174        },
15175        
15176        /**
15177         * Sets multiple attribute values.
15178         * @method setAttributes
15179         * @param {Object} map  A key-value map of attributes
15180         * @param {Boolean} silent Whether or not to suppress change events
15181         */
15182        setAttributes: function(map, silent){
15183            for (var key in map) {
15184                if ( Lang.hasOwnProperty(map, key) ) {
15185                    this.set(key, map[key], silent);
15186                }
15187            }
15188        },
15189    
15190        /**
15191         * Resets the specified attribute's value to its initial value.
15192         * @method resetValue
15193         * @param {String} key The name of the attribute
15194         * @param {Boolean} silent Whether or not to suppress change events
15195         * @return {Boolean} Whether or not the value was set
15196         */
15197        resetValue: function(key, silent){
15198            this._configs = this._configs || {};
15199            if (this._configs[key]) {
15200                this.set(key, this._configs[key]._initialConfig.value, silent);
15201                return true;
15202            }
15203            return false;
15204        },
15205    
15206        /**
15207         * Sets the attribute's value to its current value.
15208         * @method refresh
15209         * @param {String | Array} key The attribute(s) to refresh
15210         * @param {Boolean} silent Whether or not to suppress change events
15211         */
15212        refresh: function(key, silent) {
15213            this._configs = this._configs || {};
15214            var configs = this._configs;
15215            
15216            key = ( ( Lang.isString(key) ) ? [key] : key ) || 
15217                    this.getAttributeKeys();
15218            
15219            for (var i = 0, len = key.length; i < len; ++i) { 
15220                if (configs.hasOwnProperty(key[i])) {
15221                    this._configs[key[i]].refresh(silent);
15222                }
15223            }
15224        },
15225    
15226        /**
15227         * Adds an Attribute to the AttributeProvider instance. 
15228         * @method register
15229         * @param {String} key The attribute's name
15230         * @param {Object} map A key-value map containing the
15231         * attribute's properties.
15232         * @deprecated Use setAttributeConfig
15233         */
15234        register: function(key, map) {
15235            this.setAttributeConfig(key, map);
15236        },
15237        
15238        
15239        /**
15240         * Returns the attribute's properties.
15241         * @method getAttributeConfig
15242         * @param {String} key The attribute's name
15243         * @private
15244         * @return {object} A key-value map containing all of the
15245         * attribute's properties.
15246         */
15247        getAttributeConfig: function(key) {
15248            this._configs = this._configs || {};
15249            var config = this._configs[key] || {};
15250            var map = {}; // returning a copy to prevent overrides
15251            
15252            for (key in config) {
15253                if ( Lang.hasOwnProperty(config, key) ) {
15254                    map[key] = config[key];
15255                }
15256            }
15257    
15258            return map;
15259        },
15260        
15261        /**
15262         * Sets or updates an Attribute instance's properties. 
15263         * @method setAttributeConfig
15264         * @param {String} key The attribute's name.
15265         * @param {Object} map A key-value map of attribute properties
15266         * @param {Boolean} init Whether or not this should become the intial config.
15267         */
15268        setAttributeConfig: function(key, map, init) {
15269            this._configs = this._configs || {};
15270            map = map || {};
15271            if (!this._configs[key]) {
15272                map.name = key;
15273                this._configs[key] = this.createAttribute(map);
15274            } else {
15275                this._configs[key].configure(map, init);
15276            }
15277        },
15278        
15279        /**
15280         * Sets or updates an Attribute instance's properties. 
15281         * @method configureAttribute
15282         * @param {String} key The attribute's name.
15283         * @param {Object} map A key-value map of attribute properties
15284         * @param {Boolean} init Whether or not this should become the intial config.
15285         * @deprecated Use setAttributeConfig
15286         */
15287        configureAttribute: function(key, map, init) {
15288            this.setAttributeConfig(key, map, init);
15289        },
15290        
15291        /**
15292         * Resets an attribute to its intial configuration. 
15293         * @method resetAttributeConfig
15294         * @param {String} key The attribute's name.
15295         * @private
15296         */
15297        resetAttributeConfig: function(key){
15298            this._configs = this._configs || {};
15299            this._configs[key].resetConfig();
15300        },
15301        
15302        // wrapper for EventProvider.subscribe
15303        // to create events on the fly
15304        subscribe: function(type, callback) {
15305            this._events = this._events || {};
15306
15307            if ( !(type in this._events) ) {
15308                this._events[type] = this.createEvent(type);
15309            }
15310
15311            YAHOO.util.EventProvider.prototype.subscribe.apply(this, arguments);
15312        },
15313
15314        on: function() {
15315            this.subscribe.apply(this, arguments);
15316        },
15317
15318        addListener: function() {
15319            this.subscribe.apply(this, arguments);
15320        },
15321
15322        /**
15323         * Fires the attribute's beforeChange event. 
15324         * @method fireBeforeChangeEvent
15325         * @param {String} key The attribute's name.
15326         * @param {Obj} e The event object to pass to handlers.
15327         */
15328        fireBeforeChangeEvent: function(e) {
15329            var type = 'before';
15330            type += e.type.charAt(0).toUpperCase() + e.type.substr(1) + 'Change';
15331            e.type = type;
15332            return this.fireEvent(e.type, e);
15333        },
15334        
15335        /**
15336         * Fires the attribute's change event. 
15337         * @method fireChangeEvent
15338         * @param {String} key The attribute's name.
15339         * @param {Obj} e The event object to pass to the handlers.
15340         */
15341        fireChangeEvent: function(e) {
15342            e.type += 'Change';
15343            return this.fireEvent(e.type, e);
15344        },
15345
15346        createAttribute: function(map) {
15347            return new YAHOO.util.Attribute(map, this);
15348        }
15349    };
15350    
15351    YAHOO.augment(YAHOO.util.AttributeProvider, YAHOO.util.EventProvider);
15352})();
15353
15354(function() {
15355// internal shorthand
15356var Dom = YAHOO.util.Dom,
15357    AttributeProvider = YAHOO.util.AttributeProvider,
15358	specialTypes = {
15359		mouseenter: true,
15360		mouseleave: true
15361	};
15362
15363/**
15364 * Element provides an wrapper object to simplify adding
15365 * event listeners, using dom methods, and managing attributes. 
15366 * @module element
15367 * @namespace YAHOO.util
15368 * @requires yahoo, dom, event
15369 */
15370
15371/**
15372 * Element provides an wrapper object to simplify adding
15373 * event listeners, using dom methods, and managing attributes. 
15374 * @class Element
15375 * @uses YAHOO.util.AttributeProvider
15376 * @constructor
15377 * @param el {HTMLElement | String} The html element that 
15378 * represents the Element.
15379 * @param {Object} map A key-value map of initial config names and values
15380 */
15381var Element = function(el, map) {
15382    this.init.apply(this, arguments);
15383};
15384
15385Element.DOM_EVENTS = {
15386    'click': true,
15387    'dblclick': true,
15388    'keydown': true,
15389    'keypress': true,
15390    'keyup': true,
15391    'mousedown': true,
15392    'mousemove': true,
15393    'mouseout': true, 
15394    'mouseover': true, 
15395    'mouseup': true,
15396    'mouseenter': true, 
15397    'mouseleave': true,
15398    'focus': true,
15399    'blur': true,
15400    'submit': true,
15401    'change': true
15402};
15403
15404Element.prototype = {
15405    /**
15406     * Dom events supported by the Element instance.
15407     * @property DOM_EVENTS
15408     * @type Object
15409     */
15410    DOM_EVENTS: null,
15411
15412    DEFAULT_HTML_SETTER: function(value, key) {
15413        var el = this.get('element');
15414        
15415        if (el) {
15416            el[key] = value;
15417        }
15418
15419		return value;
15420
15421    },
15422
15423    DEFAULT_HTML_GETTER: function(key) {
15424        var el = this.get('element'),
15425            val;
15426
15427        if (el) {
15428            val = el[key];
15429        }
15430
15431        return val;
15432    },
15433
15434    /**
15435     * Wrapper for HTMLElement method.
15436     * @method appendChild
15437     * @param {YAHOO.util.Element || HTMLElement} child The element to append. 
15438     * @return {HTMLElement} The appended DOM element. 
15439     */
15440    appendChild: function(child) {
15441        child = child.get ? child.get('element') : child;
15442        return this.get('element').appendChild(child);
15443    },
15444    
15445    /**
15446     * Wrapper for HTMLElement method.
15447     * @method getElementsByTagName
15448     * @param {String} tag The tagName to collect
15449     * @return {HTMLCollection} A collection of DOM elements. 
15450     */
15451    getElementsByTagName: function(tag) {
15452        return this.get('element').getElementsByTagName(tag);
15453    },
15454    
15455    /**
15456     * Wrapper for HTMLElement method.
15457     * @method hasChildNodes
15458     * @return {Boolean} Whether or not the element has childNodes
15459     */
15460    hasChildNodes: function() {
15461        return this.get('element').hasChildNodes();
15462    },
15463    
15464    /**
15465     * Wrapper for HTMLElement method.
15466     * @method insertBefore
15467     * @param {HTMLElement} element The HTMLElement to insert
15468     * @param {HTMLElement} before The HTMLElement to insert
15469     * the element before.
15470     * @return {HTMLElement} The inserted DOM element. 
15471     */
15472    insertBefore: function(element, before) {
15473        element = element.get ? element.get('element') : element;
15474        before = (before && before.get) ? before.get('element') : before;
15475        
15476        return this.get('element').insertBefore(element, before);
15477    },
15478    
15479    /**
15480     * Wrapper for HTMLElement method.
15481     * @method removeChild
15482     * @param {HTMLElement} child The HTMLElement to remove
15483     * @return {HTMLElement} The removed DOM element. 
15484     */
15485    removeChild: function(child) {
15486        child = child.get ? child.get('element') : child;
15487        return this.get('element').removeChild(child);
15488    },
15489    
15490    /**
15491     * Wrapper for HTMLElement method.
15492     * @method replaceChild
15493     * @param {HTMLElement} newNode The HTMLElement to insert
15494     * @param {HTMLElement} oldNode The HTMLElement to replace
15495     * @return {HTMLElement} The replaced DOM element. 
15496     */
15497    replaceChild: function(newNode, oldNode) {
15498        newNode = newNode.get ? newNode.get('element') : newNode;
15499        oldNode = oldNode.get ? oldNode.get('element') : oldNode;
15500        return this.get('element').replaceChild(newNode, oldNode);
15501    },
15502
15503    
15504    /**
15505     * Registers Element specific attributes.
15506     * @method initAttributes
15507     * @param {Object} map A key-value map of initial attribute configs
15508     */
15509    initAttributes: function(map) {
15510    },
15511
15512    /**
15513     * Adds a listener for the given event.  These may be DOM or 
15514     * customEvent listeners.  Any event that is fired via fireEvent
15515     * can be listened for.  All handlers receive an event object. 
15516     * @method addListener
15517     * @param {String} type The name of the event to listen for
15518     * @param {Function} fn The handler to call when the event fires
15519     * @param {Any} obj A variable to pass to the handler
15520     * @param {Object} scope The object to use for the scope of the handler 
15521     */
15522    addListener: function(type, fn, obj, scope) {
15523
15524        scope = scope || this;
15525
15526        var Event = YAHOO.util.Event,
15527			el = this.get('element') || this.get('id'),
15528        	self = this;
15529
15530
15531		if (specialTypes[type] && !Event._createMouseDelegate) {
15532	        YAHOO.log("Using a " + type + " event requires the event-mouseenter module", "error", "Event");
15533	        return false;	
15534		}
15535
15536
15537        if (!this._events[type]) { // create on the fly
15538
15539            if (el && this.DOM_EVENTS[type]) {
15540				Event.on(el, type, function(e, matchedEl) {
15541
15542					// Supplement IE with target, currentTarget relatedTarget
15543
15544	                if (e.srcElement && !e.target) { 
15545	                    e.target = e.srcElement;
15546	                }
15547
15548					if ((e.toElement && !e.relatedTarget) || (e.fromElement && !e.relatedTarget)) {
15549						e.relatedTarget = Event.getRelatedTarget(e);
15550					}
15551					
15552					if (!e.currentTarget) {
15553						e.currentTarget = el;
15554					}
15555
15556					//	Note: matchedEl el is passed back for delegated listeners
15557		            self.fireEvent(type, e, matchedEl);
15558
15559		        }, obj, scope);
15560            }
15561            this.createEvent(type, {scope: this});
15562        }
15563        
15564        return YAHOO.util.EventProvider.prototype.subscribe.apply(this, arguments); // notify via customEvent
15565    },
15566
15567
15568    /**
15569     * Alias for addListener
15570     * @method on
15571     * @param {String} type The name of the event to listen for
15572     * @param {Function} fn The function call when the event fires
15573     * @param {Any} obj A variable to pass to the handler
15574     * @param {Object} scope The object to use for the scope of the handler 
15575     */
15576    on: function() {
15577        return this.addListener.apply(this, arguments);
15578    },
15579    
15580    /**
15581     * Alias for addListener
15582     * @method subscribe
15583     * @param {String} type The name of the event to listen for
15584     * @param {Function} fn The function call when the event fires
15585     * @param {Any} obj A variable to pass to the handler
15586     * @param {Object} scope The object to use for the scope of the handler 
15587     */
15588    subscribe: function() {
15589        return this.addListener.apply(this, arguments);
15590    },
15591    
15592    /**
15593     * Remove an event listener
15594     * @method removeListener
15595     * @param {String} type The name of the event to listen for
15596     * @param {Function} fn The function call when the event fires
15597     */
15598    removeListener: function(type, fn) {
15599        return this.unsubscribe.apply(this, arguments);
15600    },
15601    
15602    /**
15603     * Wrapper for Dom method.
15604     * @method addClass
15605     * @param {String} className The className to add
15606     */
15607    addClass: function(className) {
15608        Dom.addClass(this.get('element'), className);
15609    },
15610    
15611    /**
15612     * Wrapper for Dom method.
15613     * @method getElementsByClassName
15614     * @param {String} className The className to collect
15615     * @param {String} tag (optional) The tag to use in
15616     * conjunction with class name
15617     * @return {Array} Array of HTMLElements
15618     */
15619    getElementsByClassName: function(className, tag) {
15620        return Dom.getElementsByClassName(className, tag,
15621                this.get('element') );
15622    },
15623    
15624    /**
15625     * Wrapper for Dom method.
15626     * @method hasClass
15627     * @param {String} className The className to add
15628     * @return {Boolean} Whether or not the element has the class name
15629     */
15630    hasClass: function(className) {
15631        return Dom.hasClass(this.get('element'), className); 
15632    },
15633    
15634    /**
15635     * Wrapper for Dom method.
15636     * @method removeClass
15637     * @param {String} className The className to remove
15638     */
15639    removeClass: function(className) {
15640        return Dom.removeClass(this.get('element'), className);
15641    },
15642    
15643    /**
15644     * Wrapper for Dom method.
15645     * @method replaceClass
15646     * @param {String} oldClassName The className to replace
15647     * @param {String} newClassName The className to add
15648     */
15649    replaceClass: function(oldClassName, newClassName) {
15650        return Dom.replaceClass(this.get('element'), 
15651                oldClassName, newClassName);
15652    },
15653    
15654    /**
15655     * Wrapper for Dom method.
15656     * @method setStyle
15657     * @param {String} property The style property to set
15658     * @param {String} value The value to apply to the style property
15659     */
15660    setStyle: function(property, value) {
15661        return Dom.setStyle(this.get('element'),  property, value); // TODO: always queuing?
15662    },
15663    
15664    /**
15665     * Wrapper for Dom method.
15666     * @method getStyle
15667     * @param {String} property The style property to retrieve
15668     * @return {String} The current value of the property
15669     */
15670    getStyle: function(property) {
15671        return Dom.getStyle(this.get('element'),  property);
15672    },
15673    
15674    /**
15675     * Apply any queued set calls.
15676     * @method fireQueue
15677     */
15678    fireQueue: function() {
15679        var queue = this._queue;
15680        for (var i = 0, len = queue.length; i < len; ++i) {
15681            this[queue[i][0]].apply(this, queue[i][1]);
15682        }
15683    },
15684    
15685    /**
15686     * Appends the HTMLElement into either the supplied parentNode.
15687     * @method appendTo
15688     * @param {HTMLElement | Element} parentNode The node to append to
15689     * @param {HTMLElement | Element} before An optional node to insert before
15690     * @return {HTMLElement} The appended DOM element. 
15691     */
15692    appendTo: function(parent, before) {
15693        parent = (parent.get) ?  parent.get('element') : Dom.get(parent);
15694        
15695        this.fireEvent('beforeAppendTo', {
15696            type: 'beforeAppendTo',
15697            target: parent
15698        });
15699        
15700        
15701        before = (before && before.get) ? 
15702                before.get('element') : Dom.get(before);
15703        var element = this.get('element');
15704        
15705        if (!element) {
15706            YAHOO.log('appendTo failed: element not available',
15707                    'error', 'Element');
15708            return false;
15709        }
15710        
15711        if (!parent) {
15712            YAHOO.log('appendTo failed: parent not available',
15713                    'error', 'Element');
15714            return false;
15715        }
15716        
15717        if (element.parent != parent) {
15718            if (before) {
15719                parent.insertBefore(element, before);
15720            } else {
15721                parent.appendChild(element);
15722            }
15723        }
15724        
15725        YAHOO.log(element + 'appended to ' + parent);
15726        
15727        this.fireEvent('appendTo', {
15728            type: 'appendTo',
15729            target: parent
15730        });
15731
15732        return element;
15733    },
15734    
15735    get: function(key) {
15736        var configs = this._configs || {},
15737            el = configs.element; // avoid loop due to 'element'
15738
15739        if (el && !configs[key] && !YAHOO.lang.isUndefined(el.value[key]) ) {
15740            this._setHTMLAttrConfig(key);
15741        }
15742
15743        return AttributeProvider.prototype.get.call(this, key);
15744    },
15745
15746    setAttributes: function(map, silent) {
15747        // set based on configOrder
15748        var done = {},
15749            configOrder = this._configOrder;
15750
15751        // set based on configOrder
15752        for (var i = 0, len = configOrder.length; i < len; ++i) {
15753            if (map[configOrder[i]] !== undefined) {
15754                done[configOrder[i]] = true;
15755                this.set(configOrder[i], map[configOrder[i]], silent);
15756            }
15757        }
15758
15759        // unconfigured (e.g. Dom attributes)
15760        for (var att in map) {
15761            if (map.hasOwnProperty(att) && !done[att]) {
15762                this.set(att, map[att], silent);
15763            }
15764        }
15765    },
15766
15767    set: function(key, value, silent) {
15768        var el = this.get('element');
15769        if (!el) {
15770            this._queue[this._queue.length] = ['set', arguments];
15771            if (this._configs[key]) {
15772                this._configs[key].value = value; // so "get" works while queueing
15773            
15774            }
15775            return;
15776        }
15777        
15778        // set it on the element if not configured and is an HTML attribute
15779        if ( !this._configs[key] && !YAHOO.lang.isUndefined(el[key]) ) {
15780            this._setHTMLAttrConfig(key);
15781        }
15782
15783        return AttributeProvider.prototype.set.apply(this, arguments);
15784    },
15785    
15786    setAttributeConfig: function(key, map, init) {
15787        this._configOrder.push(key);
15788        AttributeProvider.prototype.setAttributeConfig.apply(this, arguments);
15789    },
15790
15791    createEvent: function(type, config) {
15792        this._events[type] = true;
15793        return AttributeProvider.prototype.createEvent.apply(this, arguments);
15794    },
15795    
15796    init: function(el, attr) {
15797        this._initElement(el, attr); 
15798    },
15799
15800    destroy: function() {
15801        var el = this.get('element');
15802        YAHOO.util.Event.purgeElement(el, true); // purge DOM listeners recursively
15803        this.unsubscribeAll(); // unsubscribe all custom events
15804
15805        if (el && el.parentNode) {
15806            el.parentNode.removeChild(el); // pull from the DOM
15807        }
15808
15809        // revert initial configs
15810        this._queue = [];
15811        this._events = {};
15812        this._configs = {};
15813        this._configOrder = []; 
15814    },
15815
15816    _initElement: function(el, attr) {
15817        this._queue = this._queue || [];
15818        this._events = this._events || {};
15819        this._configs = this._configs || {};
15820        this._configOrder = []; 
15821        attr = attr || {};
15822        attr.element = attr.element || el || null;
15823
15824        var isReady = false;  // to determine when to init HTMLElement and content
15825
15826        var DOM_EVENTS = Element.DOM_EVENTS;
15827        this.DOM_EVENTS = this.DOM_EVENTS || {};
15828
15829        for (var event in DOM_EVENTS) {
15830            if (DOM_EVENTS.hasOwnProperty(event)) {
15831                this.DOM_EVENTS[event] = DOM_EVENTS[event];
15832            }
15833        }
15834
15835        if (typeof attr.element === 'string') { // register ID for get() access
15836            this._setHTMLAttrConfig('id', { value: attr.element });
15837        }
15838
15839        if (Dom.get(attr.element)) {
15840            isReady = true;
15841            this._initHTMLElement(attr);
15842            this._initContent(attr);
15843        }
15844
15845        YAHOO.util.Event.onAvailable(attr.element, function() {
15846            if (!isReady) { // otherwise already done
15847                this._initHTMLElement(attr);
15848            }
15849
15850            this.fireEvent('available', { type: 'available', target: Dom.get(attr.element) });  
15851        }, this, true);
15852        
15853        YAHOO.util.Event.onContentReady(attr.element, function() {
15854            if (!isReady) { // otherwise already done
15855                this._initContent(attr);
15856            }
15857            this.fireEvent('contentReady', { type: 'contentReady', target: Dom.get(attr.element) });  
15858        }, this, true);
15859    },
15860
15861    _initHTMLElement: function(attr) {
15862        /**
15863         * The HTMLElement the Element instance refers to.
15864         * @attribute element
15865         * @type HTMLElement
15866         */
15867        this.setAttributeConfig('element', {
15868            value: Dom.get(attr.element),
15869            readOnly: true
15870         });
15871    },
15872
15873    _initContent: function(attr) {
15874        this.initAttributes(attr);
15875        this.setAttributes(attr, true);
15876        this.fireQueue();
15877
15878    },
15879
15880    /**
15881     * Sets the value of the property and fires beforeChange and change events.
15882     * @private
15883     * @method _setHTMLAttrConfig
15884     * @param {YAHOO.util.Element} element The Element instance to
15885     * register the config to.
15886     * @param {String} key The name of the config to register
15887     * @param {Object} map A key-value map of the config's params
15888     */
15889    _setHTMLAttrConfig: function(key, map) {
15890        var el = this.get('element');
15891        map = map || {};
15892        map.name = key;
15893
15894        map.setter = map.setter || this.DEFAULT_HTML_SETTER;
15895        map.getter = map.getter || this.DEFAULT_HTML_GETTER;
15896
15897        map.value = map.value || el[key];
15898        this._configs[key] = new YAHOO.util.Attribute(map, this);
15899    }
15900};
15901
15902/**
15903 * Fires when the Element's HTMLElement can be retrieved by Id.
15904 * <p>See: <a href="#addListener">Element.addListener</a></p>
15905 * <p><strong>Event fields:</strong><br>
15906 * <code>&lt;
15906String&gt; type</code> available<br>
15907 * <code>&lt;HTMLElement&gt;
15908 * target</code> the HTMLElement bound to this Element instance<br>
15909 * <p><strong>Usage:</strong><br>
15910 * <code>var handler = function(e) {var target = e.target};<br>
15911 * myTabs.addListener('available', handler);</code></p>
15912 * @event available
15913 */
15914 
15915/**
15916 * Fires when the Element's HTMLElement subtree is rendered.
15917 * <p>See: <a href="#addListener">Element.addListener</a></p>
15918 * <p><strong>Event fields:</strong><br>
15919 * <code>&lt;String&gt; type</code> contentReady<br>
15920 * <code>&lt;HTMLElement&gt;
15921 * target</code> the HTMLElement bound to this Element instance<br>
15922 * <p><strong>Usage:</strong><br>
15923 * <code>var handler = function(e) {var target = e.target};<br>
15924 * myTabs.addListener('contentReady', handler);</code></p>
15925 * @event contentReady
15926 */
15927
15928/**
15929 * Fires before the Element is appended to another Element.
15930 * <p>See: <a href="#addListener">Element.addListener</a></p>
15931 * <p><strong>Event fields:</strong><br>
15932 * <code>&lt;String&gt; type</code> beforeAppendTo<br>
15933 * <code>&lt;HTMLElement/Element&gt;
15934 * target</code> the HTMLElement/Element being appended to 
15935 * <p><strong>Usage:</strong><br>
15936 * <code>var handler = function(e) {var target = e.target};<br>
15937 * myTabs.addListener('beforeAppendTo', handler);</code></p>
15938 * @event beforeAppendTo
15939 */
15940
15941/**
15942 * Fires after the Element is appended to another Element.
15943 * <p>See: <a href="#addListener">Element.addListener</a></p>
15944 * <p><strong>Event fields:</strong><br>
15945 * <code>&lt;String&gt; type</code> appendTo<br>
15946 * <code>&lt;HTMLElement/Element&gt;
15947 * target</code> the HTMLElement/Element being appended to 
15948 * <p><strong>Usage:</strong><br>
15949 * <code>var handler = function(e) {var target = e.target};<br>
15950 * myTabs.addListener('appendTo', handler);</code></p>
15951 * @event appendTo
15952 */
15953
15954YAHOO.augment(Element, AttributeProvider);
15955YAHOO.util.Element = Element;
15956})();
15957
15958YAHOO.register("element", YAHOO.util.Element, {version: "2.9.0", build: "2800"});
15959/*
15960Copyright (c) 2011, Yahoo! Inc. All rights reserved.
15961Code licensed under the BSD License:
15962http://developer.yahoo.com/yui/license.html
15963version: 2.9.0
15964*/
15965/**
15966 * Provides a mechanism to fetch remote resources and
15967 * insert them into a document
15968 * This utility can fetch JavaScript and CSS files, inserting script
15969 * tags for script and link tags for CSS.  Note, this
15970 * is done via the normal browser mechanisms for inserting
15971 * these resources and making the content available to
15972 * code that would access it.  Be careful when retreiving
15973 * remote resources.  Only use this utility to fetch
15974 * files from sites you trust.
15975 *
15976 * @module get
15977 * @requires yahoo
15978 */
15979
15980/**
15981 * Fetches and inserts one or more script or link nodes into the document.
15982 * This utility can fetch JavaScript and CSS files, inserting script
15983 * tags for script and link tags for CSS.  Note, this
15984 * is done via the normal browser mechanisms for inserting
15985 * these resources and making the content available to
15986 * code that would access it.  Be careful when retreiving
15987 * remote resources.  Only use this utility to fetch
15988 * files from sites you trust.
15989 *
15990 * @namespace YAHOO.util
15991 * @class YAHOO.util.Get
15992 */
15993YAHOO.util.Get = function() {
15994
15995    /**
15996     * hash of queues to manage multiple requests
15997     * @property queues
15998     * @private
15999     */
16000    var queues={},
16001
16002    /**
16003     * queue index used to generate transaction ids
16004     * @property qidx
16005     * @type int
16006     * @private
16007     */
16008        qidx=0,
16009
16010    /**
16011     * node index used to generate unique node ids
16012     * @property nidx
16013     * @type int
16014     * @private
16015     */
16016        nidx=0,
16017
16018    /**
16019     * interal property used to prevent multiple simultaneous purge
16020     * processes
16021     * @property purging
16022     * @type boolean
16023     * @private
16024     */
16025        _purging=false,
16026
16027        ua=YAHOO.env.ua,
16028
16029        lang=YAHOO.lang,
16030
16031    _fail,
16032    _purge,
16033    _track,
16034
16035    /**
16036     * Generates an HTML element, this is not appended to a document
16037     * @method _node
16038     * @param type {string} the type of element
16039     * @param attr {string} the attributes
16040     * @param win {Window} optional window to create the element in
16041     * @return {HTMLElement} the generated node
16042     * @private
16043     */
16044    _node = function(type, attr, win) {
16045        var w = win || window, d=w.document, n=d.createElement(type), i;
16046
16047        for (i in attr) {
16048            if (attr.hasOwnProperty(i)) {
16049                n.setAttribute(i, attr[i]);
16050            }
16051        }
16052
16053        return n;
16054    },
16055
16056    /**
16057     * Generates a link node
16058     * @method _linkNode
16059     * @param url {string} the url for the css file
16060     * @param win {Window} optional window to create the node in
16061     * @return {HTMLElement} the generated node
16062     * @private
16063     */
16064    _linkNode = function(url, win, attributes) {
16065
16066        var o = {
16067            id:   "yui__dyn_" + (nidx++),
16068            type: "text/css",
16069            rel:  "stylesheet",
16070            href: url
16071        };
16072
16073        if (attributes) {
16074            lang.augmentObject(o, attributes);
16075        }
16076
16077        return _node("link", o, win);
16078    },
16079
16080    /**
16081     * Generates a script node
16082     * @method _scriptNode
16083     * @param url {string} the url for the script file
16084     * @param win {Window} optional window to create the node in
16085     * @return {HTMLElement} the generated node
16086     * @private
16087     */
16088    _scriptNode = function(url, win, attributes) {
16089        var o = {
16090            id:   "yui__dyn_" + (nidx++),
16091            type: "text/javascript",
16092            src:  url
16093        };
16094
16095        if (attributes) {
16096            lang.augmentObject(o, attributes);
16097        }
16098
16099        return _node("script", o, win);
16100    },
16101
16102    /**
16103     * Returns the data payload for callback functions
16104     * @method _returnData
16105     * @private
16106     */
16107    _returnData = function(q, msg) {
16108        return {
16109                tId: q.tId,
16110                win: q.win,
16111                data: q.data,
16112                nodes: q.nodes,
16113                msg: msg,
16114                purge: function() {
16115                    _purge(this.tId);
16116                }
16117            };
16118    },
16119
16120    _get = function(nId, tId) {
16121        var q = queues[tId],
16122            n = (lang.isString(nId)) ? q.win.document.getElementById(nId) : nId;
16123        if (!n) {
16124            _fail(tId, "target node not found: " + nId);
16125        }
16126
16127        return n;
16128    },
16129
16130
16131    /**
16132     * The request is complete, so executing the requester's callback
16133     * @method _finish
16134     * @param id {string} the id of the request
16135     * @private
16136     */
16137    _finish = function(id) {
16138        YAHOO.log("Finishing transaction " + id);
16139        var q = queues[id], msg, context;
16140        q.finished = true;
16141
16142        if (q.aborted) {
16143            msg = "transaction " + id + " was aborted";
16144            _fail(id, msg);
16145            return;
16146        }
16147
16148        // execute success callback
16149        if (q.onSuccess) {
16150            context = q.scope || q.win;
16151            q.onSuccess.call(context, _returnData(q));
16152        }
16153    },
16154
16155    /**
16156     * Timeout detected
16157     * @method _timeout
16158     * @param id {string} the id of the request
16159     * @private
16160     */
16161    _timeout = function(id) {
16162        YAHOO.log("Timeout " + id, "info", "get");
16163        var q = queues[id], context;
16164        if (q.onTimeout) {
16165            context = q.scope || q;
16166            q.onTimeout.call(context, _returnData(q));
16167        }
16168    },
16169
16170    /**
16171     * Loads the next item for a given request
16172     * @method _next
16173     * @param id {string} the id of the request
16174     * @param loaded {string} the url that was just loaded, if any
16175     * @private
16176     */
16177    _next = function(id, loaded) {
16178
16179        YAHOO.log("_next: " + id + ", loaded: " + loaded, "info", "Get");
16180
16181        var q = queues[id], w=q.win, d=w.document, h=d.getElementsByTagName("head")[0],
16182            n, msg, url, s, extra;
16183
16184        if (q.timer) {
16185            // Y.log('cancel timer');
16186            q.timer.cancel();
16187        }
16188
16189        if (q.aborted) {
16190            msg = "transaction " + id + " was aborted";
16191            _fail(id, msg);
16192            return;
16193        }
16194
16195        if (loaded) {
16196            q.url.shift();
16197            if (q.varName) {
16198                q.varName.shift();
16199            }
16200        } else {
16201            // This is the first pass: make sure the url is an array
16202            q.url = (lang.isString(q.url)) ? [q.url] : q.url;
16203            if (q.varName) {
16204                q.varName = (lang.isString(q.varName)) ? [q.varName] : q.varName;
16205            }
16206        }
16207
16208
16209        if (q.url.length === 0) {
16210            // Safari 2.x workaround - There is no way to know when
16211            // a script is ready in versions of Safari prior to 3.x.
16212            // Adding an extra node reduces the problem, but doesn't
16213            // eliminate it completely because the browser executes
16214            // them asynchronously.
16215            if (q.type === "script" && ua.webkit && ua.webkit < 420 &&
16216                    !q.finalpass && !q.varName) {
16217                // Add another script node.  This does not guarantee that the
16218                // scripts will execute in order, but it does appear to fix the
16219                // problem on fast connections more effectively than using an
16220                // arbitrary timeout.  It is possible that the browser does
16221                // block subsequent script execution in this case for a limited
16222                // time.
16223                extra = _scriptNode(null, q.win, q.attributes);
16224                extra.innerHTML='YAHOO.util.Get._finalize("' + id + '");';
16225                q.nodes.push(extra); h.appendChild(extra);
16226
16227            } else {
16228                _finish(id);
16229            }
16230
16231            return;
16232        }
16233
16234
16235        url = q.url[0];
16236
16237        // if the url is undefined, this is probably a trailing comma problem in IE
16238        if (!url) {
16239            q.url.shift();
16240            YAHOO.log('skipping empty url');
16241            return _next(id);
16242        }
16243
16244        YAHOO.log("attempting to load " + url, "info", "Get");
16245
16246        if (q.timeout) {
16247            // Y.log('create timer');
16248            q.timer = lang.later(q.timeout, q, _timeout, id);
16249        }
16250
16251        if (q.type === "script") {
16252            n = _scriptNode(url, w, q.attributes);
16253        } else {
16254            n = _linkNode(url, w, q.attributes);
16255        }
16256
16257        // track this node's load progress
16258        _track(q.type, n, id, url, w, q.url.length);
16259
16260        // add the node to the queue so we can return it to the user supplied callback
16261        q.nodes.push(n);
16262
16263        // add it to the head or insert it before 'insertBefore'
16264        if (q.insertBefore) {
16265            s = _get(q.insertBefore, id);
16266            if (s) {
16267                s.parentNode.insertBefore(n, s);
16268            }
16269        } else {
16270            h.appendChild(n);
16271        }
16272
16273        YAHOO.log("Appending node: " + url, "info", "Get");
16274
16275        // FireFox does not support the onload event for link nodes, so there is
16276        // no way to make the css requests synchronous. This means that the css
16277        // rules in multiple files could be applied out of order in this browser
16278        // if a later request returns before an earlier one.  Safari too.
16279        if ((ua.webkit || ua.gecko) && q.type === "css") {
16280            _next(id, url);
16281        }
16282    },
16283
16284    /**
16285     * Removes processed queues and corresponding nodes
16286     * @method _autoPurge
16287     * @private
16288     */
16289    _autoPurge = function() {
16290
16291        if (_purging) {
16292            return;
16293        }
16294
16295        _purging = true;
16296
16297        var i, q;
16298
16299        for (i in queues) {
16300            if (queues.hasOwnProperty(i)) {
16301                q = queues[i];
16302                if (q.autopurge && q.finished) {
16303                    _purge(q.tId);
16304                    delete queues[i];
16305                }
16306            }
16307        }
16308
16309        _purging = false;
16310    },
16311
16312    /**
16313     * Saves the state for the request and begins loading
16314     * the requested urls
16315     * @method queue
16316     * @param type {string} the type of node to insert
16317     * @param url {string} the url to load
16318     * @param opts the hash of options for this request
16319     * @private
16320     */
16321    _queue = function(type, url, opts) {
16322
16323        var id = "q" + (qidx++), q;
16324        opts = opts || {};
16325
16326        if (qidx % YAHOO.util.Get.PURGE_THRESH === 0) {
16327            _autoPurge();
16328        }
16329
16330        queues[id] = lang.merge(opts, {
16331            tId: id,
16332            type: type,
16333            url: url,
16334            finished: false,
16335            aborted: false,
16336            nodes: []
16337        });
16338
16339        q = queues[id];
16340        q.win = q.win || window;
16341        q.scope = q.scope || q.win;
16342        q.autopurge = ("autopurge" in q) ? q.autopurge :
16343                      (type === "script") ? true : false;
16344
16345        q.attributes = q.attributes || {};
16346        q.attributes.charset = opts.charset || q.attributes.charset || 'utf-8';
16347
16348        lang.later(0, q, _next, id);
16349
16350        return {
16351            tId: id
16352        };
16353    };
16354
16355    /**
16356     * Detects when a node has been loaded.  In the case of
16357     * script nodes, this does not guarantee that contained
16358     * script is ready to use.
16359     * @method _track
16360     * @param type {string} the type of node to track
16361     * @param n {HTMLElement} the node to track
16362     * @param id {string} the id of the request
16363     * @param url {string} the url that is being loaded
16364     * @param win {Window} the targeted window
16365     * @param qlength the number of remaining items in the queue,
16366     * including this one
16367     * @param trackfn {Function} function to execute when finished
16368     * the default is _next
16369     * @private
16370     */
16371    _track = function(type, n, id, url, win, qlength, trackfn) {
16372        var f = trackfn || _next, rs, q, a, freq, w, l, i, msg;
16373
16374        // IE supports the readystatechange event for script and css nodes
16375        if (ua.ie && ua.ie < 11) {
16376            n.onreadystatechange = function() {
16377                rs = this.readyState;
16378                if ("loaded" === rs || "complete" === rs) {
16379                    YAHOO.log(id + " onload " + url, "info", "Get");
16380                    n.onreadystatechange = null;
16381                    f(id, url);
16382                }
16383            };
16384
16385        // webkit prior to 3.x is problemmatic
16386        } else if (ua.webkit) {
16387
16388            if (type === "script") {
16389
16390                // Safari 3.x supports the load event for script nodes (DOM2)
16391                if (ua.webkit >= 420) {
16392
16393                    n.addEventListener("load", function() {
16394                        YAHOO.log(id + " DOM2 onload " + url, "info", "Get");
16395                        f(id, url);
16396                    });
16397
16398                // Nothing can be done with Safari < 3.x except to pause and hope
16399                // for the best, particularly after last script is inserted. The
16400                // scripts will always execute in the order they arrive, not
16401                // necessarily the order in which they were inserted.  To support
16402                // script nodes with complete reliability in these browsers, script
16403                // nodes either need to invoke a function in the window once they
16404                // are loaded or the implementer needs to provide a well-known
16405                // property that the utility can poll for.
16406                } else {
16407                    // Poll for the existence of the named variable, if it
16408                    // was supplied.
16409                    q = queues[id];
16410                    if (q.varName) {
16411                        freq = YAHOO.util.Get.POLL_FREQ;
16412                        YAHOO.log("Polling for " + q.varName[0]);
16413                        q.maxattempts = YAHOO.util.Get.TIMEOUT/freq;
16414                        q.attempts = 0;
16415                        q._cache = q.varName[0].split(".");
16416                        q.timer = lang.later(freq, q, function(o) {
16417                            a = this._cache;
16418                            l = a.length;
16419                            w = this.win;
16420                            for (i=0; i<l; i=i+1) {
16421                                w = w[a[i]];
16422                                if (!w) {
16423                                    // if we have exausted our attempts, give up
16424                                    this.attempts++;
16425                                    if (this.attempts++ > this.maxattempts) {
16426                                        msg = "Over retry limit, giving up";
16427                                        q.timer.cancel();
16428                                        _fail(id, msg);
16429                                    } else {
16430                                        YAHOO.log(a[i] + " failed, retrying");
16431                                    }
16432                                    return;
16433                                }
16434                            }
16435
16436                            YAHOO.log("Safari poll complete");
16437
16438                            q.timer.cancel();
16439                            f(id, url);
16440
16441                        }, null, true);
16442                    } else {
16443                        lang.later(YAHOO.util.Get.POLL_FREQ, null, f, [id, url]);
16444                    }
16445                }
16446            }
16447
16448        // FireFox and Opera support onload (but not DOM2 in FF) handlers for
16449        // script nodes.  Opera, but not FF, supports the onload event for link
16450        // nodes.
16451        } else {
16452            n.onload = function() {
16453                YAHOO.log(id + " onload " + url, "info", "Get");
16454                f(id, url);
16455            };
16456        }
16457    };
16458
16459    /*
16460     * The request failed, execute fail handler with whatever
16461     * was accomplished.  There isn't a failure case at the
16462     * moment unless you count aborted transactions
16463     * @method _fail
16464     * @param id {string} the id of the request
16465     * @private
16466     */
16467    _fail = function(id, msg) {
16468        YAHOO.log("get failure: " + msg, "warn", "Get");
16469        var q = queues[id], context;
16470        // execute failure callback
16471        if (q.onFailure) {
16472            context = q.scope || q.win;
16473            q.onFailure.call(context, _returnData(q, msg));
16474        }
16475    };
16476
16477    /**
16478     * Removes the nodes for the specified queue
16479     * @method _purge
16480     * @private
16481     */
16482    _purge = function(tId) {
16483        if (queues[tId]) {
16484
16485            var q     = queues[tId],
16486                nodes = q.nodes,
16487                l     = nodes.length,
16488                d     = q.win.document,
16489                h     = d.getElementsByTagName("head")[0],
16490                sib, i, node, attr;
16491
16492            if (q.insertBefore) {
16493                sib = _get(q.insertBefore, tId);
16494                if (sib) {
16495                    h = sib.parentNode;
16496                }
16497            }
16498
16499            for (i=0; i<l; i=i+1) {
16500                node = nodes[i];
16501                if (node.clearAttributes) {
16502                    node.clearAttributes();
16503                } else {
16504                    for (attr in node) {
16505                        if (node.hasOwnProperty(attr)) {
16506                            delete node[attr];
16507                        }
16508                    }
16509                }
16510
16511                h.removeChild(node);
16512            }
16513
16514            q.nodes = [];
16515        }
16516    };
16517
16518
16519    return {
16520
16521        /**
16522         * The default poll freqency in ms, when needed
16523         * @property POLL_FREQ
16524         * @static
16525         * @type int
16526         * @default 10
16527         */
16528        POLL_FREQ: 10,
16529
16530        /**
16531         * The number of request required before an automatic purge.
16532         * property PURGE_THRESH
16533         * @static
16534         * @type int
16535         * @default 20
16536         */
16537        PURGE_THRESH: 20,
16538
16539        /**
16540         * The length time to poll for varName when loading a script in
16541         * Safari 2.x before the transaction fails.
16542         * property TIMEOUT
16543         * @static
16544         * @type int
16545         * @default 2000
16546         */
16547        TIMEOUT: 2000,
16548
16549        /**
16550         * Called by the the helper for detecting script load in Safari
16551         * @method _finalize
16552         * @param id {string} the transaction id
16553         * @private
16554         */
16555        _finalize: function(id) {
16556            YAHOO.log(id + " finalized ", "info", "Get");
16557            lang.later(0, null, _finish, id);
16558        },
16559
16560        /**
16561         * Abort a transaction
16562         * @method abort
16563         * @param {string|object} either the tId or the object returned from
16564         * script() or css()
16565         */
16566        abort: function(o) {
16567            var id = (lang.isString(o)) ? o : o.tId,
16568                q = queues[id];
16569            if (q) {
16570                YAHOO.log("Aborting " + id, "info", "Get");
16571                q.aborted = true;
16572            }
16573        },
16574
16575        /**
16576         * Fetches and inserts one or more script nodes into the head
16577         * of the current document or the document in a specified window.
16578         *
16579         * @method script
16580         * @static
16581         * @param url {string|string[]} the url or urls to the script(s)
16582         * @param opts {object} Options:
16583         * <dl>
16584         * <dt>onSuccess</dt>
16585         * <dd>
16586         * callback to execute when the script(s) are finished loading
16587         * The callback receives an object back with the following
16588         * data:
16589         * <dl>
16590         * <dt>win</dt>
16591         * <dd>the window the script(s) were inserted into</dd>
16592         * <dt>data</dt>
16593         * <dd>the data object passed in when the request was made</dd>
16594         * <dt>nodes</dt>
16595         * <dd>An array containing references to the nodes that were
16596         * inserted</dd>
16597         * <dt>purge</dt>
16598         * <dd>A function that, when executed, will remove the nodes
16599         * that were inserted</dd>
16600         * <dt>
16601         * </dl>
16602         * </dd>
16603         * <dt>onFailure</dt>
16604         * <dd>
16605         * callback to execute when the script load operation fails
16606         * The callback receives an object back with the following
16607         * data:
16608         * <dl>
16609         * <dt>win</dt>
16610         * <dd>the window the script(s) were inserted into</dd>
16611         * <dt>data</dt>
16612         * <dd>the data object passed in when the request was made</dd>
16613         * <dt>nodes</dt>
16614         * <dd>An array containing references to the nodes that were
16615         * inserted successfully</dd>
16616         * <dt>purge</dt>
16617         * <dd>A function that, when executed, will remove any nodes
16618         * that were inserted</dd>
16619         * <dt>
16620         * </dl>
16621         * </dd>
16622         * <dt>onTimeout</dt>
16623         * <dd>
16624         * callback to execute when a timeout occurs.
16625         * The callback receives an object back with the following
16626         * data:
16627         * <dl>
16628         * <dt>win</dt>
16629         * <dd>the window the script(s) were inserted into</dd>
16630         * <dt>data</dt>
16631         * <dd>the data object passed in when the request was made</dd>
16632         * <dt>nodes</dt>
16633         * <dd>An array containing references to the nodes that were
16634         * inserted</dd>
16635         * <dt>purge</dt>
16636         * <dd>A function that, when executed, will remove the nodes
16637         * that were inserted</dd>
16638         * <dt>
16639         * </dl>
16640         * </dd>
16641         * <dt>scope</dt>
16642         * <dd>the execution context for the callbacks</dd>
16643         * <dt>win</dt>
16644         * <dd>a window other than the one the utility occupies</dd>
16645         * <dt>autopurge</dt>
16646         * <dd>
16647         * setting to true will let the utilities cleanup routine purge
16648         * the script once loaded
16649         * </dd>
16650         * <dt>data</dt>
16651         * <dd>
16652         * data that is supplied to the callback when the script(s) are
16653         * loaded.
16654         * </dd>
16655         * <dt>varName</dt>
16656         * <dd>
16657         * variable that should be available when a script is finished
16658         * loading.  Used to help Safari 2.x and below with script load
16659         * detection.  The type of this property should match what was
16660         * passed into the url parameter: if loading a single url, a
16661         * string can be supplied.  If loading multiple scripts, you
16662         * must supply an array that contains the variable name for
16663         * each script.
16664         * </dd>
16665         * <dt>insertBefore</dt>
16666         * <dd>node or node id that will become the new node's nextSibling</dd>
16667         * </dl>
16668         * <dt>charset</dt>
16669         * <dd>Node charset, deprecated, use 'attributes'</dd>
16670         * <dt>attributes</dt>
16671         * <dd>A hash of attributes to apply to dynamic nodes.</dd>
16672         * <dt>timeout</dt>
16673         * <dd>Number of milliseconds to wait before aborting and firing the timeout event</dd>
16674         * <pre>
16675         * // assumes yahoo, dom, and event are already on the page
16676         * &nbsp;&nbsp;YAHOO.util.Get.script(
16677         * &nbsp;&nbsp;["http://yui.yahooapis.com/2.7.0/build/dragdrop/dragdrop-min.js",
16678         * &nbsp;&nbsp;&nbsp;"http://yui.yahooapis.com/2.7.0/build/animation/animation-min.js"], &#123;
16679         * &nbsp;&nbsp;&nbsp;&nbsp;onSuccess: function(o) &#123;
16680         * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;YAHOO.log(o.data); // foo
16681         * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new YAHOO.util.DDProxy("dd1"); // also new o.reference("dd1"); would work
16682         * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.log("won't cause error because YAHOO is the scope");
16683         * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.log(o.nodes.length === 2) // true
16684         * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// o.purge(); // optionally remove the script nodes immediately
16685         * &nbsp;&nbsp;&nbsp;&nbsp;&#125;,
16686         * &nbsp;&nbsp;&nbsp;&nbsp;onFailure: function(o) &#123;
16687         * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;YAHOO.log("transaction failed");
16688         * &nbsp;&nbsp;&nbsp;&nbsp;&#125;,
16689         * &nbsp;&nbsp;&nbsp;&nbsp;data: "foo",
16690         * &nbsp;&nbsp;&nbsp;&nbsp;timeout: 10000, // 10 second timeout
16691         * &nbsp;&nbsp;&nbsp;&nbsp;scope: YAHOO,
16692         * &nbsp;&nbsp;&nbsp;&nbsp;// win: otherframe // target another window/frame
16693         * &nbsp;&nbsp;&nbsp;&nbsp;autopurge: true // allow the utility to choose when to remove the nodes
16694         * &nbsp;&nbsp;&#125;);
16695         * </pre>
16696         * @return {tId: string}
16696 an object containing info about the transaction
16697         */
16698        script: function(url, opts) { return _queue("script", url, opts); },
16699
16700        /**
16701         * Fetches and inserts one or more css link nodes into the
16702         * head of the current document or the document in a specified
16703         * window.
16704         * @method css
16705         * @static
16706         * @param url {string} the url or urls to the css file(s)
16707         * @param opts Options:
16708         * <dl>
16709         * <dt>onSuccess</dt>
16710         * <dd>
16711         * callback to execute when the css file(s) are finished loading
16712         * The callback receives an object back with the following
16713         * data:
16714         * <dl>win</dl>
16715         * <dd>the window the link nodes(s) were inserted into</dd>
16716         * <dt>data</dt>
16717         * <dd>the data object passed in when the request was made</dd>
16718         * <dt>nodes</dt>
16719         * <dd>An array containing references to the nodes that were
16720         * inserted</dd>
16721         * <dt>purge</dt>
16722         * <dd>A function that, when executed, will remove the nodes
16723         * that were inserted</dd>
16724         * <dt>
16725         * </dl>
16726         * </dd>
16727         * <dt>scope</dt>
16728         * <dd>the execution context for the callbacks</dd>
16729         * <dt>win</dt>
16730         * <dd>a window other than the one the utility occupies</dd>
16731         * <dt>data</dt>
16732         * <dd>
16733         * data that is supplied to the callbacks when the nodes(s) are
16734         * loaded.
16735         * </dd>
16736         * <dt>insertBefore</dt>
16737         * <dd>node or node id that will become the new node's nextSibling</dd>
16738         * <dt>charset</dt>
16739         * <dd>Node charset, deprecated, use 'attributes'</dd>
16740         * <dt>attributes</dt>
16741         * <dd>A hash of attributes to apply to dynamic nodes.</dd>
16742         * </dl>
16743         * <pre>
16744         *      YAHOO.util.Get.css("http://yui.yahooapis.com/2.7.0/build/menu/assets/skins/sam/menu.css");
16745         * </pre>
16746         * <pre>
16747         *      YAHOO.util.Get.css(["http://yui.yahooapis.com/2.7.0/build/menu/assets/skins/sam/menu.css",
16748         *                          "http://yui.yahooapis.com/2.7.0/build/logger/assets/skins/sam/logger.css"]);
16749         * </pre>
16750         * @return {tId: string}
16750 an object containing info about the transaction
16751         */
16752        css: function(url, opts) {
16753            return _queue("css", url, opts);
16754        }
16755    };
16756}();
16757
16758YAHOO.register("get", YAHOO.util.Get, {version: "2.9.0", build: "2800"});
16759/*
16760Copyright (c) 2011, Yahoo! Inc. All rights reserved.
16761Code licensed under the BSD License:
16762http://developer.yahoo.com/yui/license.html
16763version: 2.9.0
16764*/
16765/**
16766 * The YAHOO object is the single global object used by YUI Library.  It
16767 * contains utility function for setting up namespaces, inheritance, and
16768 * logging.  YAHOO.util, YAHOO.widget, and YAHOO.example are namespaces
16769 * created automatically for and used by the library.
16770 * @module yahoo
16771 * @title  YAHOO Global
16772 */
16773
16774/**
16775 * YAHOO_config is not included as part of the library.  Instead it is an
16776 * object that can be defined by the implementer immediately before
16777 * including the YUI library.  The properties included in this object
16778 * will be used to configure global properties needed as soon as the
16779 * library begins to load.
16780 * @class YAHOO_config
16781 * @static
16782 */
16783
16784/**
16785 * A reference to a function that will be executed every time a YAHOO module
16786 * is loaded.  As parameter, this function will receive the version
16787 * information for the module. See <a href="YAHOO.env.html#getVersion">
16788 * YAHOO.env.getVersion</a> for the description of the version data structure.
16789 * @property listener
16790 * @type Function
16791 * @static
16792 * @default undefined
16793 */
16794
16795/**
16796 * Set to true if the library will be dynamically loaded after window.onload.
16797 * Defaults to false
16798 * @property injecting
16799 * @type boolean
16800 * @static
16801 * @default undefined
16802 */
16803
16804/**
16805 * Instructs the yuiloader component to dynamically load yui components and
16806 * their dependencies.  See the yuiloader documentation for more information
16807 * about dynamic loading
16808 * @property load
16809 * @static
16810 * @default undefined
16811 * @see yuiloader
16812 */
16813
16814/**
16815 * Forces the use of the supplied locale where applicable in the library
16816 * @property locale
16817 * @type string
16818 * @static
16819 * @default undefined
16820 */
16821
16822if (typeof YAHOO == "undefined" || !YAHOO) {
16823    /**
16824     * The YAHOO global namespace object.  If YAHOO is already defined, the
16825     * existing YAHOO object will not be overwritten so that defined
16826     * namespaces are preserved.
16827     * @class YAHOO
16828     * @static
16829     */
16830    var YAHOO = {};
16831}
16832
16833/**
16834 * Returns the namespace specified and creates it if it doesn't exist
16835 * <pre>
16836 * YAHOO.namespace("property.package");
16837 * YAHOO.namespace("YAHOO.property.package");
16838 * </pre>
16839 * Either of the above would create YAHOO.property, then
16840 * YAHOO.property.package
16841 *
16842 * Be careful when naming packages. Reserved words may work in some browsers
16843 * and not others. For instance, the following will fail in Safari:
16844 * <pre>
16845 * YAHOO.namespace("really.long.nested.namespace");
16846 * </pre>
16847 * This fails because "long" is a future reserved word in ECMAScript
16848 *
16849 * For implementation code that uses YUI, do not create your components
16850 * in the namespaces defined by YUI (
16851 * <code>YAHOO.util</code>,
16852 * <code>YAHOO.widget</code>,
16853 * <code>YAHOO.lang</code>,
16854 * <code>YAHOO.tool</code>,
16855 * <code>YAHOO.example</code>,
16856 * <code>YAHOO.env</code>) -- create your own namespace (e.g., 'companyname').
16857 *
16858 * @method namespace
16859 * @static
16860 * @param  {String*} arguments 1-n namespaces to create
16861 * @return {Object}  A reference to the last namespace object created
16862 */
16863YAHOO.namespace = function() {
16864    var a=arguments, o=null, i, j, d;
16865    for (i=0; i<a.length; i=i+1) {
16866        d=(""+a[i]).split(".");
16867        o=YAHOO;
16868
16869        // YAHOO is implied, so it is ignored if it is included
16870        for (j=(d[0] == "YAHOO") ? 1 : 0; j<d.length; j=j+1) {
16871            o[d[j]]=o[d[j]] || {};
16872            o=o[d[j]];
16873        }
16874    }
16875
16876    return o;
16877};
16878
16879/**
16880 * Uses YAHOO.widget.Logger to output a log message, if the widget is
16881 * available.
16882 * Note: LogReader adds the message, category, and source to the DOM as HTML.
16883 *
16884 * @method log
16885 * @static
16886 * @param  {HTML}  msg  The message to log.
16887 * @param  {HTML}  cat  The log category for the message.  Default
16888 *                        categories are "info", "warn", "error", time".
16889 *                        Custom categories can be used as well. (opt)
16890 * @param  {HTML}  src  The source of the the message (opt)
16891 * @return {Boolean}      True if the log operation was successful.
16892 */
16893YAHOO.log = function(msg, cat, src) {
16894    var l=YAHOO.widget.Logger;
16895    if(l && l.log) {
16896        return l.log(msg, cat, src);
16897    } else {
16898        return false;
16899    }
16900};
16901
16902/**
16903 * Registers a module with the YAHOO object
16904 * @method register
16905 * @static
16906 * @param {String}   name    the name of the module (event, slider, etc)
16907 * @param {Function} mainClass a reference to class in the module.  This
16908 *                             class will be tagged with the version info
16909 *                             so that it will be possible to identify the
16910 *                             version that is in use when multiple versions
16911 *                             have loaded
16912 * @param {Object}   data      metadata object for the module.  Currently it
16913 *                             is expected to contain a "version" property
16914 *                             and a "build" property at minimum.
16915 */
16916YAHOO.register = function(name, mainClass, data) {
16917    var mods = YAHOO.env.modules, m, v, b, ls, i;
16918
16919    if (!mods[name]) {
16920        mods[name] = {
16921            versions:[],
16922            builds:[]
16923        };
16924    }
16925
16926    m  = mods[name];
16927    v  = data.version;
16928    b  = data.build;
16929    ls = YAHOO.env.listeners;
16930
16931    m.name = name;
16932    m.version = v;
16933    m.build = b;
16934    m.versions.push(v);
16935    m.builds.push(b);
16936    m.mainClass = mainClass;
16937
16938    // fire the module load listeners
16939    for (i=0;i<ls.length;i=i+1) {
16940        ls[i](m);
16941    }
16942    // label the main class
16943    if (mainClass) {
16944        mainClass.VERSION = v;
16945        mainClass.BUILD = b;
16946    } else {
16947        YAHOO.log("mainClass is undefined for module " + name, "warn");
16948    }
16949};
16950
16951/**
16952 * YAHOO.env is used to keep track of what is known about the YUI library and
16953 * the browsing environment
16954 * @class YAHOO.env
16955 * @static
16956 */
16957YAHOO.env = YAHOO.env || {
16958
16959    /**
16960     * Keeps the version info for all YUI modules that have reported themselves
16961     * @property modules
16962     * @type Object[]
16963     */
16964    modules: [],
16965
16966    /**
16967     * List of functions that should be executed every time a YUI module
16968     * reports itself.
16969     * @property listeners
16970     * @type Function[]
16971     */
16972    listeners: []
16973};
16974
16975/**
16976 * Returns the version data for the specified module:
16977 *      <dl>
16978 *      <dt>name:</dt>      <dd>The name of the module</dd>
16979 *      <dt>version:</dt>   <dd>The version in use</dd>
16980 *      <dt>build:</dt>     <dd>The build number in use</dd>
16981 *      <dt>versions:</dt>  <dd>All versions that were registered</dd>
16982 *      <dt>builds:</dt>    <dd>All builds that were registered.</dd>
16983 *      <dt>mainClass:</dt> <dd>An object that was was stamped with the
16984 *                 current version and build. If
16985 *                 mainClass.VERSION != version or mainClass.BUILD != build,
16986 *                 multiple versions of pieces of the library have been
16987 *                 loaded, potentially causing issues.</dd>
16988 *       </dl>
16989 *
16990 * @method getVersion
16991 * @static
16992 * @param {String}  name the name of the module (event, slider, etc)
16993 * @return {Object} The version info
16994 */
16995YAHOO.env.getVersion = function(name) {
16996    return YAHOO.env.modules[name] || null;
16997};
16998
16999/**
17000 * Do not fork for a browser if it can be avoided.  Use feature detection when
17001 * you can.  Use the user agent as a last resort.  YAHOO.env.ua stores a version
17002 * number for the browser engine, 0 otherwise.  This value may or may not map
17003 * to the version number of the browser using the engine.  The value is
17004 * presented as a float so that it can easily be used for boolean evaluation
17005 * as well as for looking for a particular range of versions.  Because of this,
17006 * some of the granularity of the version info may be lost (e.g., Gecko 1.8.0.9
17007 * reports 1.8).
17008 * @class YAHOO.env.ua
17009 * @static
17010 */
17011
17012/**
17013 * parses a user agent string (or looks for one in navigator to parse if
17014 * not supplied).
17015 * @method parseUA
17016 * @since 2.9.0
17017 * @static
17018 */
17019YAHOO.env.parseUA = function(agent) {
17020
17021        var numberify = function(s) {
17022            var c = 0;
17023            return parseFloat(s.replace(/\./g, function() {
17024                return (c++ == 1) ? '' : '.';
17025            }));
17026        },
17027
17028        nav = navigator,
17029
17030        o = {
17031
17032        /**
17033         * Internet Explorer version number or 0.  Example: 6
17034         * @property ie
17035         * @type float
17036         * @static
17037         */
17038        ie: 0,
17039
17040        /**
17041         * Opera version number or 0.  Example: 9.2
17042         * @property opera
17043         * @type float
17044         * @static
17045         */
17046        opera: 0,
17047
17048        /**
17049         * Gecko engine revision number.  Will evaluate to 1 if Gecko
17050         * is detected but the revision could not be found. Other browsers
17051         * will be 0.  Example: 1.8
17052         * <pre>
17053         * Firefox 1.0.0.4: 1.7.8   <-- Reports 1.7
17054         * Firefox 1.5.0.9: 1.8.0.9 <-- 1.8
17055         * Firefox 2.0.0.3: 1.8.1.3 <-- 1.81
17056         * Firefox 3.0   <-- 1.9
17057         * Firefox 3.5   <-- 1.91
17058         * </pre>
17059         * @property gecko
17060         * @type float
17061         * @static
17062         */
17063        gecko: 0,
17064
17065        /**
17066         * AppleWebKit version.  KHTML browsers that are not WebKit browsers
17067         * will evaluate to 1, other browsers 0.  Example: 418.9
17068         * <pre>
17069         * Safari 1.3.2 (312.6): 312.8.1 <-- Reports 312.8 -- currently the
17070         *                                   latest available for Mac OSX 10.3.
17071         * Safari 2.0.2:         416     <-- hasOwnProperty introduced
17072         * Safari 2.0.4:         418     <-- preventDefault fixed
17073         * Safari 2.0.4 (419.3): 418.9.1 <-- One version of Safari may run
17074         *                                   different versions of webkit
17075         * Safari 2.0.4 (419.3): 419     <-- Tiger installations that have been
17076         *                                   updated, but not updated
17077         *                                   to the latest patch.
17078         * Webkit 212 nightly:   522+    <-- Safari 3.0 precursor (with native
17079         * SVG and many major issues fixed).
17080         * Safari 3.0.4 (523.12) 523.12  <-- First Tiger release - automatic
17081         * update from 2.x via the 10.4.11 OS patch.
17082         * Webkit nightly 1/2008:525+    <-- Supports DOMContentLoaded event.
17083         *                                   yahoo.com user agent hack removed.
17084         * </pre>
17085         * http://en.wikipedia.org/wiki/Safari_version_history
17086         * @property webkit
17087         * @type float
17088         * @static
17089         */
17090        webkit: 0,
17091
17092        /**
17093         * Chrome will be detected as webkit, but this property will also
17094         * be populated with the Chrome version number
17095         * @property chrome
17096         * @type float
17097         * @static
17098         */
17099        chrome: 0,
17100
17101        /**
17102         * The mobile property will be set to a string containing any relevant
17103         * user agent information when a modern mobile browser is detected.
17104         * Currently limited to Safari on the iPhone/iPod Touch, Nokia N-series
17105         * devices with the WebKit-based browser, and Opera Mini.
17106         * @property mobile
17107         * @type string
17108         * @static
17109         */
17110        mobile: null,
17111
17112        /**
17113         * Adobe AIR version number or 0.  Only populated if webkit is detected.
17114         * Example: 1.0
17115         * @property air
17116         * @type float
17117         */
17118        air: 0,
17119        /**
17120         * Detects Apple iPad's OS version
17121         * @property ipad
17122         * @type float
17123         * @static
17124         */
17125        ipad: 0,
17126        /**
17127         * Detects Apple iPhone's OS version
17128         * @property iphone
17129         * @type float
17130         * @static
17131         */
17132        iphone: 0,
17133        /**
17134         * Detects Apples iPod's OS version
17135         * @property ipod
17136         * @type float
17137         * @static
17138         */
17139        ipod: 0,
17140        /**
17141         * General truthy check for iPad, iPhone or iPod
17142         * @property ios
17143         * @type float
17144         * @static
17145         */
17146        ios: null,
17147        /**
17148         * Detects Googles Android OS version
17149         * @property android
17150         * @type float
17151         * @static
17152         */
17153        android: 0,
17154        /**
17155         * Detects Palms WebOS version
17156         * @property webos
17157         * @type float
17158         * @static
17159         */
17160        webos: 0,
17161
17162        /**
17163         * Google Caja version number or 0.
17164         * @property caja
17165         * @type float
17166         */
17167        caja: nav && nav.cajaVersion,
17168
17169        /**
17170         * Set to true if the page appears to be in SSL
17171         * @property secure
17172         * @type boolean
17173         * @static
17174         */
17175        secure: false,
17176
17177        /**
17178         * The operating system.  Currently only detecting windows or macintosh
17179         * @property os
17180         * @type string
17181         * @static
17182         */
17183        os: null
17184
17185    },
17186
17187    ua = agent || (navigator && navigator.userAgent),
17188
17189    loc = window && window.location,
17190
17191    href = loc && loc.href,
17192
17193    m;
17194
17195    o.secure = href && (href.toLowerCase().indexOf("https") === 0);
17196
17197    if (ua) {
17198
17199        if ((/windows|win32/i).test(ua)) {
17200            o.os = 'windows';
17201        } else if ((/macintosh/i).test(ua)) {
17202            o.os = 'macintosh';
17203        } else if ((/rhino/i).test(ua)) {
17204            o.os = 'rhino';
17205        }
17206
17207        // Modern KHTML browsers should qualify as Safari X-Grade
17208        if ((/KHTML/).test(ua)) {
17209            o.webkit = 1;
17210        }
17211        // Modern WebKit browsers are at least X-Grade
17212        m = ua.match(/AppleWebKit\/([^\s]*)/);
17213        if (m && m[1]) {
17214            o.webkit = numberify(m[1]);
17215
17216            // Mobile browser check
17217            if (/ Mobile\//.test(ua)) {
17218                o.mobile = 'Apple'; // iPhone or iPod Touch
17219
17220                m = ua.match(/OS ([^\s]*)/);
17221                if (m && m[1]) {
17222                    m = numberify(m[1].replace('_', '.'));
17223                }
17224                o.ios = m;
17225                o.ipad = o.ipod = o.iphone = 0;
17226
17227                m = ua.match(/iPad|iPod|iPhone/);
17228                if (m && m[0]) {
17229                    o[m[0].toLowerCase()] = o.ios;
17230                }
17231            } else {
17232                m = ua.match(/NokiaN[^\/]*|Android \d\.\d|webOS\/\d\.\d/);
17233                if (m) {
17234                    // Nokia N-series, Android, webOS, ex: NokiaN95
17235                    o.mobile = m[0];
17236                }
17237                if (/webOS/.test(ua)) {
17238                    o.mobile = 'WebOS';
17239                    m = ua.match(/webOS\/([^\s]*);/);
17240                    if (m && m[1]) {
17241                        o.webos = numberify(m[1]);
17242                    }
17243                }
17244                if (/ Android/.test(ua)) {
17245                    o.mobile = 'Android';
17246                    m = ua.match(/Android ([^\s]*);/);
17247                    if (m && m[1]) {
17248                        o.android = numberify(m[1]);
17249                    }
17250
17251                }
17252            }
17253
17254            m = ua.match(/Chrome\/([^\s]*)/);
17255            if (m && m[1]) {
17256                o.chrome = numberify(m[1]); // Chrome
17257            } else {
17258                m = ua.match(/AdobeAIR\/([^\s]*)/);
17259                if (m) {
17260                    o.air = m[0]; // Adobe AIR 1.0 or better
17261                }
17262            }
17263        }
17264
17265        if (!o.webkit) { // not webkit
17266// @todo check Opera/8.01 (J2ME/MIDP; Opera Mini/2.0.4509/1316; fi; U; ssr)
17267            m = ua.match(/Opera[\s\/]([^\s]*)/);
17268            if (m && m[1]) {
17269                o.opera = numberify(m[1]);
17270                m = ua.match(/Version\/([^\s]*)/);
17271                if (m && m[1]) {
17272                    o.opera = numberify(m[1]); // opera 10+
17273                }
17274                m = ua.match(/Opera Mini[^;]*/);
17275                if (m) {
17276                    o.mobile = m[0]; // ex: Opera Mini/2.0.4509/1316
17277                }
17278            } else { // not opera or webkit
17279                m = ua.match(/MSIE ([^;]*)|Trident.*; rv.([0-9.]+)/);
17280                if (m && (m[1] || m[2])) {
17281                    o.ie = numberify(m[1] || m[2]);
17282                } else { // not opera, webkit, or ie
17283                    m = ua.match(/Gecko\/([^\s]*)/);
17284                    if (m) {
17285                        o.gecko = 1; // Gecko detected, look for revision
17286                        m = ua.match(/rv:([^\s\)]*)/);
17287                        if (m && m[1]) {
17288                            o.gecko = numberify(m[1]);
17289                        }
17290                    }
17291                }
17292            }
17293        }
17294    }
17295
17296    return o;
17297};
17298
17299YAHOO.env.ua = YAHOO.env.parseUA();
17300
17301/*
17302 * Initializes the global by creating the default namespaces and applying
17303 * any new configuration information that is detected.  This is the setup
17304 * for env.
17305 * @method init
17306 * @static
17307 * @private
17308 */
17309(function() {
17310    YAHOO.namespace("util", "widget", "example");
17311    /*global YAHOO_config*/
17312    if ("undefined" !== typeof YAHOO_config) {
17313        var l=YAHOO_config.listener, ls=YAHOO.env.listeners,unique=true, i;
17314        if (l) {
17315            // if YAHOO is loaded multiple times we need to check to see if
17316            // this is a new config object.  If it is, add the new component
17317            // load listener to the stack
17318            for (i=0; i<ls.length; i++) {
17319                if (ls[i] == l) {
17320                    unique = false;
17321                    break;
17322                }
17323            }
17324
17325            if (unique) {
17326                ls.push(l);
17327            }
17328        }
17329    }
17330})();
17331/**
17332 * Provides the language utilites and extensions used by the library
17333 * @class YAHOO.lang
17334 */
17335YAHOO.lang = YAHOO.lang || {};
17336
17337(function() {
17338
17339
17340var L = YAHOO.lang,
17341
17342    OP = Object.prototype,
17343    ARRAY_TOSTRING = '[object Array]',
17344    FUNCTION_TOSTRING = '[object Function]',
17345    OBJECT_TOSTRING = '[object Object]',
17346    NOTHING = [],
17347
17348    HTML_CHARS = {
17349        '&': '&amp;',
17350        '<': '&lt;',
17351        '>': '&gt;',
17352        '"': '&quot;',
17353        "'": '&#x27;',
17354        '/': '&#x2F;',
17355        '`': '&#x60;'
17356    },
17357
17358    // ADD = ["toString", "valueOf", "hasOwnProperty"],
17359    ADD = ["toString", "valueOf"],
17360
17361    OB = {
17362
17363    /**
17364     * Determines wheather or not the provided object is an array.
17365     * @method isArray
17366     * @param {any} o The object being testing
17367     * @return {boolean} the result
17368     */
17369    isArray: function(o) {
17370        return OP.toString.apply(o) === ARRAY_TOSTRING;
17371    },
17372
17373    /**
17374     * Determines whether or not the provided object is a boolean
17375     * @method isBoolean
17376     * @param {any} o The object being testing
17377     * @return {boolean} the result
17378     */
17379    isBoolean: function(o) {
17380        return typeof o === 'boolean';
17381    },
17382
17383    /**
17384     * Determines whether or not the provided object is a function.
17385     * Note: Internet Explorer thinks certain functions are objects:
17386     *
17387     * var obj = document.createElement("object");
17388     * YAHOO.lang.isFunction(obj.getAttribute) // reports false in IE
17389     *
17390     * var input = document.createElement("input"); // append to body
17391     * YAHOO.lang.isFunction(input.focus) // reports false in IE
17392     *
17393     * You will have to implement additional tests if these functions
17394     * matter to you.
17395     *
17396     * @method isFunction
17397     * @param {any} o The object being testing
17398     * @return {boolean} the result
17399     */
17400    isFunction: function(o) {
17401        return (typeof o === 'function') || OP.toString.apply(o) === FUNCTION_TOSTRING;
17402    },
17403
17404    /**
17405     * Determines whether or not the provided object is null
17406     * @method isNull
17407     * @param {any} o The object being testing
17408     * @return {boolean} the result
17409     */
17410    isNull: function(o) {
17411        return o === null;
17412    },
17413
17414    /**
17415     * Determines whether or not the provided object is a legal number
17416     * @method isNumber
17417     * @param {any} o The object being testing
17418     * @return {boolean} the result
17419     */
17420    isNumber: function(o) {
17421        return typeof o === 'number' && isFinite(o);
17422    },
17423
17424    /**
17425     * Determines whether or not the provided object is of type object
17426     * or function
17427     * @method isObject
17428     * @param {any} o The object being testing
17429     * @return {boolean} the result
17430     */
17431    isObject: function(o) {
17432return (o && (typeof o === 'object' || L.isFunction(o))) || false;
17433    },
17434
17435    /**
17436     * Determines whether or not the provided object is a string
17437     * @method isString
17438     * @param {any} o The object being testing
17439     * @return {boolean} the result
17440     */
17441    isString: function(o) {
17442        return typeof o === 'string';
17443    },
17444
17445    /**
17446     * Determines whether or not the provided object is undefined
17447     * @method isUndefined
17448     * @param {any} o The object being testing
17449     * @return {boolean} the result
17450     */
17451    isUndefined: function(o) {
17452        return typeof o === 'undefined';
17453    },
17454
17455
17456    /**
17457     * IE will not enumerate native functions in a derived object even if the
17458     * function was overridden.  This is a workaround for specific functions
17459     * we care about on the Object prototype.
17460     * @property _IEEnumFix
17461     * @param {Function} r  the object to receive the augmentation
17462     * @param {Function} s  the object that supplies the properties to augment
17463     * @static
17464     * @private
17465     */
17466    _IEEnumFix: (YAHOO.env.ua.ie) ? function(r, s) {
17467            var i, fname, f;
17468            for (i=0;i<ADD.length;i=i+1) {
17469
17470                fname = ADD[i];
17471                f = s[fname];
17472
17473                if (L.isFunction(f) && f!=OP[fname]) {
17474                    r[fname]=f;
17475                }
17476            }
17477    } : function(){},
17478
17479    /**
17480     * <p>
17481     * Returns a copy of the specified string with special HTML characters
17482     * escaped. The following characters will be converted to their
17483     * corresponding character entities:
17484     * <code>&amp; &lt; &gt; &quot; &#x27; &#x2F; &#x60;</code>
17485     * </p>
17486     *
17487     * <p>
17488     * This implementation is based on the
17489     * <a href="http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet">OWASP
17490     * HTML escaping recommendations</a>. In addition to the characters
17491     * in the OWASP recommendation, we also escape the <code>&#x60;</code>
17492     * character, since IE interprets it as an attribute delimiter when used in
17493     * innerHTML.
17494     * </p>
17495     *
17496     * @method escapeHTML
17497     * @param {String} html String to escape.
17498     * @return {String} Escaped string.
17499     * @static
17500     * @since 2.9.0
17501     */
17502    escapeHTML: function (html) {
17503        return html.replace(/[&<>"'\/`]/g, function (match) {
17504            return HTML_CHARS[match];
17505        });
17506    },
17507
17508    /**
17509     * Utility to set up the prototype, constructor and superclass properties to
17510     * support an inheritance strategy that can chain constructors and methods.
17511     * Static members will not be inherited.
17512     *
17513     * @method extend
17514     * @static
17515     * @param {Function} subc   the object to modify
17516     * @param {Function} superc the object to inherit
17517     * @param {Object} overrides  additional properties/methods to add to the
17518     *                              subclass prototype.  These will override the
17519     *                              matching items obtained from the superclass
17520     *                              if present.
17521     */
17522    extend: function(subc, superc, overrides) {
17523        if (!superc||!subc) {
17524            throw new Error("extend failed, please check that " +
17525                            "all dependencies are included.");
17526        }
17527        var F = function() {}, i;
17528        F.prototype=superc.prototype;
17529        subc.prototype=new F();
17530        subc.prototype.constructor=subc;
17531        subc.superclass=superc.prototype;
17532        if (superc.prototype.constructor == OP.constructor) {
17533            superc.prototype.constructor=superc;
17534        }
17535
17536        if (overrides) {
17537            for (i in overrides) {
17538                if (L.hasOwnProperty(overrides, i)) {
17539                    subc.prototype[i]=overrides[i];
17540                }
17541            }
17542
17543            L._IEEnumFix(subc.prototype, overrides);
17544        }
17545    },
17546
17547    /**
17548     * Applies all properties in the supplier to the receiver if the
17549     * receiver does not have these properties yet.  Optionally, one or
17550     * more methods/properties can be specified (as additional
17551     * parameters).  This option will overwrite the property if receiver
17552     * has it already.  If true is passed as the third parameter, all
17553     * properties will be applied and _will_ overwrite properties in
17554     * the receiver.
17555     *
17556     * @method augmentObject
17557     * @static
17558     * @since 2.3.0
17559     * @param {Function} r  the object to receive the augmentation
17560     * @param {Function} s  the object that supplies the properties to augment
17561     * @param {String*|boolean}  arguments zero or more properties methods
17562     *        to augment the receiver with.  If none specified, everything
17563     *        in the supplier will be used unless it would
17564     *        overwrite an existing property in the receiver. If true
17565     *        is specified as the third parameter, all properties will
17566     *        be applied and will overwrite an existing property in
17567     *        the receiver
17568     */
17569    augmentObject: function(r, s) {
17570        if (!s||!r) {
17571            throw new Error("Absorb failed, verify dependencies.");
17572        }
17573        var a=arguments, i, p, overrideList=a[2];
17574        if (overrideList && overrideList!==true) { // only absorb the specified properties
17575            for (i=2; i<a.length; i=i+1) {
17576                r[a[i]] = s[a[i]];
17577            }
17578        } else { // take everything, overwriting only if the third parameter is true
17579            for (p in s) {
17580                if (overrideList || !(p in r)) {
17581                    r[p] = s[p];
17582                }
17583            }
17584
17585            L._IEEnumFix(r, s);
17586        }
17587
17588        return r;
17589    },
17590
17591    /**
17592     * Same as YAHOO.lang.augmentObject, except it only applies prototype properties
17593     * @see YAHOO.lang.augmentObject
17594     * @method augmentProto
17595     * @static
17596     * @param {Function} r  the object to receive the augmentation
17597     * @param {Function} s  the object that supplies the properties to augment
17598     * @param {String*|boolean}  arguments zero or more properties methods
17599     *        to augment the receiver with.  If none specified, everything
17600     *        in the supplier will be used unless it would overwrite an existing
17601     *        property in the receiver.  if true is specified as the third
17602     *        parameter, all properties will be applied and will overwrite an
17603     *        existing property in the receiver
17604     */
17605    augmentProto: function(r, s) {
17606        if (!s||!r) {
17607            throw new Error("Augment failed, verify dependencies.");
17608        }
17609        //var a=[].concat(arguments);
17610        var a=[r.prototype,s.prototype], i;
17611        for (i=2;i<arguments.length;i=i+1) {
17612            a.push(arguments[i]);
17613        }
17614        L.augmentObject.apply(this, a);
17615
17616        return r;
17617    },
17618
17619
17620    /**
17621     * Returns a simple string representation of the object or array.
17622     * Other types of objects will be returned unprocessed.  Arrays
17623     * are expected to be indexed.  Use object notation for
17624     * associative arrays.
17625     * @method dump
17626     * @since 2.3.0
17627     * @param o {Object} The object to dump
17628     * @param d {int} How deep to recurse child objects, default 3
17629     * @return {String} the dump result
17630     */
17631    dump: function(o, d) {
17632        var i,len,s=[],OBJ="{...}",FUN="f(){...}",
17633            COMMA=', ', ARROW=' => ';
17634
17635        // Cast non-objects to string
17636        // Skip dates because the std toString is what we want
17637        // Skip HTMLElement-like objects because trying to dump
17638        // an element will cause an unhandled exception in FF 2.x
17639        if (!L.isObject(o)) {
17640            return o + "";
17641        } else if (o instanceof Date || ("nodeType" in o && "tagName" in o)) {
17642            return o;
17643        } else if  (L.isFunction(o)) {
17644            return FUN;
17645        }
17646
17647        // dig into child objects the depth specifed. Default 3
17648        d = (L.isNumber(d)) ? d : 3;
17649
17650        // arrays [1, 2, 3]
17651        if (L.isArray(o)) {
17652            s.push("[");
17653            for (i=0,len=o.length;i<len;i=i+1) {
17654                if (L.isObject(o[i])) {
17655                    s.push((d > 0) ? L.dump(o[i], d-1) : OBJ);
17656                } else {
17657                    s.push(o[i]);
17658                }
17659                s.push(COMMA);
17660            }
17661            if (s.length > 1) {
17662                s.pop();
17663            }
17664            s.push("]");
17665        // objects {k1 => v1, k2 => v2}
17666        } else {
17667            s.push("{");
17668            for (i in o) {
17669                if (L.hasOwnProperty(o, i)) {
17670                    s.push(i + ARROW);
17671                    if (L.isObject(o[i])) {
17672                        s.push((d > 0) ? L.dump(o[i], d-1) : OBJ);
17673                    } else {
17674                        s.push(o[i]);
17675                    }
17676                    s.push(COMMA);
17677                }
17678            }
17679            if (s.length > 1) {
17680                s.pop();
17681            }
17682            s.push("}");
17683        }
17684
17685        return s.join("");
17686    },
17687
17688    /**
17689     * Does variable substitution on a string. It scans through the string
17690     * looking for expressions enclosed in { } braces. If an expression
17691     * is found, it is used a key on the object.  If there is a space in
17692     * the key, the first word is used for the key and the rest is provided
17693     * to an optional function to be used to programatically determine the
17694     * value (the extra information might be used for this decision). If
17695     * the value for the key in the object, or what is returned from the
17696     * function has a string value, number value, or object value, it is
17697     * substituted for the bracket expression and it repeats.  If this
17698     * value is an object, it uses the Object's toString() if this has
17699     * been overridden, otherwise it does a shallow dump of the key/value
17700     * pairs.
17701     *
17702     * By specifying the recurse option, the string is rescanned after
17703     * every replacement, allowing for nested template substitutions.
17704     * The side effect of this option is that curly braces in the
17705     * replacement content must be encoded.
17706     *
17707     * @method substitute
17708     * @since 2.3.0
17709     * @param s {String} The string that will be modified.
17710     * @param o {Object}
17710 An object containing the replacement values
17711     * @param f {Function} An optional function that can be used to
17712     *                     process each match.  It receives the key,
17713     *                     value, and any extra metadata included with
17714     *                     the key inside of the braces.
17715     * @param recurse {boolean} default true - if not false, the replaced
17716     * string will be rescanned so that nested substitutions are possible.
17717     * @return {String} the substituted string
17718     */
17719    substitute: function (s, o, f, recurse) {
17720        var i, j, k, key, v, meta, saved=[], token, lidx=s.length,
17721            DUMP='dump', SPACE=' ', LBRACE='{', RBRACE='}',
17722            dump, objstr;
17723
17724        for (;;) {
17725            i = s.lastIndexOf(LBRACE, lidx);
17726            if (i < 0) {
17727                break;
17728            }
17729            j = s.indexOf(RBRACE, i);
17730            if (i + 1 > j) {
17731                break;
17732            }
17733
17734            //Extract key and meta info
17735            token = s.substring(i + 1, j);
17736            key = token;
17737            meta = null;
17738            k = key.indexOf(SPACE);
17739            if (k > -1) {
17740                meta = key.substring(k + 1);
17741                key = key.substring(0, k);
17742            }
17743
17744            // lookup the value
17745            v = o[key];
17746
17747            // if a substitution function was provided, execute it
17748            if (f) {
17749                v = f(key, v, meta);
17750            }
17751
17752            if (L.isObject(v)) {
17753                if (L.isArray(v)) {
17754                    v = L.dump(v, parseInt(meta, 10));
17755                } else {
17756                    meta = meta || "";
17757
17758                    // look for the keyword 'dump', if found force obj dump
17759                    dump = meta.indexOf(DUMP);
17760                    if (dump > -1) {
17761                        meta = meta.substring(4);
17762                    }
17763
17764                    objstr = v.toString();
17765
17766                    // use the toString if it is not the Object toString
17767                    // and the 'dump' meta info was not found
17768                    if (objstr === OBJECT_TOSTRING || dump > -1) {
17769                        v = L.dump(v, parseInt(meta, 10));
17770                    } else {
17771                        v = objstr;
17772                    }
17773                }
17774            } else if (!L.isString(v) && !L.isNumber(v)) {
17775                // This {block} has no replace string. Save it for later.
17776                v = "~-" + saved.length + "-~";
17777                saved[saved.length] = token;
17778
17779                // break;
17780            }
17781
17782            s = s.substring(0, i) + v + s.substring(j + 1);
17783
17784            if (recurse === false) {
17785                lidx = i-1;
17786            }
17787
17788        }
17789
17790        // restore saved {block}s
17791        for (i=saved.length-1; i>=0; i=i-1) {
17792            s = s.replace(new RegExp("~-" + i + "-~"), "{"  + saved[i] + "}", "g");
17793        }
17794
17795        return s;
17796    },
17797
17798
17799    /**
17800     * Returns a string without any leading or trailing whitespace.  If
17801     * the input is not a string, the input will be returned untouched.
17802     * @method trim
17803     * @since 2.3.0
17804     * @param s {string} the string to trim
17805     * @return {string} the trimmed string
17806     */
17807    trim: function(s){
17808        try {
17809            return s.replace(/^\s+|\s+$/g, "");
17810        } catch(e) {
17811            return s;
17812        }
17813    },
17814
17815    /**
17816     * Returns a new object containing all of the properties of
17817     * all the supplied objects.  The properties from later objects
17818     * will overwrite those in earlier objects.
17819     * @method merge
17820     * @since 2.3.0
17821     * @param arguments {Object*} the objects to merge
17822     * @return the new merged object
17823     */
17824    merge: function() {
17825        var o={}, a=arguments, l=a.length, i;
17826        for (i=0; i<l; i=i+1) {
17827            L.augmentObject(o, a[i], true);
17828        }
17829        return o;
17830    },
17831
17832    /**
17833     * Executes the supplied function in the context of the supplied
17834     * object 'when' milliseconds later.  Executes the function a
17835     * single time unless periodic is set to true.
17836     * @method later
17837     * @since 2.4.0
17838     * @param when {int} the number of milliseconds to wait until the fn
17839     * is executed
17840     * @param o the context object
17841     * @param fn {Function|String} the function to execute or the name of
17842     * the method in the 'o' object to execute
17843     * @param data [Array] data that is provided to the function.  This accepts
17844     * either a single item or an array.  If an array is provided, the
17845     * function is executed with one parameter for each array item.  If
17846     * you need to pass a single array parameter, it needs to be wrapped in
17847     * an array [myarray]
17848     * @param periodic {boolean} if true, executes continuously at supplied
17849     * interval until canceled
17850     * @return a timer object. Call the cancel() method on this object to
17851     * stop the timer.
17852     */
17853    later: function(when, o, fn, data, periodic) {
17854        when = when || 0;
17855        o = o || {};
17856        var m=fn, d=data, f, r;
17857
17858        if (L.isString(fn)) {
17859            m = o[fn];
17860        }
17861
17862        if (!m) {
17863            throw new TypeError("method undefined");
17864        }
17865
17866        if (!L.isUndefined(data) && !L.isArray(d)) {
17867            d = [data];
17868        }
17869
17870        f = function() {
17871            m.apply(o, d || NOTHING);
17872        };
17873
17874        r = (periodic) ? setInterval(f, when) : setTimeout(f, when);
17875
17876        return {
17877            interval: periodic,
17878            cancel: function() {
17879                if (this.interval) {
17880                    clearInterval(r);
17881                } else {
17882                    clearTimeout(r);
17883                }
17884            }
17885        };
17886    },
17887
17888    /**
17889     * A convenience method for detecting a legitimate non-null value.
17890     * Returns false for null/undefined/NaN, true for other values,
17891     * including 0/false/''
17892     * @method isValue
17893     * @since 2.3.0
17894     * @param o {any} the item to test
17895     * @return {boolean} true if it is not null/undefined/NaN || false
17896     */
17897    isValue: function(o) {
17898        // return (o || o === false || o === 0 || o === ''); // Infinity fails
17899return (L.isObject(o) || L.isString(o) || L.isNumber(o) || L.isBoolean(o));
17900    }
17901
17902};
17903
17904/**
17905 * Determines whether or not the property was added
17906 * to the object instance.  Returns false if the property is not present
17907 * in the object, or was inherited from the prototype.
17908 * This abstraction is provided to enable hasOwnProperty for Safari 1.3.x.
17909 * There is a discrepancy between YAHOO.lang.hasOwnProperty and
17910 * Object.prototype.hasOwnProperty when the property is a primitive added to
17911 * both the instance AND prototype with the same value:
17912 * <pre>
17913 * var A = function() {};
17914 * A.prototype.foo = 'foo';
17915 * var a = new A();
17916 * a.foo = 'foo';
17917 * alert(a.hasOwnProperty('foo')); // true
17918 * alert(YAHOO.lang.hasOwnProperty(a, 'foo')); // false when using fallback
17919 * </pre>
17920 * @method hasOwnProperty
17921 * @param {any} o The object being testing
17922 * @param prop {string} the name of the property to test
17923 * @return {boolean} the result
17924 */
17925L.hasOwnProperty = (OP.hasOwnProperty) ?
17926    function(o, prop) {
17927        return o && o.hasOwnProperty && o.hasOwnProperty(prop);
17928    } : function(o, prop) {
17929        return !L.isUndefined(o[prop]) &&
17930                o.constructor.prototype[prop] !== o[prop];
17931    };
17932
17933// new lang wins
17934OB.augmentObject(L, OB, true);
17935
17936/*
17937 * An alias for <a href="YAHOO.lang.html">YAHOO.lang</a>
17938 * @class YAHOO.util.Lang
17939 */
17940YAHOO.util.Lang = L;
17941
17942/**
17943 * Same as YAHOO.lang.augmentObject, except it only applies prototype
17944 * properties.  This is an alias for augmentProto.
17945 * @see YAHOO.lang.augmentObject
17946 * @method augment
17947 * @static
17948 * @param {Function} r  the object to receive the augmentation
17949 * @param {Function} s  the object that supplies the properties to augment
17950 * @param {String*|boolean}  arguments zero or more properties methods to
17951 *        augment the receiver with.  If none specified, everything
17952 *        in the supplier will be used unless it would
17953 *        overwrite an existing property in the receiver.  if true
17954 *        is specified as the third parameter, all properties will
17955 *        be applied and will overwrite an existing property in
17956 *        the receiver
17957 */
17958L.augment = L.augmentProto;
17959
17960/**
17961 * An alias for <a href="YAHOO.lang.html#augment">YAHOO.lang.augment</a>
17962 * @for YAHOO
17963 * @method augment
17964 * @static
17965 * @param {Function} r  the object to receive the augmentation
17966 * @param {Function} s  the object that supplies the properties to augment
17967 * @param {String*}  arguments zero or more properties methods to
17968 *        augment the receiver with.  If none specified, everything
17969 *        in the supplier will be used unless it would
17970 *        overwrite an existing property in the receiver
17971 */
17972YAHOO.augment = L.augmentProto;
17973
17974/**
17975 * An alias for <a href="YAHOO.lang.html#extend">YAHOO.lang.extend</a>
17976 * @method extend
17977 * @static
17978 * @param {Function} subc   the object to modify
17979 * @param {Function} superc the object to inherit
17980 * @param {Object} overrides  additional properties/methods to add to the
17981 *        subclass prototype.  These will override the
17982 *        matching items obtained from the superclass if present.
17983 */
17984YAHOO.extend = L.extend;
17985
17986})();
17987YAHOO.register("yahoo", YAHOO, {version: "2.9.0", build: "2800"});
17988/**
17989 * Provides a mechanism to fetch remote resources and
17990 * insert them into a document
17991 * This utility can fetch JavaScript and CSS files, inserting script
17992 * tags for script and link tags for CSS.  Note, this
17993 * is done via the normal browser mechanisms for inserting
17994 * these resources and making the content available to
17995 * code that would access it.  Be careful when retreiving
17996 * remote resources.  Only use this utility to fetch
17997 * files from sites you trust.
17998 *
17999 * @module get
18000 * @requires yahoo
18001 */
18002
18003/**
18004 * Fetches and inserts one or more script or link nodes into the document.
18005 * This utility can fetch JavaScript and CSS files, inserting script
18006 * tags for script and link tags for CSS.  Note, this
18007 * is done via the normal browser mechanisms for inserting
18008 * these resources and making the content available to
18009 * code that would access it.  Be careful when retreiving
18010 * remote resources.  Only use this utility to fetch
18011 * files from sites you trust.
18012 *
18013 * @namespace YAHOO.util
18014 * @class YAHOO.util.Get
18015 */
18016YAHOO.util.Get = function() {
18017
18018    /**
18019     * hash of queues to manage multiple requests
18020     * @property queues
18021     * @private
18022     */
18023    var queues={},
18024
18025    /**
18026     * queue index used to generate transaction ids
18027     * @property qidx
18028     * @type int
18029     * @private
18030     */
18031        qidx=0,
18032
18033    /**
18034     * node index used to generate unique node ids
18035     * @property nidx
18036     * @type int
18037     * @private
18038     */
18039        nidx=0,
18040
18041    /**
18042     * interal property used to prevent multiple simultaneous purge
18043     * processes
18044     * @property purging
18045     * @type boolean
18046     * @private
18047     */
18048        _purging=false,
18049
18050        ua=YAHOO.env.ua,
18051
18052        lang=YAHOO.lang,
18053
18054    _fail,
18055    _purge,
18056    _track,
18057
18058    /**
18059     * Generates an HTML element, this is not appended to a document
18060     * @method _node
18061     * @param type {string} the type of element
18062     * @param attr {string} the attributes
18063     * @param win {Window} optional window to create the element in
18064     * @return {HTMLElement} the generated node
18065     * @private
18066     */
18067    _node = function(type, attr, win) {
18068        var w = win || window, d=w.document, n=d.createElement(type), i;
18069
18070        for (i in attr) {
18071            if (attr.hasOwnProperty(i)) {
18072                n.setAttribute(i, attr[i]);
18073            }
18074        }
18075
18076        return n;
18077    },
18078
18079    /**
18080     * Generates a link node
18081     * @method _linkNode
18082     * @param url {string} the url for the css file
18083     * @param win {Window} optional window to create the node in
18084     * @return {HTMLElement} the generated node
18085     * @private
18086     */
18087    _linkNode = function(url, win, attributes) {
18088
18089        var o = {
18090            id:   "yui__dyn_" + (nidx++),
18091            type: "text/css",
18092            rel:  "stylesheet",
18093            href: url
18094        };
18095
18096        if (attributes) {
18097            lang.augmentObject(o, attributes);
18098        }
18099
18100        return _node("link", o, win);
18101    },
18102
18103    /**
18104     * Generates a script node
18105     * @method _scriptNode
18106     * @param url {string} the url for the script file
18107     * @param win {Window} optional window to create the node in
18108     * @return {HTMLElement} the generated node
18109     * @private
18110     */
18111    _scriptNode = function(url, win, attributes) {
18112        var o = {
18113            id:   "yui__dyn_" + (nidx++),
18114            type: "text/javascript",
18115            src:  url
18116        };
18117
18118        if (attributes) {
18119            lang.augmentObject(o, attributes);
18120        }
18121
18122        return _node("script", o, win);
18123    },
18124
18125    /**
18126     * Returns the data payload for callback functions
18127     * @method _returnData
18128     * @private
18129     */
18130    _returnData = function(q, msg) {
18131        return {
18132                tId: q.tId,
18133                win: q.win,
18134                data: q.data,
18135                nodes: q.nodes,
18136                msg: msg,
18137                purge: function() {
18138                    _purge(this.tId);
18139                }
18140            };
18141    },
18142
18143    _get = function(nId, tId) {
18144        var q = queues[tId],
18145            n = (lang.isString(nId)) ? q.win.document.getElementById(nId) : nId;
18146        if (!n) {
18147            _fail(tId, "target node not found: " + nId);
18148        }
18149
18150        return n;
18151    },
18152
18153
18154    /**
18155     * The request is complete, so executing the requester's callback
18156     * @method _finish
18157     * @param id {string} the id of the request
18158     * @private
18159     */
18160    _finish = function(id) {
18161        YAHOO.log("Finishing transaction " + id);
18162        var q = queues[id], msg, context;
18163        q.finished = true;
18164
18165        if (q.aborted) {
18166            msg = "transaction " + id + " was aborted";
18167            _fail(id, msg);
18168            return;
18169        }
18170
18171        // execute success callback
18172        if (q.onSuccess) {
18173            context = q.scope || q.win;
18174            q.onSuccess.call(context, _returnData(q));
18175        }
18176    },
18177
18178    /**
18179     * Timeout detected
18180     * @method _timeout
18181     * @param id {string} the id of the request
18182     * @private
18183     */
18184    _timeout = function(id) {
18185        YAHOO.log("Timeout " + id, "info", "get");
18186        var q = queues[id], context;
18187        if (q.onTimeout) {
18188            context = q.scope || q;
18189            q.onTimeout.call(context, _returnData(q));
18190        }
18191    },
18192
18193    /**
18194     * Loads the next item for a given request
18195     * @method _next
18196     * @param id {string} the id of the request
18197     * @param loaded {string} the url that was just loaded, if any
18198     * @private
18199     */
18200    _next = function(id, loaded) {
18201
18202        YAHOO.log("_next: " + id + ", loaded: " + loaded, "info", "Get");
18203
18204        var q = queues[id], w=q.win, d=w.document, h=d.getElementsByTagName("head")[0],
18205            n, msg, url, s, extra;
18206
18207        if (q.timer) {
18208            // Y.log('cancel timer');
18209            q.timer.cancel();
18210        }
18211
18212        if (q.aborted) {
18213            msg = "transaction " + id + " was aborted";
18214            _fail(id, msg);
18215            return;
18216        }
18217
18218        if (loaded) {
18219            q.url.shift();
18220            if (q.varName) {
18221                q.varName.shift();
18222            }
18223        } else {
18224            // This is the first pass: make sure the url is an array
18225            q.url = (lang.isString(q.url)) ? [q.url] : q.url;
18226            if (q.varName) {
18227                q.varName = (lang.isString(q.varName)) ? [q.varName] : q.varName;
18228            }
18229        }
18230
18231
18232        if (q.url.length === 0) {
18233            // Safari 2.x workaround - There is no way to know when
18234            // a script is ready in versions of Safari prior to 3.x.
18235            // Adding an extra node reduces the problem, but doesn't
18236            // eliminate it completely because the browser executes
18237            // them asynchronously.
18238            if (q.type === "script" && ua.webkit && ua.webkit < 420 &&
18239                    !q.finalpass && !q.varName) {
18240                // Add another script node.  This does not guarantee that the
18241                // scripts will execute in order, but it does appear to fix the
18242                // problem on fast connections more effectively than using an
18243                // arbitrary timeout.  It is possible that the browser does
18244                // block subsequent script execution in this case for a limited
18245                // time.
18246                extra = _scriptNode(null, q.win, q.attributes);
18247                extra.innerHTML='YAHOO.util.Get._finalize("' + id + '");';
18248                q.nodes.push(extra); h.appendChild(extra);
18249
18250            } else {
18251                _finish(id);
18252            }
18253
18254            return;
18255        }
18256
18257
18258        url = q.url[0];
18259
18260        // if the url is undefined, this is probably a trailing comma problem in IE
18261        if (!url) {
18262            q.url.shift();
18263            YAHOO.log('skipping empty url');
18264            return _next(id);
18265        }
18266
18267        YAHOO.log("attempting to load " + url, "info", "Get");
18268
18269        if (q.timeout) {
18270            // Y.log('create timer');
18271            q.timer = lang.later(q.timeout, q, _timeout, id);
18272        }
18273
18274        if (q.type === "script") {
18275            n = _scriptNode(url, w, q.attributes);
18276        } else {
18277            n = _linkNode(url, w, q.attributes);
18278        }
18279
18280        // track this node's load progress
18281        _track(q.type, n, id, url, w, q.url.length);
18282
18283        // add the node to the queue so we can return it to the user supplied callback
18284        q.nodes.push(n);
18285
18286        // add it to the head or insert it before 'insertBefore'
18287        if (q.insertBefore) {
18288            s = _get(q.insertBefore, id);
18289            if (s) {
18290                s.parentNode.insertBefore(n, s);
18291            }
18292        } else {
18293            h.appendChild(n);
18294        }
18295
18296        YAHOO.log("Appending node: " + url, "info", "Get");
18297
18298        // FireFox does not support the onload event for link nodes, so there is
18299        // no way to make the css requests synchronous. This means that the css
18300        // rules in multiple files could be applied out of order in this browser
18301        // if a later request returns before an earlier one.  Safari too.
18302        if ((ua.webkit || ua.gecko) && q.type === "css") {
18303            _next(id, url);
18304        }
18305    },
18306
18307    /**
18308     * Removes processed queues and corresponding nodes
18309     * @method _autoPurge
18310     * @private
18311     */
18312    _autoPurge = function() {
18313
18314        if (_purging) {
18315            return;
18316        }
18317
18318        _purging = true;
18319
18320        var i, q;
18321
18322        for (i in queues) {
18323            if (queues.hasOwnProperty(i)) {
18324                q = queues[i];
18325                if (q.autopurge && q.finished) {
18326                    _purge(q.tId);
18327                    delete queues[i];
18328                }
18329            }
18330        }
18331
18332        _purging = false;
18333    },
18334
18335    /**
18336     * Saves the state for the request and begins loading
18337     * the requested urls
18338     * @method queue
18339     * @param type {string} the type of node to insert
18340     * @param url {string} the url to load
18341     * @param opts the hash of options for this request
18342     * @private
18343     */
18344    _queue = function(type, url, opts) {
18345
18346        var id = "q" + (qidx++), q;
18347        opts = opts || {};
18348
18349        if (qidx % YAHOO.util.Get.PURGE_THRESH === 0) {
18350            _autoPurge();
18351        }
18352
18353        queues[id] = lang.merge(opts, {
18354            tId: id,
18355            type: type,
18356            url: url,
18357            finished: false,
18358            aborted: false,
18359            nodes: []
18360        });
18361
18362        q = queues[id];
18363        q.win = q.win || window;
18364        q.scope = q.scope || q.win;
18365        q.autopurge = ("autopurge" in q) ? q.autopurge :
18366                      (type === "script") ? true : false;
18367
18368        q.attributes = q.attributes || {};
18369        q.attributes.charset = opts.charset || q.attributes.charset || 'utf-8';
18370
18371        lang.later(0, q, _next, id);
18372
18373        return {
18374            tId: id
18375        };
18376    };
18377
18378    /**
18379     * Detects when a node has been loaded.  In the case of
18380     * script nodes, this does not guarantee that contained
18381     * script is ready to use.
18382     * @method _track
18383     * @param type {string} the type of node to track
18384     * @param n {HTMLElement} the node to track
18385     * @param id {string} the id of the request
18386     * @param url {string} the url that is being loaded
18387     * @param win {Window} the targeted window
18388     * @param qlength the number of remaining items in the queue,
18389     * including this one
18390     * @param trackfn {Function} function to execute when finished
18391     * the default is _next
18392     * @private
18393     */
18394    _track = function(type, n, id, url, win, qlength, trackfn) {
18395        var f = trackfn || _next, rs, q, a, freq, w, l, i, msg;
18396
18397        // IE supports the readystatechange event for script and css nodes
18398        if (ua.ie && ua.ie < 11) {
18399            n.onreadystatechange = function() {
18400                rs = this.readyState;
18401                if ("loaded" === rs || "complete" === rs) {
18402                    YAHOO.log(id + " onload " + url, "info", "Get");
18403                    n.onreadystatechange = null;
18404                    f(id, url);
18405                }
18406            };
18407
18408        // webkit prior to 3.x is problemmatic
18409        } else if (ua.webkit) {
18410
18411            if (type === "script") {
18412
18413                // Safari 3.x supports the load event for script nodes (DOM2)
18414                if (ua.webkit >= 420) {
18415
18416                    n.addEventListener("load", function() {
18417                        YAHOO.log(id + " DOM2 onload " + url, "info", "Get");
18418                        f(id, url);
18419                    });
18420
18421                // Nothing can be done with Safari < 3.x except to pause and hope
18422                // for the best, particularly after last script is inserted. The
18423                // scripts will always execute in the order they arrive, not
18424                // necessarily the order in which they were inserted.  To support
18425                // script nodes with complete reliability in these browsers, script
18426                // nodes either need to invoke a function in the window once they
18427                // are loaded or the implementer needs to provide a well-known
18428                // property that the utility can poll for.
18429                } else {
18430                    // Poll for the existence of the named variable, if it
18431                    // was supplied.
18432                    q = queues[id];
18433                    if (q.varName) {
18434                        freq = YAHOO.util.Get.POLL_FREQ;
18435                        YAHOO.log("Polling for " + q.varName[0]);
18436                        q.maxattempts = YAHOO.util.Get.TIMEOUT/freq;
18437                        q.attempts = 0;
18438                        q._cache = q.varName[0].split(".");
18439                        q.timer = lang.later(freq, q, function(o) {
18440                            a = this._cache;
18441                            l = a.length;
18442                            w = this.win;
18443                            for (i=0; i<l; i=i+1) {
18444                                w = w[a[i]];
18445                                if (!w) {
18446                                    // if we have exausted our attempts, give up
18447                                    this.attempts++;
18448                                    if (this.attempts++ > this.maxattempts) {
18449                                        msg = "Over retry limit, giving up";
18450                                        q.timer.cancel();
18451                                        _fail(id, msg);
18452                                    } else {
18453                                        YAHOO.log(a[i] + " failed, retrying");
18454                                    }
18455                                    return;
18456                                }
18457                            }
18458
18459                            YAHOO.log("Safari poll complete");
18460
18461                            q.timer.cancel();
18462                            f(id, url);
18463
18464                        }, null, true);
18465                    } else {
18466                        lang.later(YAHOO.util.Get.POLL_FREQ, null, f, [id, url]);
18467                    }
18468                }
18469            }
18470
18471        // FireFox and Opera support onload (but not DOM2 in FF) handlers for
18472        // script nodes.  Opera, but not FF, supports the onload event for link
18473        // nodes.
18474        } else {
18475            n.onload = function() {
18476                YAHOO.log(id + " onload " + url, "info", "Get");
18477                f(id, url);
18478            };
18479        }
18480    };
18481
18482    /*
18483     * The request failed, execute fail handler with whatever
18484     * was accomplished.  There isn't a failure case at the
18485     * moment unless you count aborted transactions
18486     * @method _fail
18487     * @param id {string} the id of the request
18488     * @private
18489     */
18490    _fail = function(id, msg) {
18491        YAHOO.log("get failure: " + msg, "warn", "Get");
18492        var q = queues[id], context;
18493        // execute failure callback
18494        if (q.onFailure) {
18495            context = q.scope || q.win;
18496            q.onFailure.call(context, _returnData(q, msg));
18497        }
18498    };
18499
18500    /**
18501     * Removes the nodes for the specified queue
18502     * @method _purge
18503     * @private
18504     */
18505    _purge = function(tId) {
18506        if (queues[tId]) {
18507
18508            var q     = queues[tId],
18509                nodes = q.nodes,
18510                l     = nodes.length,
18511                d     = q.win.document,
18512                h     = d.getElementsByTagName("head")[0],
18513                sib, i, node, attr;
18514
18515            if (q.insertBefore) {
18516                sib = _get(q.insertBefore, tId);
18517                if (sib) {
18518                    h = sib.parentNode;
18519                }
18520            }
18521
18522            for (i=0; i<l; i=i+1) {
18523                node = nodes[i];
18524                if (node.clearAttributes) {
18525                    node.clearAttributes();
18526                } else {
18527                    for (attr in node) {
18528                        if (node.hasOwnProperty(attr)) {
18529                            delete node[attr];
18530                        }
18531                    }
18532                }
18533
18534                h.removeChild(node);
18535            }
18536
18537            q.nodes = [];
18538        }
18539    };
18540
18541
18542    return {
18543
18544        /**
18545         * The default poll freqency in ms, when needed
18546         * @property POLL_FREQ
18547         * @static
18548         * @type int
18549         * @default 10
18550         */
18551        POLL_FREQ: 10,
18552
18553        /**
18554         * The number of request required before an automatic purge.
18555         * property PURGE_THRESH
18556         * @static
18557         * @type int
18558         * @default 20
18559         */
18560        PURGE_THRESH: 20,
18561
18562        /**
18563         * The length time to poll for varName when loading a script in
18564         * Safari 2.x before the transaction fails.
18565         * property TIMEOUT
18566         * @static
18567         * @type int
18568         * @default 2000
18569         */
18570        TIMEOUT: 2000,
18571
18572        /**
18573         * Called by the the helper for detecting script load in Safari
18574         * @method _finalize
18575         * @param id {string} the transaction id
18576         * @private
18577         */
18578        _finalize: function(id) {
18579            YAHOO.log(id + " finalized ", "info", "Get");
18580            lang.later(0, null, _finish, id);
18581        },
18582
18583        /**
18584         * Abort a transaction
18585         * @method abort
18586         * @param {string|object} either the tId or the object returned from
18587         * script() or css()
18588         */
18589        abort: function(o) {
18590            var id = (lang.isString(o)) ? o : o.tId,
18591                q = queues[id];
18592            if (q) {
18593                YAHOO.log("Aborting " + id, "info", "Get");
18594                q.aborted = true;
18595            }
18596        },
18597
18598        /**
18599         * Fetches and inserts one or more script nodes into the head
18600         * of the current document or the document in a specified window.
18601         *
18602         * @method script
18603         * @static
18604         * @param url {string|string[]} the url or urls to the script(s)
18605         * @param opts {object} Options:
18606         * <dl>
18607         * <dt>onSuccess</dt>
18608         * <dd>
18609         * callback to execute when the script(s) are finished loading
18610         * The callback receives an object back with the following
18611         * data:
18612         * <dl>
18613         * <dt>win</dt>
18614         * <dd>the window the script(s) were inserted into</dd>
18615         * <dt>data</dt>
18616         * <dd>the data object passed in when the request was made</dd>
18617         * <dt>nodes</dt>
18618         * <dd>An array containing references to the nodes that were
18619         * inserted</dd>
18620         * <dt>purge</dt>
18621         * <dd>A function that, when executed, will remove the nodes
18622         * that were inserted</dd>
18623         * <dt>
18624         * </dl>
18625         * </dd>
18626         * <dt>onFailure</dt>
18627         * <dd>
18628         * callback to execute when the script load operation fails
18629         * The callback receives an object back with the following
18630         * data:
18631         * <dl>
18632         * <dt>win</dt>
18633         * <dd>the window the script(s) were inserted into</dd>
18634         * <dt>data</dt>
18635         * <dd>the data object passed in when the request was made</dd>
18636         * <dt>nodes</dt>
18637         * <dd>An array containing references to the nodes that were
18638         * inserted successfully</dd>
18639         * <dt>purge</dt>
18640         * <dd>A function that, when executed, will remove any nodes
18641         * that were inserted</dd>
18642         * <dt>
18643         * </dl>
18644         * </dd>
18645         * <dt>onTimeout</dt>
18646         * <dd>
18647         * callback to execute when a timeout occurs.
18648         * The callback receives an object back with the following
18649         * data:
18650         * <dl>
18651         * <dt>win</dt>
18652         * <dd>the window the script(s) were inserted into</dd>
18653         * <dt>data</dt>
18654         * <dd>the data object passed in when the request was made</dd>
18655         * <dt>nodes</dt>
18656         * <dd>An array containing references to the nodes that were
18657         * inserted</dd>
18658         * <dt>purge</dt>
18659         * <dd>A function that, when executed, will remove the nodes
18660         * that were inserted</dd>
18661         * <dt>
18662         * </dl>
18663         * </dd>
18664         * <dt>scope</dt>
18665         * <dd>the execution context for the callbacks</dd>
18666         * <dt>win</dt>
18667         * <dd>a window other than the one the utility occupies</dd>
18668         * <dt>autopurge</dt>
18669         * <dd>
18670         * setting to true will let the utilities cleanup routine purge
18671         * the script once loaded
18672         * </dd>
18673         * <dt>data</dt>
18674         * <dd>
18675         * data that is supplied to the callback when the script(s) are
18676         * loaded.
18677         * </dd>
18678         * <dt>varName</dt>
18679         * <dd>
18680         * variable that should be available when a script is finished
18681         * loading.  Used to help Safari 2.x and below with script load
18682         * detection.  The type of this property should match what was
18683         * passed into the url parameter: if loading a single url, a
18684         * string can be supplied.  If loading multiple scripts, you
18685         * must supply an array that contains the variable name for
18686         * each script.
18687         * </dd>
18688         * <dt>insertBefore</dt>
18689         * <dd>node or node id that will become the new node's nextSibling</dd>
18690         * </dl>
18691         * <dt>charset</dt>
18692         * <dd>Node charset, deprecated, use 'attributes'</dd>
18693         * <dt>attributes</dt>
18694         * <dd>A hash of attributes to apply to dynamic nodes.</dd>
18695         * <dt>timeout</dt>
18696         * <dd>Number of milliseconds to wait before aborting and firing the timeout event</dd>
18697         * <pre>
18698         * // assumes yahoo, dom, and event are already on the page
18699         * &nbsp;&nbsp;YAHOO.util.Get.script(
18700         * &nbsp;&nbsp;["http://yui.yahooapis.com/2.7.0/build/dragdrop/dragdrop-min.js",
18701         * &nbsp;&nbsp;&nbsp;"http://yui.yahooapis.com/2.7.0/build/animation/animation-min.js"], &#123;
18702         * &nbsp;&nbsp;&nbsp;&nbsp;onSuccess: function(o) &#123;
18703         * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;YAHOO.log(o.data); // foo
18704         * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new YAHOO.util.DDProxy("dd1"); // also new o.reference("dd1"); would work
18705         * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.log("won't cause error because YAHOO is the scope");
18706         * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.log(o.nodes.length === 2) // true
18707         * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// o.purge(); // optionally remove the script nodes immediately
18708         * &nbsp;&nbsp;&nbsp;&nbsp;&#125;,
18709         * &nbsp;&nbsp;&nbsp;&nbsp;onFailure: function(o) &#123;
18710         * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;YAHOO.log("transaction failed");
18711         * &nbsp;&nbsp;&nbsp;&nbsp;&#125;,
18712         * &nbsp;&nbsp;&nbsp;&nbsp;data: "foo",
18713         * &nbsp;&nbsp;&nbsp;&nbsp;timeout: 10000, // 10 second timeout
18714         * &nbsp;&nbsp;&nbsp;&nbsp;scope: YAHOO,
18715         * &nbsp;&nbsp;&nbsp;&nbsp;// win: otherframe // target another window/frame
18716         * &nbsp;&nbsp;&nbsp;&nbsp;autopurge: true // allow the utility to choose when to remove the nodes
18717         * &nbsp;&nbsp;&#125;);
18718         * </pre>
18719         * @return {tId: string}
18719 an object containing info about the transaction
18720         */
18721        script: function(url, opts) { return _queue("script", url, opts); },
18722
18723        /**
18724         * Fetches and inserts one or more css link nodes into the
18725         * head of the current document or the document in a specified
18726         * window.
18727         * @method css
18728         * @static
18729         * @param url {string} the url or urls to the css file(s)
18730         * @param opts Options:
18731         * <dl>
18732         * <dt>onSuccess</dt>
18733         * <dd>
18734         * callback to execute when the css file(s) are finished loading
18735         * The callback receives an object back with the following
18736         * data:
18737         * <dl>win</dl>
18738         * <dd>the window the link nodes(s) were inserted into</dd>
18739         * <dt>data</dt>
18740         * <dd>the data object passed in when the request was made</dd>
18741         * <dt>nodes</dt>
18742         * <dd>An array containing references to the nodes that were
18743         * inserted</dd>
18744         * <dt>purge</dt>
18745         * <dd>A function that, when executed, will remove the nodes
18746         * that were inserted</dd>
18747         * <dt>
18748         * </dl>
18749         * </dd>
18750         * <dt>scope</dt>
18751         * <dd>the execution context for the callbacks</dd>
18752         * <dt>win</dt>
18753         * <dd>a window other than the one the utility occupies</dd>
18754         * <dt>data</dt>
18755         * <dd>
18756         * data that is supplied to the callbacks when the nodes(s) are
18757         * loaded.
18758         * </dd>
18759         * <dt>insertBefore</dt>
18760         * <dd>node or node id that will become the new node's nextSibling</dd>
18761         * <dt>charset</dt>
18762         * <dd>Node charset, deprecated, use 'attributes'</dd>
18763         * <dt>attributes</dt>
18764         * <dd>A hash of attributes to apply to dynamic nodes.</dd>
18765         * </dl>
18766         * <pre>
18767         *      YAHOO.util.Get.css("http://yui.yahooapis.com/2.7.0/build/menu/assets/skins/sam/menu.css");
18768         * </pre>
18769         * <pre>
18770         *      YAHOO.util.Get.css(["http://yui.yahooapis.com/2.7.0/build/menu/assets/skins/sam/menu.css",
18771         *                          "http://yui.yahooapis.com/2.7.0/build/logger/assets/skins/sam/logger.css"]);
18772         * </pre>
18773         * @return {tId: string}
18773 an object containing info about the transaction
18774         */
18775        css: function(url, opts) {
18776            return _queue("css", url, opts);
18777        }
18778    };
18779}();
18780
18781YAHOO.register("get", YAHOO.util.Get, {version: "2.9.0", build: "2800"});
18782/*jslint evil: true, strict: false, regexp: false*/
18783
18784/**
18785 * Provides dynamic loading for the YUI library.  It includes the dependency
18786 * info for the library, and will automatically pull in dependencies for
18787 * the modules requested.  It supports rollup files (such as utilities.js
18788 * and yahoo-dom-event.js), and will automatically use these when
18789 * appropriate in order to minimize the number of http connections
18790 * required to load all of the dependencies.
18791 *
18792 * @module yuiloader
18793 * @namespace YAHOO.util
18794 */
18795
18796/**
18797 * YUILoader provides dynamic loading for YUI.
18798 * @class YAHOO.util.YUILoader
18799 * @todo
18800 *      version management, automatic sandboxing
18801 */
18802(function() {
18803
18804    var Y = YAHOO,
18805        util = Y.util,
18806        lang = Y.lang,
18807        env = Y.env,
18808        PROV = "_provides",
18809        SUPER = "_supersedes",
18810        REQ = "expanded",
18811        AFTER = "_after",
18812        VERSION = "2.9.0";
18813
18814    // version hack for cdn testing
18815    // if (/VERSION/.test(VERSION)) {
18816        // VERSION = "2.8.2";
18817    // }
18818
18819    var YUI = {
18820
18821        dupsAllowed: {'yahoo': true, 'get': true},
18822
18823        /*
18824         * The library metadata for the current release  The is the default
18825         * value for YAHOO.util.YUILoader.moduleInfo
18826         * @property YUIInfo
18827         * @static
18828         */
18829        info: {
18830
18831    // 'root': '2.5.2/build/',
18832    // 'base': 'http://yui.yahooapis.com/2.5.2/build/',
18833
18834    'root': VERSION + '/build/',
18835    'base': 'http://yui.yahooapis.com/' + VERSION + '/build/',
18836
18837    'comboBase': 'http://yui.yahooapis.com/combo?',
18838
18839    'skin': {
18840        'defaultSkin': 'sam',
18841        'base': 'assets/skins/',
18842        'path': 'skin.css',
18843        'after': ['reset', 'fonts', 'grids', 'base'],
18844        'rollup': 3
18845    },
18846
18847    dupsAllowed: ['yahoo', 'get'],
18848
18849    'moduleInfo': {
18850
18851        'animation': {
18852            'type': 'js',
18853            'path': 'animation/animation-min.js',
18854            'requires': ['dom', 'event']
18855        },
18856
18857        'autocomplete': {
18858            'type': 'js',
18859            'path': 'autocomplete/autocomplete-min.js',
18860            'requires': ['dom', 'event', 'datasource'],
18861            'optional': ['connection', 'animation'],
18862            'skinnable': true
18863        },
18864
18865        'base': {
18866            'type': 'css',
18867            'path': 'base/base-min.css',
18868            'after': ['reset', 'fonts', 'grids']
18869        },
18870
18871        'button': {
18872            'type': 'js',
18873            'path': 'button/button-min.js',
18874            'requires': ['element'],
18875            'optional': ['menu'],
18876            'skinnable': true
18877        },
18878
18879        'calendar': {
18880            'type': 'js',
18881            'path': 'calendar/calendar-min.js',
18882            'requires': ['event', 'dom'],
18883            supersedes: ['datemath'],
18884            'skinnable': true
18885        },
18886
18887        'carousel': {
18888            'type': 'js',
18889            'path': 'carousel/carousel-min.js',
18890            'requires': ['element'],
18891            'optional': ['animation'],
18892            'skinnable': true
18893        },
18894
18895        'charts': {
18896            'type': 'js',
18897            'path': 'charts/charts-min.js',
18898            'requires': ['element', 'json', 'datasource', 'swf']
18899        },
18900
18901        'colorpicker': {
18902            'type': 'js',
18903            'path': 'colorpicker/colorpicker-min.js',
18904            'requires': ['slider', 'element'],
18905            'optional': ['animation'],
18906            'skinnable': true
18907        },
18908
18909        'connection': {
18910            'type': 'js',
18911            'path': 'connection/connection-min.js',
18912            'requires': ['event'],
18913            'supersedes': ['connectioncore']
18914        },
18915
18916        'connectioncore': {
18917            'type': 'js',
18918            'path': 'connection/connection_core-min.js',
18919            'requires': ['event'],
18920            'pkg': 'connection'
18921        },
18922
18923        'container': {
18924            'type': 'js',
18925            'path': 'container/container-min.js',
18926            'requires': ['dom', 'event'],
18927            // button is also optional, but this creates a circular
18928            // dependency when loadOptional is specified.  button
18929            // optionally includes menu, menu requires container.
18930            'optional': ['dragdrop', 'animation', 'connection'],
18931            'supersedes': ['containercore'],
18932            'skinnable': true
18933        },
18934
18935        'containercore': {
18936            'type': 'js',
18937            'path': 'container/container_core-min.js',
18938            'requires': ['dom', 'event'],
18939            'pkg': 'container'
18940        },
18941
18942        'cookie': {
18943            'type': 'js',
18944            'path': 'cookie/cookie-min.js',
18945            'requires': ['yahoo']
18946        },
18947
18948        'datasource': {
18949            'type': 'js',
18950            'path': 'datasource/datasource-min.js',
18951            'requires': ['event'],
18952            'optional': ['connection']
18953        },
18954
18955        'datatable': {
18956            'type': 'js',
18957            'path': 'datatable/datatable-min.js',
18958            'requires': ['element', 'datasource'],
18959            'optional': ['calendar', 'dragdrop', 'paginator'],
18960            'skinnable': true
18961        },
18962
18963        datemath: {
18964            'type': 'js',
18965            'path': 'datemath/datemath-min.js',
18966            'requires': ['yahoo']
18967        },
18968
18969        'dom': {
18970            'type': 'js',
18971            'path': 'dom/dom-min.js',
18972            'requires': ['yahoo']
18973        },
18974
18975        'dragdrop': {
18976            'type': 'js',
18977            'path': 'dragdrop/dragdrop-min.js',
18978            'requires': ['dom', 'event']
18979        },
18980
18981        'editor': {
18982            'type': 'js',
18983            'path': 'editor/editor-min.js',
18984            'requires': ['menu', 'element', 'button'],
18985            'optional': ['animation', 'dragdrop'],
18986            'supersedes': ['simpleeditor'],
18987            'skinnable': true
18988        },
18989
18990        'element': {
18991            'type': 'js',
18992            'path': 'element/element-min.js',
18993            'requires': ['dom', 'event'],
18994            'optional': ['event-mouseenter', 'event-delegate']
18995        },
18996
18997        'element-delegate': {
18998            'type': 'js',
18999            'path': 'element-delegate/element-delegate-min.js',
19000            'requires': ['element']
19001        },
19002
19003        'event': {
19004            'type': 'js',
19005            'path': 'event/event-min.js',
19006            'requires': ['yahoo']
19007        },
19008
19009        'event-simulate': {
19010            'type': 'js',
19011            'path': 'event-simulate/event-simulate-min.js',
19012            'requires': ['event']
19013        },
19014
19015        'event-delegate': {
19016            'type': 'js',
19017            'path': 'event-delegate/event-delegate-min.js',
19018            'requires': ['event'],
19019            'optional': ['selector']
19020        },
19021
19022        'event-mouseenter': {
19023            'type': 'js',
19024            'path': 'event-mouseenter/event-mouseenter-min.js',
19025            'requires': ['dom', 'event']
19026        },
19027
19028        'fonts': {
19029            'type': 'css',
19030            'path': 'fonts/fonts-min.css'
19031        },
19032
19033        'get': {
19034            'type': 'js',
19035            'path': 'get/get-min.js',
19036            'requires': ['yahoo']
19037        },
19038
19039        'grids': {
19040            'type': 'css',
19041            'path': 'grids/grids-min.css',
19042            'requires': ['fonts'],
19043            'optional': ['reset']
19044        },
19045
19046        'history': {
19047            'type': 'js',
19048            'path': 'history/history-min.js',
19049            'requires': ['event']
19050        },
19051
19052         'imagecropper': {
19053             'type': 'js',
19054             'path': 'imagecropper/imagecropper-min.js',
19055             'requires': ['dragdrop', 'element', 'resize'],
19056             'skinnable': true
19057         },
19058
19059         'imageloader': {
19060            'type': 'js',
19061            'path': 'imageloader/imageloader-min.js',
19062            'requires': ['event', 'dom']
19063         },
19064
19065         'json': {
19066            'type': 'js',
19067            'path': 'json/json-min.js',
19068            'requires': ['yahoo']
19069         },
19070
19071         'layout': {
19072             'type': 'js',
19073             'path': 'layout/layout-min.js',
19074             'requires': ['element'],
19075             'optional': ['animation', 'dragdrop', 'resize', 'selector'],
19076             'skinnable': true
19077         },
19078
19079        'logger': {
19080            'type': 'js',
19081            'path': 'logger/logger-min.js',
19082            'requires': ['event', 'dom'],
19083            'optional': ['dragdrop'],
19084            'skinnable': true
19085        },
19086
19087        'menu': {
19088            'type': 'js',
19089            'path': 'menu/menu-min.js',
19090            'requires': ['containercore'],
19091            'skinnable': true
19092        },
19093
19094        'paginator': {
19095            'type': 'js',
19096            'path': 'paginator/paginator-min.js',
19097            'requires': ['element'],
19098            'skinnable': true
19099        },
19100
19101        'profiler': {
19102            'type': 'js',
19103            'path': 'profiler/profiler-min.js',
19104            'requires': ['yahoo']
19105        },
19106
19107
19108        'profilerviewer': {
19109            'type': 'js',
19110            'path': 'profilerviewer/profilerviewer-min.js',
19111            'requires': ['profiler', 'yuiloader', 'element'],
19112            'skinnable': true
19113        },
19114
19115        'progressbar': {
19116            'type': 'js',
19117            'path': 'progressbar/progressbar-min.js',
19118            'requires': ['element'],
19119            'optional': ['animation'],
19120            'skinnable': true
19121        },
19122
19123        'reset': {
19124            'type': 'css',
19125            'path': 'reset/reset-min.css'
19126        },
19127
19128        'reset-fonts-grids': {
19129            'type': 'css',
19130            'path': 'reset-fonts-grids/reset-fonts-grids.css',
19131            'supersedes': ['reset', 'fonts', 'grids', 'reset-fonts'],
19132            'rollup': 4
19133        },
19134
19135        'reset-fonts': {
19136            'type': 'css',
19137            'path': 'reset-fonts/reset-fonts.css',
19138            'supersedes': ['reset', 'fonts'],
19139            'rollup': 2
19140        },
19141
19142         'resize': {
19143             'type': 'js',
19144             'path': 'resize/resize-min.js',
19145             'requires': ['dragdrop', 'element'],
19146             'optional': ['animation'],
19147             'skinnable': true
19148         },
19149
19150        'selector': {
19151            'type': 'js',
19152            'path': 'selector/selector-min.js',
19153            'requires': ['yahoo', 'dom']
19154        },
19155
19156        'simpleeditor': {
19157            'type': 'js',
19158            'path': 'editor/simpleeditor-min.js',
19159            'requires': ['element'],
19160            'optional': ['containercore', 'menu', 'button', 'animation', 'dragdrop'],
19161            'skinnable': true,
19162            'pkg': 'editor'
19163        },
19164
19165        'slider': {
19166            'type': 'js',
19167            'path': 'slider/slider-min.js',
19168            'requires': ['dragdrop'],
19169            'optional': ['animation'],
19170            'skinnable': true
19171        },
19172
19173        'storage': {
19174            'type': 'js',
19175            'path': 'storage/storage-min.js',
19176            'requires': ['yahoo', 'event', 'cookie'],
19177            'optional': ['swfstore']
19178        },
19179
19180         'stylesheet': {
19181            'type': 'js',
19182            'path': 'stylesheet/stylesheet-min.js',
19183            'requires': ['yahoo']
19184         },
19185
19186        'swf': {
19187            'type': 'js',
19188            'path': 'swf/swf-min.js',
19189            'requires': ['element'],
19190            'supersedes': ['swfdetect']
19191        },
19192
19193        'swfdetect': {
19194            'type': 'js',
19195            'path': 'swfdetect/swfdetect-min.js',
19196            'requires': ['yahoo']
19197        },
19198
19199        'swfstore': {
19200            'type': 'js',
19201            'path': 'swfstore/swfstore-min.js',
19202            'requires': ['element', 'cookie', 'swf']
19203        },
19204
19205        'tabview': {
19206            'type': 'js',
19207            'path': 'tabview/tabview-min.js',
19208            'requires': ['element'],
19209            'optional': ['connection'],
19210            'skinnable': true
19211        },
19212
19213        'treeview': {
19214            'type': 'js',
19215            'path': 'treeview/treeview-min.js',
19216            'requires': ['event', 'dom'],
19217            'optional': ['json', 'animation', 'calendar'],
19218            'skinnable': true
19219        },
19220
19221        'uploader': {
19222            'type': 'js',
19223            'path': 'uploader/uploader-min.js',
19224            'requires': ['element']
19225        },
19226
19227        'utilities': {
19228            'type': 'js',
19229            'path': 'utilities/utilities.js',
19230            'supersedes': ['yahoo', 'event', 'dragdrop', 'animation', 'dom', 'connection', 'element', 'yahoo-dom-event', 'get', 'yuiloader', 'yuiloader-dom-event'],
19231            'rollup': 8
19232        },
19233
19234        'yahoo': {
19235            'type': 'js',
19236            'path': 'yahoo/yahoo-min.js'
19237        },
19238
19239        'yahoo-dom-event': {
19240            'type': 'js',
19241            'path': 'yahoo-dom-event/yahoo-dom-event.js',
19242            'supersedes': ['yahoo', 'event', 'dom'],
19243            'rollup': 3
19244        },
19245
19246        'yuiloader': {
19247            'type': 'js',
19248            'path': 'yuiloader/yuiloader-min.js',
19249            'supersedes': ['yahoo', 'get']
19250        },
19251
19252        'yuiloader-dom-event': {
19253            'type': 'js',
19254            'path': 'yuiloader-dom-event/yuiloader-dom-event.js',
19255            'supersedes': ['yahoo', 'dom', 'event', 'get', 'yuiloader', 'yahoo-dom-event'],
19256            'rollup': 5
19257        },
19258
19259        'yuitest': {
19260            'type': 'js',
19261            'path': 'yuitest/yuitest-min.js',
19262            'requires': ['logger'],
19263            'optional': ['event-simulate'],
19264            'skinnable': true
19265        }
19266    }
19267},
19268        ObjectUtil: {
19269            appendArray: function(o, a) {
19270                if (a) {
19271                    for (var i=0; i<a.length; i=i+1) {
19272                        o[a[i]] = true;
19273                    }
19274                }
19275            },
19276
19277            keys: function(o, ordered) {
19278                var a=[], i;
19279                for (i in o) {
19280                    if (lang.hasOwnProperty(o, i)) {
19281                        a.push(i);
19282                    }
19283                }
19284
19285                return a;
19286            }
19287        },
19288
19289        ArrayUtil: {
19290
19291            appendArray: function(a1, a2) {
19292                Array.prototype.push.apply(a1, a2);
19293                /*
19294                for (var i=0; i<a2.length; i=i+1) {
19295                    a1.push(a2[i]);
19296                }
19297                */
19298            },
19299
19300            indexOf: function(a, val) {
19301                for (var i=0; i<a.length; i=i+1) {
19302                    if (a[i] === val) {
19303                        return i;
19304                    }
19305                }
19306
19307                return -1;
19308            },
19309
19310            toObject: function(a) {
19311                var o = {};
19312                for (var i=0; i<a.length; i=i+1) {
19313                    o[a[i]] = true;
19314                }
19315
19316                return o;
19317            },
19318
19319            /*
19320             * Returns a unique array.  Does not maintain order, which is fine
19321             * for this application, and performs better than it would if it
19322             * did.
19323             */
19324            uniq: function(a) {
19325                return YUI.ObjectUtil.keys(YUI.ArrayUtil.toObject(a));
19326            }
19327        }
19328    };
19329
19330    YAHOO.util.YUILoader = function(o) {
19331
19332        /**
19333         * Internal callback to handle multiple internal insert() calls
19334         * so that css is inserted prior to js
19335         * @property _internalCallback
19336         * @private
19337         */
19338        this._internalCallback = null;
19339
19340        /**
19341         * Use the YAHOO environment listener to detect script load.  This
19342         * is only switched on for Safari 2.x and below.
19343         * @property _useYahooListener
19344         * @private
19345         */
19346        this._useYahooListener = false;
19347
19348        /**
19349         * Callback that will be executed when the loader is finished
19350         * with an insert
19351         * @method onSuccess
19352         * @type function
19353         */
19354        this.onSuccess = null;
19355
19356        /**
19357         * Callback that will be executed if there is a failure
19358         * @method onFailure
19359         * @type function
19360         */
19361        this.onFailure = Y.log;
19362
19363        /**
19364         * Callback that will be executed each time a new module is loaded
19365         * @method onProgress
19366         * @type function
19367         */
19368        this.onProgress = null;
19369
19370        /**
19371         * Callback that will be executed if a timeout occurs
19372         * @method onTimeout
19373         * @type function
19374         */
19375        this.onTimeout = null;
19376
19377        /**
19378         * The execution scope for all callbacks
19379         * @property scope
19380         * @default this
19381         */
19382        this.scope = this;
19383
19384        /**
19385         * Data that is passed to all callbacks
19386         * @property data
19387         */
19388        this.data = null;
19389
19390        /**
19391         * Node reference or id where new nodes should be inserted before
19392         * @property insertBefore
19393         * @type string|HTMLElement
19394         */
19395        this.insertBefore = null;
19396
19397        /**
19398         * The charset attribute for inserted nodes
19399         * @property charset
19400         * @type string
19401         * @default utf-8
19402         */
19403        this.charset = null;
19404
19405        /**
19406         * The name of the variable in a sandbox or script node
19407         * (for external script support in Safari 2.x and earlier)
19408         * to reference when the load is complete.  If this variable
19409         * is not available in the specified scripts, the operation will
19410         * fail.
19411         * @property varName
19412         * @type string
19413         */
19414        this.varName = null;
19415
19416        /**
19417         * The base directory.
19418         * @property base
19419         * @type string
19420         * @default http://yui.yahooapis.com/[YUI VERSION]/build/
19421         */
19422        this.base = YUI.info.base;
19423
19424        /**
19425         * Base path for the combo service
19426         * @property comboBase
19427         * @type string
19428         * @default http://yui.yahooapis.com/combo?
19429         */
19430        this.comboBase = YUI.info.comboBase;
19431
19432        /**
19433         * If configured, YUI will use the the combo handler on the
19434         * Yahoo! CDN to pontentially reduce the number of http requests
19435         * required.
19436         * @property combine
19437         * @type boolean
19438         * @default false
19439         */
19440        // this.combine = (o && !('base' in o));
19441        this.combine = false;
19442
19443
19444        /**
19445         * Root path to prepend to module path for the combo
19446         * service
19447         * @property root
19448         * @type string
19449         * @default [YUI VERSION]/build/
19450         */
19451        this.root = YUI.info.root;
19452
19453        /**
19454         * Timeout value in milliseconds.  If set, this value will be used by
19455         * the get utility.  the timeout event will fire if
19456         * a timeout occurs.
19457         * @property timeout
19458         * @type int
19459         */
19460        this.timeout = 0;
19461
19462        /**
19463         * A list of modules that should not be loaded, even if
19464         * they turn up in the dependency tree
19465         * @property ignore
19466         * @type string[]
19467         */
19468        this.ignore = null;
19469
19470        /**
19471         * A list of modules that should always be loaded, even
19472         * if they have already been inserted into the page.
19473         * @property force
19474         * @type string[]
19475         */
19476        this.force = null;
19477
19478        /**
19479         * Should we allow rollups
19480         * @property allowRollup
19481         * @type boolean
19482         * @default true
19483         */
19484        this.allowRollup = true;
19485
19486        /**
19487         * A filter to apply to result urls.  This filter will modify the default
19488         * path for all modules.  The default path for the YUI library is the
19489         * minified version of the files (e.g., event-min.js).  The filter property
19490         * can be a predefined filter or a custom filter.  The valid predefined
19491         * filters are:
19492         * <dl>
19493         *  <dt>DEBUG</dt>
19494         *  <dd>Selects the debug versions of the library (e.g., event-debug.js).
19495         *      This option will automatically include the logger widget</dd>
19496         *  <dt>RAW</dt>
19497         *  <dd>Selects the non-minified version of the library (e.g., event.js).
19498         * </dl>
19499         * You can also define a custom filter, which must be an object literal
19500         * containing a search expression and a replace string:
19501         * <pre>
19502         *  myFilter: &#123;
19503         *      'searchExp': "-min\\.js",
19504         *      'replaceStr': "-debug.js"
19505         *  &#125;
19506         * </pre>
19507         * @property filter
19508         * @type string|{searchExp: string, replaceStr: string}
19509         */
19510        this.filter = null;
19511
19512        /**
19513         * The list of requested modules
19514         * @property required
19515         * @type {string: boolean}
19516         */
19517        this.required = {};
19518
19519        /**
19520         * The library metadata
19521         * @property moduleInfo
19522         */
19523        this.moduleInfo = lang.merge(YUI.info.moduleInfo);
19524
19525        /**
19526         * List of rollup files found in the library metadata
19527         * @property rollups
19528         */
19529        this.rollups = null;
19530
19531        /**
19532         * Whether or not to load optional dependencies for
19533         * the requested modules
19534         * @property loadOptional
19535         * @type boolean
19536         * @default false
19537         */
19538        this.loadOptional = false;
19539
19540        /**
19541         * All of the derived dependencies in sorted order, which
19542         * will be populated when either calculate() or insert()
19543         * is called
19544         * @property sorted
19545         * @type string[]
19546         */
19547        this.sorted = [];
19548
19549        /**
19550         * Set when beginning to compute the dependency tree.
19551         * Composed of what YAHOO reports to be loaded combined
19552         * with what has been loaded by the tool
19553         * @propery loaded
19554         * @type {string: boolean}
19555         */
19556        this.loaded = {};
19557
19558        /**
19559         * Flag to indicate the dependency tree needs to be recomputed
19560         * if insert is called again.
19561         * @property dirty
19562         * @type boolean
19563         * @default true
19564         */
19565        this.dirty = true;
19566
19567        /**
19568         * List of modules inserted by the utility
19569         * @property inserted
19570         * @type {string: boolean}
19571         */
19572        this.inserted = {};
19573
19574        /**
19575         * Provides the information used to skin the skinnable components.
19576         * The following skin definition would result in 'skin1' and 'skin2'
19577         * being loaded for calendar (if calendar was requested), and
19578         * 'sam' for all other skinnable components:
19579         *
19580         *   <code>
19581         *   skin: {
19582         *
19583         *      // The default skin, which is automatically applied if not
19584         *      // overriden by a component-specific skin definition.
19585         *      // Change this in to apply a different skin globally
19586         *      defaultSkin: 'sam',
19587         *
19588         *      // This is combined with the loader base property to get
19589         *      // the default root directory for a skin. ex:
19590         *      // http://yui.yahooapis.com/2.3.0/build/assets/skins/sam/
19591         *      base: 'assets/skins/',
19592         *
19593         *      // The name of the rollup css file for the skin
19594         *      path: 'skin.css',
19595         *
19596         *      // The number of skinnable components requested that are
19597         *      // required before using the rollup file rather than the
19598         *      // individual component css files
19599         *      rollup: 3,
19600         *
19601         *      // Any component-specific overrides can be specified here,
19602         *      // making it possible to load different skins for different
19603         *      // components.  It is possible to load more than one skin
19604         *      // for a given component as well.
19605         *      overrides: {
19606         *          calendar: ['skin1', 'skin2']
19607         *      }
19608         *   }
19609         *   </code>
19610         *   @property skin
19611         */
19612
19613        var self = this;
19614
19615        env.listeners.push(function(m) {
19616            if (self._useYahooListener) {
19617                //Y.log("YAHOO listener: " + m.name);
19618                self.loadNext(m.name);
19619            }
19620        });
19621
19622        this.skin = lang.merge(YUI.info.skin);
19623
19624        this._config(o);
19625
19626    };
19627
19628    Y.util.YUILoader.prototype = {
19629
19630        FILTERS: {
19631            RAW: {
19632                'searchExp': "-min\\.js",
19633                'replaceStr': ".js"
19634            },
19635            DEBUG: {
19636                'searchExp': "-min\\.js",
19637                'replaceStr': "-debug.js"
19638            }
19639        },
19640
19641        SKIN_PREFIX: "skin-",
19642
19643        _config: function(o) {
19644
19645            // apply config values
19646            if (o) {
19647                for (var i in o) {
19648                    if (lang.hasOwnProperty(o, i)) {
19649                        if (i == "require") {
19650                            this.require(o[i]);
19651                        } else {
19652                            this[i] = o[i];
19653                        }
19654                    }
19655                }
19656            }
19657
19658            // fix filter
19659            var f = this.filter;
19660
19661            if (lang.isString(f)) {
19662                f = f.toUpperCase();
19663
19664                // the logger must be available in order to use the debug
19665                // versions of the library
19666                if (f === "DEBUG") {
19667                    this.require("logger");
19668                }
19669
19670                // hack to handle a a bug where LogWriter is being instantiated
19671                // at load time, and the loader has no way to sort above it
19672                // at the moment.
19673                if (!Y.widget.LogWriter) {
19674                    Y.widget.LogWriter = function() {
19675                        return Y;
19676                    };
19677                }
19678
19679                this.filter = this.FILTERS[f];
19680            }
19681
19682        },
19683
19684        /** Add a new module to the component metadata.
19685         * <dl>
19686         *     <dt>name:</dt>       <dd>required, the component name</dd>
19687         *     <dt>type:</dt>       <dd>required, the component type (js or css)</dd>
19688         *     <dt>path:</dt>       <dd>required, the path to the script from "base"</dd>
19689         *     <dt>requires:</dt>   <dd>array of modules required by this component</dd>
19690         *     <dt>optional:</dt>   <dd>array of optional modules for this component</dd>
19691         *     <dt>supersedes:</dt> <dd>array of the modules this component replaces</dd>
19692         *     <dt>after:</dt>      <dd>array of modules the components which, if present, should be sorted above this one</dd>
19693         *     <dt>rollup:</dt>     <dd>the number of superseded modules required for automatic rollup</dd>
19694         *     <dt>fullpath:</dt>   <dd>If fullpath is specified, this is used instead of the configured base + path</dd>
19695         *     <dt>skinnable:</dt>  <dd>flag to determine if skin assets should automatically be pulled in</dd>
19696         * </dl>
19697         * @method addModule
19698         * @param o An object containing the module data
19699         * @return {boolean} true if the module was added, false if
19700         * the object passed in did not provide all required attributes
19701         */
19702        addModule: function(o) {
19703
19704            if (!o || !o.name || !o.type || (!o.path && !o.fullpath)) {
19705                return false;
19706            }
19707
19708            o.ext = ('ext' in o) ? o.ext : true;
19709            o.requires = o.requires || [];
19710
19711            this.moduleInfo[o.name] = o;
19712            this.dirty = true;
19713
19714            return true;
19715        },
19716
19717        /**
19718         * Add a requirement for one or more module
19719         * @method require
19720         * @param what {string[] | string*} the modules to load
19721         */
19722        require: function(what) {
19723            var a = (typeof what === "string") ? arguments : what;
19724            this.dirty = true;
19725            YUI.ObjectUtil.appendArray(this.required, a);
19726        },
19727
19728        /**
19729         * Adds the skin def to the module info
19730         * @method _addSkin
19731         * @param skin {string} the name of the skin
19732         * @param mod {string} the name of the module
19733         * @return {string} the module name for the skin
19734         * @private
19735         */
19736        _addSkin: function(skin, mod) {
19737
19738            // Add a module definition for the skin rollup css
19739            var name = this.formatSkin(skin), info = this.moduleInfo,
19740                sinf = this.skin, ext = info[mod] && info[mod].ext;
19741
19742            // Y.log('ext? ' + mod + ": " + ext);
19743            if (!info[name]) {
19744                // Y.log('adding skin ' + name);
19745                this.addModule({
19746                    'name': name,
19747                    'type': 'css',
19748                    'path': sinf.base + skin + '/' + sinf.path,
19749                    //'supersedes': '*',
19750                    'after': sinf.after,
19751                    'rollup': sinf.rollup,
19752                    'ext': ext
19753                });
19754            }
19755
19756            // Add a module definition for the module-specific skin css
19757            if (mod) {
19758                name = this.formatSkin(skin, mod);
19759                if (!info[name]) {
19760                    var mdef = info[mod], pkg = mdef.pkg || mod;
19761                    // Y.log('adding skin ' + name);
19762                    this.addModule({
19763                        'name': name,
19764                        'type': 'css',
19765                        'after': sinf.after,
19766                        'path': pkg + '/' + sinf.base + skin + '/' + mod + '.css',
19767                        'ext': ext
19768                    });
19769                }
19770            }
19771
19772            return name;
19773        },
19774
19775        /**
19776         * Returns an object containing properties for all modules required
19777         * in order to load the requested module
19778         * @method getRequires
19779         * @param mod The module definition from moduleInfo
19780         */
19781        getRequires: function(mod) {
19782            if (!mod) {
19783                return [];
19784            }
19785
19786            if (!this.dirty && mod.expanded) {
19787                return mod.expanded;
19788            }
19789
19790            mod.requires=mod.requires || [];
19791            var i, d=[], r=mod.requires, o=mod.optional, info=this.moduleInfo, m;
19792            for (i=0; i<r.length; i=i+1) {
19793                d.push(r[i]);
19794                m = info[r[i]];
19795                YUI.ArrayUtil.appendArray(d, this.getRequires(m));
19796            }
19797
19798            if (o && this.loadOptional) {
19799                for (i=0; i<o.length; i=i+1) {
19800                    d.push(o[i]);
19801                    YUI.ArrayUtil.appendArray(d, this.getRequires(info[o[i]]));
19802                }
19803            }
19804
19805            mod.expanded = YUI.ArrayUtil.uniq(d);
19806
19807            return mod.expanded;
19808        },
19809
19810
19811        /**
19812         * Returns an object literal of the modules the supplied module satisfies
19813         * @method getProvides
19814         * @param name{string} The name of the module
19815         * @param notMe {string} don't add this module name, only include superseded modules
19816         * @return what this module provides
19817         */
19818        getProvides: function(name, notMe) {
19819            var addMe = !(notMe), ckey = (addMe) ? PROV : SUPER,
19820                m = this.moduleInfo[name], o = {};
19821
19822            if (!m) {
19823                return o;
19824            }
19825
19826            if (m[ckey]) {
19827// Y.log('cached: ' + name + ' ' + ckey + ' ' + lang.dump(this.moduleInfo[name][ckey], 0));
19828                return m[ckey];
19829            }
19830
19831            var s = m.supersedes, done={}, me = this;
19832
19833            // use worker to break cycles
19834            var add = function(mm) {
19835                if (!done[mm]) {
19836                    // Y.log(name + ' provides worker trying: ' + mm);
19837                    done[mm] = true;
19838                    // we always want the return value normal behavior
19839                    // (provides) for superseded modules.
19840                    lang.augmentObject(o, me.getProvides(mm));
19841                }
19842
19843                // else {
19844                // Y.log(name + ' provides worker skipping done: ' + mm);
19845                // }
19846            };
19847
19848            // calculate superseded modules
19849            if (s) {
19850                for (var i=0; i<s.length; i=i+1) {
19851                    add(s[i]);
19852                }
19853            }
19854
19855            // supersedes cache
19856            m[SUPER] = o;
19857            // provides cache
19858            m[PROV] = lang.merge(o);
19859            m[PROV][name] = true;
19860
19861// Y.log(name + " supersedes " + lang.dump(m[SUPER], 0));
19862// Y.log(name + " provides " + lang.dump(m[PROV], 0));
19863
19864            return m[ckey];
19865        },
19866
19867
19868        /**
19869         * Calculates the dependency tree, the result is stored in the sorted
19870         * property
19871         * @method calculate
19872         * @param o optional options object
19873         */
19874        calculate: function(o) {
19875            if (o || this.dirty) {
19876                this._config(o);
19877                this._setup();
19878                this._explode();
19879                if (this.allowRollup) {
19880                    this._rollup();
19881                }
19882                this._reduce();
19883                this._sort();
19884
19885                // Y.log("after calculate: " + lang.dump(this.required));
19886
19887                this.dirty = false;
19888            }
19889        },
19890
19891        /**
19892         * Investigates the current YUI configuration on the page.  By default,
19893         * modules already detected will not be loaded again unless a force
19894         * option is encountered.  Called by calculate()
19895         * @method _setup
19896         * @private
19897         */
19898        _setup: function() {
19899
19900            var info = this.moduleInfo, name, i, j;
19901
19902            // Create skin modules
19903            for (name in info) {
19904
19905                if (lang.hasOwnProperty(info, name)) {
19906                    var m = info[name];
19907                    if (m && m.skinnable) {
19908                        // Y.log("skinning: " + name);
19909                        var o=this.skin.overrides, smod;
19910                        if (o && o[name]) {
19911                            for (i=0; i<o[name].length; i=i+1) {
19912                                smod = this._addSkin(o[name][i], name);
19913                            }
19914                        } else {
19915                            smod = this._addSkin(this.skin.defaultSkin, name);
19916                        }
19917
19918                        if (YUI.ArrayUtil.indexOf(m.requires, smod) == -1) {
19919                            m.requires.push(smod);
19920                        }
19921                    }
19922                }
19923
19924            }
19925
19926            var l = lang.merge(this.inserted); // shallow clone
19927
19928            if (!this._sandbox) {
19929                l = lang.merge(l, env.modules);
19930            }
19931
19932            // Y.log("Already loaded stuff: " + lang.dump(l, 0));
19933
19934            // add the ignore list to the list of loaded packages
19935            if (this.ignore) {
19936                YUI.ObjectUtil.appendArray(l, this.ignore);
19937            }
19938
19939            // remove modules on the force list from the loaded list
19940            if (this.force) {
19941                for (i=0; i<this.force.length; i=i+1) {
19942                    if (this.force[i] in l) {
19943                        delete l[this.force[i]];
19944                    }
19945                }
19946            }
19947
19948            // expand the list to include superseded modules
19949            for (j in l) {
19950                // Y.log("expanding: " + j);
19951                if (lang.hasOwnProperty(l, j)) {
19952                    lang.augmentObject(l, this.getProvides(j));
19953                }
19954            }
19955
19956            // Y.log("loaded expanded: " + lang.dump(l, 0));
19957
19958            this.loaded = l;
19959
19960        },
19961
19962
19963        /**
19964         * Inspects the required modules list looking for additional
19965         * dependencies.  Expands the required list to include all
19966         * required modules.  Called by calculate()
19967         * @method _explode
19968         * @private
19969         */
19970        _explode: function() {
19971
19972            var r=this.required, i, mod;
19973
19974            for (i in r) {
19975                if (lang.hasOwnProperty(r, i)) {
19976                    mod = this.moduleInfo[i];
19977                    if (mod) {
19978
19979                        var req = this.getRequires(mod);
19980
19981                        if (req) {
19982                            YUI.ObjectUtil.appendArray(r, req);
19983                        }
19984                    }
19985                }
19986            }
19987        },
19988
19989        /*
19990         * @method _skin
19991         * @private
19992         * @deprecated
19993         */
19994        _skin: function() {
19995        },
19996
19997        /**
19998         * Returns the skin module name for the specified skin name.  If a
19999         * module name is supplied, the returned skin module name is
20000         * specific to the module passed in.
20001         * @method formatSkin
20002         * @param skin {string} the name of the skin
20003         * @param mod {string} optional: the name of a module to skin
20004         * @return {string} the full skin module name
20005         */
20006        formatSkin: function(skin, mod) {
20007            var s = this.SKIN_PREFIX + skin;
20008            if (mod) {
20009                s = s + "-" + mod;
20010            }
20011
20012            return s;
20013        },
20014
20015        /**
20016         * Reverses <code>formatSkin</code>, providing the skin name and
20017         * module name if the string matches the pattern for skins.
20018         * @method parseSkin
20019         * @param mod {string} the module name to parse
20020         * @return {skin: string, module: string} the parsed skin name
20021         * and module name, or null if the supplied string does not match
20022         * the skin pattern
20023         */
20024        parseSkin: function(mod) {
20025
20026            if (mod.indexOf(this.SKIN_PREFIX) === 0) {
20027                var a = mod.split("-");
20028                return {skin: a[1], module: a[2]};
20029            }
20030
20031            return null;
20032        },
20033
20034        /**
20035         * Look for rollup packages to determine if all of the modules a
20036         * rollup supersedes are required.  If so, include the rollup to
20037         * help reduce the total number of connections required.  Called
20038         * by calculate()
20039         * @method _rollup
20040         * @private
20041         */
20042        _rollup: function() {
20043            var i, j, m, s, rollups={}, r=this.required, roll,
20044                info = this.moduleInfo;
20045
20046            // find and cache rollup modules
20047            if (this.dirty || !this.rollups) {
20048                for (i in info) {
20049                    if (lang.hasOwnProperty(info, i)) {
20050                        m = info[i];
20051                        //if (m && m.rollup && m.supersedes) {
20052                        if (m && m.rollup) {
20053                            rollups[i] = m;
20054                        }
20055                    }
20056                }
20057
20058                this.rollups = rollups;
20059            }
20060
20061            // make as many passes as needed to pick up rollup rollups
20062            for (;;) {
20063                var rolled = false;
20064
20065                // go through the rollup candidates
20066                for (i in rollups) {
20067
20068                    // there can be only one
20069                    if (!r[i] && !this.loaded[i]) {
20070                        m =info[i]; s = m.supersedes; roll=false;
20071
20072                        if (!m.rollup) {
20073                            continue;
20074                        }
20075
20076                        var skin = (m.ext) ? false : this.parseSkin(i), c = 0;
20077
20078                        // Y.log('skin? ' + i + ": " + skin);
20079                        if (skin) {
20080                            for (j in r) {
20081                                if (lang.hasOwnProperty(r, j)) {
20082                                    if (i !== j && this.parseSkin(j)) {
20083                                        c++;
20084                                        roll = (c >= m.rollup);
20085                                        if (roll) {
20086                                            // Y.log("skin rollup " + lang.dump(r));
20087                                            break;
20088                                        }
20089                                    }
20090                                }
20091                            }
20092
20093                        } else {
20094
20095                            // check the threshold
20096                            for (j=0;j<s.length;j=j+1) {
20097
20098                                // if the superseded module is loaded, we can't load the rollup
20099                                if (this.loaded[s[j]] && (!YUI.dupsAllowed[s[j]])) {
20100                                    roll = false;
20101                                    break;
20102                                // increment the counter if this module is required.  if we are
20103                                // beyond the rollup threshold, we will use the rollup module
20104                                } else if (r[s[j]]) {
20105                                    c++;
20106                                    roll = (c >= m.rollup);
20107                                    if (roll) {
20108                                        // Y.log("over thresh " + c + ", " + lang.dump(r));
20109                                        break;
20110                                    }
20111                                }
20112                            }
20113                        }
20114
20115                        if (roll) {
20116                            // Y.log("rollup: " +  i + ", " + lang.dump(this, 1));
20117                            // add the rollup
20118                            r[i] = true;
20119                            rolled = true;
20120
20121                            // expand the rollup's dependencies
20122                            this.getRequires(m);
20123                        }
20124                    }
20125                }
20126
20127                // if we made it here w/o rolling up something, we are done
20128                if (!rolled) {
20129                    break;
20130                }
20131            }
20132        },
20133
20134        /**
20135         * Remove superceded modules and loaded modules.  Called by
20136         * calculate() after we have the mega list of all dependencies
20137         * @method _reduce
20138         * @private
20139         */
20140        _reduce: function() {
20141
20142            var i, j, s, m, r=this.required;
20143            for (i in r) {
20144
20145                // remove if already loaded
20146                if (i in this.loaded) {
20147                    delete r[i];
20148
20149                // remove anything this module supersedes
20150                } else {
20151
20152                    var skinDef = this.parseSkin(i);
20153
20154                    if (skinDef) {
20155                        //YAHOO.log("skin found in reduce: " + skinDef.skin + ", " + skinDef.module);
20156                        // the skin rollup will not have a module name
20157                        if (!skinDef.module) {
20158                            var skin_pre = this.SKIN_PREFIX + skinDef.skin;
20159                            //YAHOO.log("skin_pre: " + skin_pre);
20160                            for (j in r) {
20161
20162                                if (lang.hasOwnProperty(r, j)) {
20163                                    m = this.moduleInfo[j];
20164                                    var ext = m && m.ext;
20165                                    if (!ext && j !== i && j.indexOf(skin_pre) > -1) {
20166                                        // Y.log ("removing component skin: " + j);
20167                                        delete r[j];
20168                                    }
20169                                }
20170                            }
20171                        }
20172                    } else {
20173
20174                         m = this.moduleInfo[i];
20175                         s = m && m.supersedes;
20176                         if (s) {
20177                             for (j=0; j<s.length; j=j+1) {
20178                                 if (s[j] in r) {
20179                                     delete r[s[j]];
20180                                 }
20181                             }
20182                         }
20183                    }
20184                }
20185            }
20186        },
20187
20188        _onFailure: function(msg) {
20189            YAHOO.log('Failure', 'info', 'loader');
20190
20191            var f = this.onFailure;
20192            if (f) {
20193                f.call(this.scope, {
20194                    msg: 'failure: ' + msg,
20195                    data: this.data,
20196                    success: false
20197                });
20198            }
20199        },
20200
20201        _onTimeout: function() {
20202            YAHOO.log('Timeout', 'info', 'loader');
20203            var f = this.onTimeout;
20204            if (f) {
20205                f.call(this.scope, {
20206                    msg: 'timeout',
20207                    data: this.data,
20208                    success: false
20209                });
20210            }
20211        },
20212
20213        /**
20214         * Sorts the dependency tree.  The last step of calculate()
20215         * @method _sort
20216         * @private
20217         */
20218        _sort: function() {
20219            // create an indexed list
20220            var s=[], info=this.moduleInfo, loaded=this.loaded,
20221                checkOptional=!this.loadOptional, me = this;
20222
20223            // returns true if b is not loaded, and is required
20224            // directly or by means of modules it supersedes.
20225            var requires = function(aa, bb) {
20226
20227                var mm=info[aa];
20228
20229                if (loaded[bb] || !mm) {
20230                    return false;
20231                }
20232
20233                var ii,
20234                    rr = mm.expanded,
20235                    after = mm.after,
20236                    other = info[bb],
20237                    optional = mm.optional;
20238
20239
20240                // check if this module requires the other directly
20241                if (rr && YUI.ArrayUtil.indexOf(rr, bb) > -1) {
20242                    return true;
20243                }
20244
20245                // check if this module should be sorted after the other
20246                if (after && YUI.ArrayUtil.indexOf(after, bb) > -1) {
20247                    return true;
20248                }
20249
20250                // if loadOptional is not specified, optional dependencies still
20251                // must be sorted correctly when present.
20252                if (checkOptional && optional && YUI.ArrayUtil.indexOf(optional, bb) > -1) {
20253                    return true;
20254                }
20255
20256                // check if this module requires one the other supersedes
20257                var ss=info[bb] && info[bb].supersedes;
20258                if (ss) {
20259                    for (ii=0; ii<ss.length; ii=ii+1) {
20260                        if (requires(aa, ss[ii])) {
20261                            return true;
20262                        }
20263                    }
20264                }
20265
20266                // external css files should be sorted below yui css
20267                if (mm.ext && mm.type == 'css' && !other.ext && other.type == 'css') {
20268                    return true;
20269                }
20270
20271                return false;
20272            };
20273
20274            // get the required items out of the obj into an array so we
20275            // can sort
20276            for (var i in this.required) {
20277                if (lang.hasOwnProperty(this.required, i)) {
20278                    s.push(i);
20279                }
20280            }
20281
20282            // pointer to the first unsorted item
20283            var p=0;
20284
20285            // keep going until we make a pass without moving anything
20286            for (;;) {
20287
20288                var l=s.length, a, b, j, k, moved=false;
20289
20290                // start the loop after items that are already sorted
20291                for (j=p; j<l; j=j+1) {
20292
20293                    // check the next module on the list to see if its
20294                    // dependencies have been met
20295                    a = s[j];
20296
20297                    // check everything below current item and move if we
20298                    // find a requirement for the current item
20299                    for (k=j+1; k<l; k=k+1) {
20300                        if (requires(a, s[k])) {
20301
20302                            // extract the dependency so we can move it up
20303                            b = s.splice(k, 1);
20304
20305                            // insert the dependency above the item that
20306                            // requires it
20307                            s.splice(j, 0, b[0]);
20308
20309                            moved = true;
20310                            break;
20311                        }
20312                    }
20313
20314                    // jump out of loop if we moved something
20315                    if (moved) {
20316                        break;
20317                    // this item is sorted, move our pointer and keep going
20318                    } else {
20319                        p = p + 1;
20320                    }
20321                }
20322
20323                // when we make it here and moved is false, we are
20324                // finished sorting
20325                if (!moved) {
20326                    break;
20327                }
20328
20329            }
20330
20331            this.sorted = s;
20332        },
20333
20334        toString: function() {
20335            var o = {
20336                type: "YUILoader",
20337                base: this.base,
20338                filter: this.filter,
20339                required: this.required,
20340                loaded: this.loaded,
20341                inserted: this.inserted
20342            };
20343
20344            lang.dump(o, 1);
20345        },
20346
20347        _combine: function() {
20348
20349                this._combining = [];
20350
20351                var self = this,
20352                    s=this.sorted,
20353                    len = s.length,
20354                    js = this.comboBase,
20355                    css = this.comboBase,
20356                    target,
20357                    startLen = js.length,
20358                    i, m, type = this.loadType;
20359
20360                YAHOO.log('type ' + type);
20361
20362                for (i=0; i<len; i=i+1) {
20363
20364                    m = this.moduleInfo[s[i]];
20365
20366                    if (m && !m.ext && (!type || type === m.type)) {
20367
20368                        target = this.root + m.path;
20369
20370                        // if (i < len-1) {
20371                        target += '&';
20372                        // }
20373
20374                        if (m.type == 'js') {
20375                            js += target;
20376                        } else {
20377                            css += target;
20378                        }
20379
20380                        // YAHOO.log(target);
20381                        this._combining.push(s[i]);
20382                    }
20383                }
20384
20385                if (this._combining.length) {
20386
20387YAHOO.log('Attempting to combine: ' + this._combining, "info", "loader");
20388
20389                    var callback=function(o) {
20390                        // YAHOO.log('Combo complete: ' + o.data, "info", "loader");
20391                        // this._combineComplete = true;
20392
20393                        var c=this._combining, len=c.length, i, m;
20394                        for (i=0; i<len; i=i+1) {
20395                            this.inserted[c[i]] = true;
20396                        }
20397
20398                        this.loadNext(o.data);
20399                    },
20400
20401                    loadScript = function() {
20402                        // YAHOO.log('combining js: ' + js);
20403                        if (js.length > startLen) {
20404                            YAHOO.util.Get.script(self._filter(js), {
20405                                data: self._loading,
20406                                onSuccess: callback,
20407                                onFailure: self._onFailure,
20408                                onTimeout: self._onTimeout,
20409                                insertBefore: self.insertBefore,
20410                                charset: self.charset,
20411                                timeout: self.timeout,
20412                                scope: self
20413                            });
20414                        } else {
20415                            this.loadNext();
20416                        }
20417                    };
20418
20419                    // load the css first
20420                    // YAHOO.log('combining css: ' + css);
20421                    if (css.length > startLen) {
20422                        YAHOO.util.Get.css(this._filter(css), {
20423                            data: this._loading,
20424                            onSuccess: loadScript,
20425                            onFailure: this._onFailure,
20426                            onTimeout: this._onTimeout,
20427                            insertBefore: this.insertBefore,
20428                            charset: this.charset,
20429                            timeout: this.timeout,
20430                            scope: self
20431                        });
20432                    } else {
20433                        loadScript();
20434                    }
20435
20436                    return;
20437
20438                } else {
20439                    // this._combineComplete = true;
20440                    this.loadNext(this._loading);
20441                }
20442        },
20443
20444        /**
20445         * inserts the requested modules and their dependencies.
20446         * <code>type</code> can be "js" or "css".  Both script and
20447         * css are inserted if type is not provided.
20448         * @method insert
20449         * @param o optional options object
20450         * @param type {string} the type of dependency to insert
20451         */
20452        insert: function(o, type) {
20453            // if (o) {
20454            //     Y.log("insert: " + lang.dump(o, 1) + ", " + type);
20455            // } else {
20456            //     Y.log("insert: " + this.toString() + ", " + type);
20457            // }
20458
20459            // build the dependency list
20460            this.calculate(o);
20461
20462
20463            // set a flag to indicate the load has started
20464            this._loading = true;
20465
20466            // flag to indicate we are done with the combo service
20467            // and any additional files will need to be loaded
20468            // individually
20469            // this._combineComplete = false;
20470
20471            // keep the loadType (js, css or undefined) cached
20472            this.loadType = type;
20473
20474            if (this.combine) {
20475                return this._combine();
20476            }
20477
20478            if (!type) {
20479                // Y.log("trying to load css first");
20480                var self = this;
20481                this._internalCallback = function() {
20482                            self._internalCallback = null;
20483                            self.insert(null, "js");
20484                        };
20485                this.insert(null, "css");
20486                return;
20487            }
20488
20489
20490            // start the load
20491            this.loadNext();
20492
20493        },
20494
20495        /**
20496         * Interns the script for the requested modules.  The callback is
20497         * provided a reference to the sandboxed YAHOO object.  This only
20498         * applies to the script: css can not be sandboxed; css will be
20499         * loaded into the page normally if specified.
20500         * @method sandbox
20501         * @param callback {Function} the callback to exectued when the load is
20502         *        complete.
20503         */
20504        sandbox: function(o, type) {
20505            // if (o) {
20506                // YAHOO.log("sandbox: " + lang.dump(o, 1) + ", " + type);
20507            // } else {
20508                // YAHOO.log("sandbox: " + this.toString() + ", " + type);
20509            // }
20510
20511            var self = this,
20512                success = function(o) {
20513
20514                    var idx=o.argument[0], name=o.argument[2];
20515
20516                    // store the response in the position it was requested
20517                    self._scriptText[idx] = o.responseText;
20518
20519                    // YAHOO.log("received: " + o.responseText.substr(0, 100) + ", " + idx);
20520
20521                    if (self.onProgress) {
20522                        self.onProgress.call(self.scope, {
20523                                    name: name,
20524                                    scriptText: o.responseText,
20525                                    xhrResponse: o,
20526                                    data: self.data
20527                                });
20528                    }
20529
20530                    // only generate the sandbox once everything is loaded
20531                    self._loadCount++;
20532
20533                    if (self._loadCount >= self._stopCount) {
20534
20535                        // the variable to find
20536                        var v = self.varName || "YAHOO";
20537
20538                        // wrap the contents of the requested modules in an anonymous function
20539                        var t = "(function() {\n";
20540
20541                        // return the locally scoped reference.
20542                        var b = "\nreturn " + v + ";\n})();";
20543
20544                        var ref = eval(t + self._scriptText.join("\n") + b);
20545
20546                        self._pushEvents(ref);
20547
20548                        if (ref) {
20549                            self.onSuccess.call(self.scope, {
20550                                    reference: ref,
20551                                    data: self.data
20552                                });
20553                        } else {
20554                            self._onFailure.call(self.varName + " reference failure");
20555                        }
20556                    }
20557                },
20558
20559                failure = function(o) {
20560                    self.onFailure.call(self.scope, {
20561                            msg: "XHR failure",
20562                            xhrResponse: o,
20563                            data: self.data
20564                        });
20565                };
20566
20567            self._config(o);
20568
20569            if (!self.onSuccess) {
20570throw new Error("You must supply an onSuccess handler for your sandbox");
20571            }
20572
20573            self._sandbox = true;
20574
20575
20576            // take care of any css first (this can't be sandboxed)
20577            if (!type || type !== "js") {
20578                self._internalCallback = function() {
20579                            self._internalCallback = null;
20580                            self.sandbox(null, "js");
20581                        };
20582                self.insert(null, "css");
20583                return;
20584            }
20585
20586            // get the connection manager if not on the page
20587            if (!util.Connect) {
20588                // get a new loader instance to load connection.
20589                var ld = new YAHOO.util.YUILoader();
20590                ld.insert({
20591                    base: self.base,
20592                    filter: self.filter,
20593                    require: "connection",
20594                    insertBefore: self.insertBefore,
20595                    charset: self.charset,
20596                    onSuccess: function() {
20597                        self.sandbox(null, "js");
20598                    },
20599                    scope: self
20600                }, "js");
20601                return;
20602            }
20603
20604            self._scriptText = [];
20605            self._loadCount = 0;
20606            self._stopCount = self.sorted.length;
20607            self._xhr = [];
20608
20609            self.calculate();
20610
20611            var s=self.sorted, l=s.length, i, m, url;
20612
20613            for (i=0; i<l; i=i+1) {
20614                m = self.moduleInfo[s[i]];
20615
20616                // undefined modules cause a failure
20617                if (!m) {
20618                    self._onFailure("undefined module " + m);
20619                    for (var j=0;j<self._xhr.length;j=j+1) {
20620                        self._xhr[j].abort();
20621                    }
20622                    return;
20623                }
20624
20625                // css files should be done
20626                if (m.type !== "js") {
20627                    self._loadCount++;
20628                    continue;
20629                }
20630
20631                url = m.fullpath;
20632                url = (url) ? self._filter(url) : self._url(m.path);
20633
20634                // YAHOO.log("xhr request: " + url + ", " + i);
20635
20636                var xhrData = {
20637                    success: success,
20638                    failure: failure,
20639                    scope: self,
20640                    // [module index, module name, sandbox name]
20641                    argument: [i, url, s[i]]
20642                };
20643
20644                self._xhr.push(util.Connect.asyncRequest('GET', url, xhrData));
20645            }
20646        },
20647
20648        /**
20649         * Executed every time a module is loaded, and if we are in a load
20650         * cycle, we attempt to load the next script.  Public so that it
20651         * is possible to call this if using a method other than
20652         * YAHOO.register to determine when scripts are fully loaded
20653         * @method loadNext
20654         * @param mname {string} optional the name of the module that has
20655         * been loaded (which is usually why it is time to load the next
20656         * one)
20657         */
20658        loadNext: function(mname) {
20659
20660            // It is possible that this function is executed due to something
20661            // else one the page loading a YUI module.  Only react when we
20662            // are actively loading something
20663            if (!this._loading) {
20664                return;
20665            }
20666
20667            var self = this,
20668                donext = function(o) {
20669                    self.loadNext(o.data);
20670                }, successfn, s = this.sorted, len=s.length, i, fn, m, url;
20671
20672
20673            if (mname) {
20674
20675                // if the module that was just loaded isn't what we were expecting,
20676                // continue to wait
20677                if (mname !== this._loading) {
20678                    return;
20679                }
20680
20681                // YAHOO.log("loadNext executing, just loaded " + mname);
20682
20683                // The global handler that is called when each module is loaded
20684                // will pass that module name to this function.  Storing this
20685                // data to avoid loading the same module multiple times
20686                this.inserted[mname] = true;
20687
20688                if (this.onProgress) {
20689                    this.onProgress.call(this.scope, {
20690                            name: mname,
20691                            data: this.data
20692                        });
20693                }
20694                //var o = this.getProvides(mname);
20695                //this.inserted = lang.merge(this.inserted, o);
20696            }
20697
20698
20699
20700            for (i=0; i<len; i=i+1) {
20701
20702                // This.inserted keeps track of what the loader has loaded
20703                if (s[i] in this.inserted) {
20704                    // YAHOO.log(s[i] + " alread loaded ");
20705                    continue;
20706                }
20707
20708                // Because rollups will cause multiple load notifications
20709                // from YAHOO, loadNext may be called multiple times for
20710                // the same module when loading a rollup.  We can safely
20711                // skip the subsequent requests
20712                if (s[i] === this._loading) {
20713                    // YAHOO.log("still loading " + s[i] + ", waiting");
20714                    return;
20715                }
20716
20717                // log("inserting " + s[i]);
20718                m = this.moduleInfo[s[i]];
20719
20720                if (!m) {
20721                    this.onFailure.call(this.scope, {
20722                            msg: "undefined module " + m,
20723                            data: this.data
20724                        });
20725                    return;
20726                }
20727
20728                // The load type is stored to offer the possibility to load
20729                // the css separately from the script.
20730                if (!this.loadType || this.loadType === m.type) {
20731
20732                    successfn = donext;
20733
20734                    this._loading = s[i];
20735                    //YAHOO.log("attempting to load " + s[i] + ", " + this.base);
20736
20737                    fn = (m.type === "css") ? util.Get.css : util.Get.script;
20738                    url = m.fullpath;
20739                    url = (url) ? this._filter(url) : this._url(m.path);
20740
20741                    // safari 2.x or lower, script, and part of YUI
20742                    if (env.ua.webkit && env.ua.webkit < 420 && m.type === "js" &&
20743                          !m.varName) {
20744                          //YUI.info.moduleInfo[s[i]]) {
20745                          //YAHOO.log("using YAHOO env " + s[i] + ", " + m.varName);
20746                        successfn = null;
20747                        this._useYahooListener = true;
20748                    }
20749
20750                    fn(url, {
20751                        data: s[i],
20752                        onSuccess: successfn,
20753                        onFailure: this._onFailure,
20754                        onTimeout: this._onTimeout,
20755                        insertBefore: this.insertBefore,
20756                        charset: this.charset,
20757                        timeout: this.timeout,
20758                        varName: m.varName,
20759                        scope: self
20760                    });
20761
20762                    return;
20763                }
20764            }
20765
20766            // we are finished
20767            this._loading = null;
20768
20769            // internal callback for loading css first
20770            if (this._internalCallback) {
20771                var f = this._internalCallback;
20772                this._internalCallback = null;
20773                f.call(this);
20774            } else if (this.onSuccess) {
20775                this._pushEvents();
20776                this.onSuccess.call(this.scope, {
20777                        data: this.data
20778                    });
20779            }
20780
20781        },
20782
20783        /**
20784         * In IE, the onAvailable/onDOMReady events need help when Event is
20785         * loaded dynamically
20786         * @method _pushEvents
20787         * @param {Function} optional function reference
20788         * @private
20789         */
20790        _pushEvents: function(ref) {
20791            var r = ref || YAHOO;
20792            if (r.util && r.util.Event) {
20793                r.util.Event._load();
20794            }
20795        },
20796
20797        /**
20798         * Applies filter
20799         * method _filter
20800         * @return {string} the filtered string
20801         * @private
20802         */
20803        _filter: function(str) {
20804            var f = this.filter;
20805            return (f) ?  str.replace(new RegExp(f.searchExp, 'g'), f.replaceStr) : str;
20806        },
20807
20808        /**
20809         * Generates the full url for a module
20810         * method _url
20811         * @param path {string} the path fragment
20812         * @return {string} the full url
20813         * @private
20814         */
20815        _url: function(path) {
20816            return this._filter((this.base || "") + path);
20817        }
20818
20819    };
20820
20821})();
20822
20823YAHOO.register("yuiloader", YAHOO.util.YUILoader, {version: "2.9.0", build: "2800"});
20824/*
20825Copyright (c) 2011, Yahoo! Inc. All rights reserved.
20826Code licensed under the BSD License:
20827http://developer.yahoo.com/yui/license.html
20828version: 2.9.0
20829*/
20830/**
20831* @module button
20832* @description <p>The Button Control enables the creation of rich, graphical 
20833* buttons that function like traditional HTML form buttons.  <em>Unlike</em> 
20834* traditional HTML form buttons, buttons created with the Button Control can have 
20835* a label that is different from its value.  With the inclusion of the optional 
20836* <a href="module_menu.html">Menu Control</a>, the Button Control can also be
20837* used to create menu buttons and split buttons, controls that are not 
20838* available natively in HTML.  The Button Control can also be thought of as a 
20839* way to create more visually engaging implementations of the browser's 
20840* default radio-button and check-box controls.</p>
20841* <p>The Button Control supports the following types:</p>
20842* <dl>
20843* <dt>push</dt>
20844* <dd>Basic push button that can execute a user-specified command when 
20845* pressed.</dd>
20846* <dt>link</dt>
20847* <dd>Navigates to a specified url when pressed.</dd>
20848* <dt>submit</dt>
20849* <dd>Submits the parent form when pressed.</dd>
20850* <dt>reset</dt>
20851* <dd>Resets the parent form when pressed.</dd>
20852* <dt>checkbox</dt>
20853* <dd>Maintains a "checked" state that can be toggled on and off.</dd>
20854* <dt>radio</dt>
20855* <dd>Maintains a "checked" state that can be toggled on and off.  Use with 
20856* the ButtonGroup class to create a set of controls that are mutually 
20857* exclusive; checking one button in the set will uncheck all others in 
20858* the group.</dd>
20859* <dt>menu</dt>
20860* <dd>When pressed will show/hide a menu.</dd>
20861* <dt>split</dt>
20862* <dd>Can execute a user-specified command or display a menu when pressed.</dd>
20863* </dl>
20864* @title Button
20865* @namespace YAHOO.widget
20866* @requires yahoo, dom, element, event
20867* @optional container, menu
20868*/
20869
20870
20871(function () {
20872
20873
20874    /**
20875    * The Button class creates a rich, graphical button.
20876    * @param {String} p_oElement String specifying the id attribute of the 
20877    * <code>&#60;input&#62;</code>, <code>&#60;button&#62;</code>,
20878    * <code>&#60;a&#62;</code>, or <code>&#60;span&#62;</code> element to 
20879    * be used to create the button.
20880    * @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
20881    * one-html.html#ID-6043025">HTMLInputElement</a>|<a href="http://www.w3.org
20882    * /TR/2000/WD-DOM-Level-1-20000929/level-one-html.html#ID-34812697">
20883    * HTMLButtonElement</a>|<a href="
20884    * http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-html.html#
20885    * ID-33759296">HTMLElement</a>} p_oElement Object reference for the 
20886    * <code>&#60;input&#62;</code>, <code>&#60;button&#62;</code>, 
20887    * <code>&#60;a&#62;</code>, or <code>&#60;span&#62;</code> element to be 
20888    * used to create the button.
20889    * @param {Object} p_oElement Object literal specifying a set of   
20890    * configuration attributes used to create the button.
20891    * @param {Object} p_oAttributes Optional. Object literal specifying a set  
20892    * of configuration attributes used to create the button.
20893    * @namespace YAHOO.widget
20894    * @class Button
20895    * @constructor
20896    * @extends YAHOO.util.Element
20897    */
20898
20899
20900
20901    // Shorthard for utilities
20902
20903    var Dom = YAHOO.util.Dom,
20904        Event = YAHOO.util.Event,
20905        Lang = YAHOO.lang,
20906        UA = YAHOO.env.ua,
20907        Overlay = YAHOO.widget.Overlay,
20908        Menu = YAHOO.widget.Menu,
20909    
20910    
20911        // Private member variables
20912    
20913        m_oButtons = {},    // Collection of all Button instances
20914        m_oOverlayManager = null,   // YAHOO.widget.OverlayManager instance
20915        m_oSubmitTrigger = null,    // The button that submitted the form 
20916        m_oFocusedButton = null;    // The button that has focus
20917
20918
20919
20920    // Private methods
20921
20922    
20923    
20924    /**
20925    * @method createInputElement
20926    * @description Creates an <code>&#60;input&#62;</code> element of the 
20927    * specified type.
20928    * @private
20929    * @param {String} p_sType String specifying the type of 
20930    * <code>&#60;input&#62;</code> element to create.
20931    * @param {String} p_sName String specifying the name of 
20932    * <code>&#60;input&#62;</code> element to create.
20933    * @param {String} p_sValue String specifying the value of 
20934    * <code>&#60;input&#62;</code> element to create.
20935    * @param {String} p_bChecked Boolean specifying if the  
20936    * <code>&#60;input&#62;</code> element is to be checked.
20937    * @return {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
20938    * one-html.html#ID-6043025">HTMLInputElement</a>}
20939    */
20940    function createInputElement(p_sType, p_sName, p_sValue, p_bChecked) {
20941    
20942        var oInput,
20943            sInput;
20944    
20945        if (Lang.isString(p_sType) && Lang.isString(p_sName)) {
20946        
20947            if (UA.ie && (UA.ie < 9)) {
20948        
20949                /*
20950                    For IE it is necessary to create the element with the 
20951                    "type," "name," "value," and "checked" properties set all 
20952                    at once.
20953                */
20954            
20955                sInput = "<input type=\"" + p_sType + "\" name=\"" + 
20956                    p_sName + "\"";
20957        
20958                if (p_bChecked) {
20959        
20960                    sInput += " checked";
20961                
20962                }
20963                
20964                sInput += ">";
20965        
20966                oInput = document.createElement(sInput);
20967        
20968                oInput.value = p_sValue;
20969
20970            } else {
20971            
20972                oInput = document.createElement("input");
20973                oInput.name = p_sName;
20974                oInput.type = p_sType;
20975                oInput.value = p_sValue;
20976        
20977                if (p_bChecked) {
20978        
20979                    oInput.checked = true;
20980                
20981                }
20982        
20983            }
20984        
20985        
20986        }
20987
20988		return oInput;
20989    
20990    }
20991    
20992    
20993    /**
20994    * @method setAttributesFromSrcElement
20995    * @description Gets the values for all the attributes of the source element 
20996    * (either <code>&#60;input&#62;</code> or <code>&#60;a&#62;</code>) that 
20997    * map to Button configuration attributes and sets them into a collection 
20998    * that is passed to the Button constructor.
20999    * @private
21000    * @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
21001    * one-html.html#ID-6043025">HTMLInputElement</a>|<a href="http://www.w3.org/
21002    * TR/2000/WD-DOM-Level-1-20000929/level-one-html.html#ID-
21003    * 48250443">HTMLAnchorElement</a>} p_oElement Object reference to the HTML 
21004    * element (either <code>&#60;input&#62;</code> or <code>&#60;span&#62;
21005    * </code>) used to create the button.
21006    * @param {Object} p_oAttributes Object reference for the collection of 
21007    * configuration attributes used to create the button.
21008    */
21009    function setAttributesFromSrcElement(p_oElement, p_oAttributes) {
21010    
21011        var sSrcElementNodeName = p_oElement.nodeName.toUpperCase(),
21012			sClass = (this.CLASS_NAME_PREFIX + this.CSS_CLASS_NAME),
21013            me = this,
21014            oAttribute,
21015            oRootNode,
21016            sText;
21017            
21018    
21019        /**
21020        * @method setAttributeFromDOMAttribute
21021        * @description Gets the value of the specified DOM attribute and sets it 
21022        * into the collection of configuration attributes used to configure 
21023        * the button.
21024        * @private
21025        * @param {String} p_sAttribute String representing the name of the 
21026        * attribute to retrieve from the DOM element.
21027        */
21028        function setAttributeFromDOMAttribute(p_sAttribute) {
21029    
21030            if (!(p_sAttribute in p_oAttributes)) {
21031    
21032                /*
21033                    Need to use "getAttributeNode" instead of "getAttribute" 
21034                    because using "getAttribute," IE will return the innerText 
21035                    of a <code>&#60;button&#62;</code> for the value attribute  
21036                    rather than the value of the "value" attribute.
21037                */
21038        
21039                oAttribute = p_oElement.getAttributeNode(p_sAttribute);
21040        
21041    
21042                if (oAttribute && ("value" in oAttribute)) {
21043    
21044                    YAHOO.log("Setting attribute \"" + p_sAttribute + 
21045                        "\" using source element's attribute value of \"" + 
21046                        oAttribute.value + "\"", "info", me.toString());
21047    
21048                    p_oAttributes[p_sAttribute] = oAttribute.value;
21049    
21050                }
21051    
21052            }
21053        
21054        }
21055    
21056    
21057        /**
21058        * @method setFormElementProperties
21059        * @description Gets the value of the attributes from the form element  
21060        * and sets them into the collection of configuration attributes used to 
21061        * configure the button.
21062        * @private
21063        */
21064        function setFormElementProperties() {
21065    
21066            setAttributeFromDOMAttribute("type");
21067    
21068            if (p_oAttributes.type == "button") {
21069            
21070                p_oAttributes.type = "push";
21071            
21072            }
21073    
21074            if (!("disabled" in p_oAttributes)) {
21075    
21076                p_oAttributes.disabled = p_oElement.disabled;
21077    
21078            }
21079    
21080            setAttributeFromDOMAttribute("name");
21081            setAttributeFromDOMAttribute("value");
21082            setAttributeFromDOMAttribute("title");
21083    
21084        }
21085
21086    
21087        switch (sSrcElementNodeName) {
21088        
21089        case "A":
21090            
21091            p_oAttributes.type = "link";
21092            
21093            setAttributeFromDOMAttribute("href");
21094            setAttributeFromDOMAttribute("target");
21095        
21096            break;
21097    
21098        case "INPUT":
21099
21100            setFormElementProperties();
21101
21102            if (!("checked" in p_oAttributes)) {
21103    
21104                p_oAttributes.checked = p_oElement.checked;
21105    
21106            }
21107
21108            break;
21109
21110        case "BUTTON":
21111
21112            setFormElementProperties();
21113
21114            oRootNode = p_oElement.parentNode.parentNode;
21115
21116            if (Dom.hasClass(oRootNode, sClass + "-checked")) {
21117            
21118                p_oAttributes.checked = true;
21119            
21120            }
21121
21122            if (Dom.hasClass(oRootNode, sClass + "-disabled")) {
21123
21124                p_oAttributes.disabled = true;
21125            
21126            }
21127
21128            p_oElement.removeAttribute("value");
21129
21130            p_oElement.setAttribute("type", "button");
21131
21132            break;
21133        
21134        }
21135
21136        p_oElement.removeAttribute("id");
21137        p_oElement.removeAttribute("name");
21138        
21139        if (!("tabindex" in p_oAttributes)) {
21140
21141            p_oAttributes.tabindex = p_oElement.tabIndex;
21142
21143        }
21144    
21145        if (!("label" in p_oAttributes)) {
21146    
21147            // Set the "label" property
21148        
21149            sText = sSrcElementNodeName == "INPUT" ? 
21150                            p_oElement.value : p_oElement.innerHTML;
21151        
21152    
21153            if (sText && sText.length > 0) {
21154                
21155                p_oAttributes.label = sText;
21156                
21157            } 
21158    
21159        }
21160    
21161    }
21162    
21163    
21164    /**
21165    * @method initConfig
21166    * @description Initializes the set of configuration attributes that are 
21167    * used to instantiate the button.
21168    * @private
21169    * @param {Object} Object representing the button's set of 
21170    * configuration attributes.
21171    */
21172    function initConfig(p_oConfig) {
21173    
21174        var oAttributes = p_oConfig.attributes,
21175            oSrcElement = oAttributes.srcelement,
21176            sSrcElementNodeName = oSrcElement.nodeName.toUpperCase(),
21177            me = this;
21178    
21179    
21180        if (sSrcElementNodeName == this.NODE_NAME) {
21181    
21182            p_oConfig.element = oSrcElement;
21183            p_oConfig.id = oSrcElement.id;
21184
21185            Dom.getElementsBy(function (p_oElement) {
21186            
21187                switch (p_oElement.nodeName.toUpperCase()) {
21188                
21189                case "BUTTON":
21190                case "A":
21191                case "INPUT":
21192
21193                    setAttributesFromSrcElement.call(me, p_oElement, 
21194                        oAttributes);
21195
21196                    break;                        
21197                
21198                }
21199            
21200            }, "*", oSrcElement);
21201        
21202        }
21203        else {
21204    
21205            switch (sSrcElementNodeName) {
21206
21207            case "BUTTON":
21208            case "A":
21209            case "INPUT":
21210
21211                setAttributesFromSrcElement.call(this, oSrcElement, 
21212                    oAttributes);
21213
21214                break;
21215
21216            }
21217        
21218        }
21219    
21220    }
21221
21222
21223
21224    //  Constructor
21225
21226    YAHOO.widget.Button = function (p_oElement, p_oAttributes) {
21227    
21228		if (!Overlay && YAHOO.widget.Overlay) {
21229		
21230			Overlay = YAHOO.widget.Overlay;
21231		
21232		}
21233
21234
21235		if (!Menu && YAHOO.widget.Menu) {
21236		
21237			Menu = YAHOO.widget.Menu;
21238		
21239		}
21240
21241
21242        var fnSuperClass = YAHOO.widget.Button.superclass.constructor,
21243            oConfig,
21244            oElement;
21245    
21246
21247        if (arguments.length == 1 && !Lang.isString(p_oElement) && !p_oElement.nodeName) {
21248    
21249            if (!p_oElement.id) {
21250    
21251                p_oElement.id = Dom.generateId();
21252    
21253                YAHOO.log("No value specified for the button's \"id\" " + 
21254                    "attribute. Setting button id to \"" + p_oElement.id + 
21255                    "\".", "info", this.toString());
21256    
21257            }
21258    
21259            YAHOO.log("No source HTML element.  Building the button " +
21260                    "using the set of configuration attributes.", "info", this.toString());
21261    
21262            fnSuperClass.call(this, (this.createButtonElement(p_oElement.type)), p_oElement);
21263    
21264        }
21265        else {
21266    
21267            oConfig = { element: null, attributes: (p_oAttributes || {}) };
21268    
21269    
21270            if (Lang.isString(p_oElement)) {
21271    
21272                oElement = Dom.get(p_oElement);
21273    
21274                if (oElement) {
21275
21276                    if (!oConfig.attributes.id) {
21277                    
21278                        oConfig.attributes.id = p_oElement;
21279                    
21280                    }
21281    
21282                    YAHOO.log("Building the button using an existing " + 
21283                            "HTML element as a source element.", "info", this.toString());
21284                
21285                
21286                    oConfig.attributes.srcelement = oElement;
21287                
21288                    initConfig.call(this, oConfig);
21289                
21290                
21291                    if (!oConfig.element) {
21292                
21293                        YAHOO.log("Source element could not be used " +
21294                                "as is.  Creating a new HTML element for " + 
21295                                "the button.", "info", this.toString());
21296                
21297                        oConfig.element = this.createButtonElement(oConfig.attributes.type);
21298                
21299                    }
21300                
21301                    fnSuperClass.call(this, oConfig.element, oConfig.attributes);
21302    
21303                }
21304    
21305            }
21306            else if (p_oElement.nodeName) {
21307    
21308                if (!oConfig.attributes.id) {
21309    
21310                    if (p_oElement.id) {
21311        
21312                        oConfig.attributes.id = p_oElement.id;
21313                    
21314                    }
21315                    else {
21316        
21317                        oConfig.attributes.id = Dom.generateId();
21318        
21319                        YAHOO.log("No value specified for the button's " +
21320                            "\"id\" attribute. Setting button id to \"" + 
21321                            oConfig.attributes.id + "\".", "info", this.toString());
21322        
21323                    }
21324    
21325                }
21326    
21327                YAHOO.log("Building the button using an existing HTML " + 
21328                    "element as a source element.", "info", this.toString());
21329    
21330    
21331                oConfig.attributes.srcelement = p_oElement;
21332        
21333                initConfig.call(this, oConfig);
21334        
21335        
21336                if (!oConfig.element) {
21337    
21338                    YAHOO.log("Source element could not be used as is." +
21339                            "  Creating a new HTML element for the button.", 
21340                            "info", this.toString());
21341            
21342                    oConfig.element = this.createButtonElement(oConfig.attributes.type);
21343            
21344                }
21345            
21346                fnSuperClass.call(this, oConfig.element, oConfig.attributes);
21347            
21348            }
21349    
21350        }
21351    
21352    };
21353
21354
21355
21356    YAHOO.extend(YAHOO.widget.Button, YAHOO.util.Element, {
21357    
21358    
21359        // Protected properties
21360        
21361        
21362        /** 
21363        * @property _button
21364        * @description Object reference to the button's internal 
21365        * <code>&#60;a&#62;</code> or <code>&#60;button&#62;</code> element.
21366        * @default null
21367        * @protected
21368        * @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
21369        * level-one-html.html#ID-48250443">HTMLAnchorElement</a>|<a href="
21370        * http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-html.html
21371        * #ID-34812697">HTMLButtonElement</a>
21372        */
21373        _button: null,
21374        
21375        
21376        /** 
21377        * @property _menu
21378        * @description Object reference to the button's menu.
21379        * @default null
21380        * @protected
21381        * @type {<a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>|
21382        * <a href="YAHOO.widget.Menu.html">YAHOO.widget.Menu</a>}
21383        */
21384        _menu: null,
21385        
21386        
21387        /** 
21388        * @property _hiddenFields
21389        * @description Object reference to the <code>&#60;input&#62;</code>  
21390        * element, or array of HTML form elements used to represent the button
21391        *  when its parent form is submitted.
21392        * @default null
21393        * @protected
21394        * @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
21395        * level-one-html.html#ID-6043025">HTMLInputElement</a>|Array
21396        */
21397        _hiddenFields: null,
21398        
21399        
21400        /** 
21401        * @property _onclickAttributeValue
21402        * @description Object reference to the button's current value for the 
21403        * "onclick" configuration attribute.
21404        * @default null
21405        * @protected
21406        * @type Object
21407        */
21408        _onclickAttributeValue: null,
21409        
21410        
21411        /** 
21412        * @property _activationKeyPressed
21413        * @description Boolean indicating if the key(s) that toggle the button's 
21414        * "active" state have been pressed.
21415        * @default false
21416        * @protected
21417        * @type Boolean
21418        */
21419        _activationKeyPressed: false,
21420        
21421        
21422        /** 
21423        * @property _activationButtonPressed
21424        * @description Boolean indicating if the mouse button that toggles 
21425        * the button's "active" state has been pressed.
21426        * @default false
21427        * @protected
21428        * @type Boolean
21429        */
21430        _activationButtonPressed: false,
21431        
21432        
21433        /** 
21434        * @property _hasKeyEventHandlers
21435        * @description Boolean indicating if the button's "blur", "keydown" and 
21436        * "keyup" event handlers are assigned
21437        * @default false
21438        * @protected
21439        * @type Boolean
21440        */
21441        _hasKeyEventHandlers: false,
21442        
21443        
21444        /** 
21445        * @property _hasMouseEventHandlers
21446        * @description Boolean indicating if the button's "mouseout," 
21447        * "mousedown," and "mouseup" event handlers are assigned
21448        * @default false
21449        * @protected
21450        * @type Boolean
21451        */
21452        _hasMouseEventHandlers: false,
21453
21454
21455        /** 
21456        * @property _nOptionRegionX
21457        * @description Number representing the X coordinate of the leftmost edge of the Button's 
21458        * option region.  Applies only to Buttons of type "split".
21459        * @default 0
21460        * @protected
21461        * @type Number
21462        */        
21463        _nOptionRegionX: 0,
21464        
21465
21466
21467        // Constants
21468
21469        /**
21470        * @property CLASS_NAME_PREFIX
21471        * @description Prefix used for all class names applied to a Button.
21472        * @default "yui-"
21473        * @final
21474        * @type String
21475        */
21476        CLASS_NAME_PREFIX: "yui-",
21477        
21478        
21479        /**
21480        * @property NODE_NAME
21481        * @description The name of the node to be used for the button's 
21482        * root element.
21483        * @default "SPAN"
21484        * @final
21485        * @type String
21486        */
21487        NODE_NAME: "SPAN",
21488        
21489        
21490        /**
21491        * @property CHECK_ACTIVATION_KEYS
21492        * @description Array of numbers representing keys that (when pressed) 
21493        * toggle the button's "checked" attribute.
21494        * @default [32]
21495        * @final
21496        * @type Array
21497        */
21498        CHECK_ACTIVATION_KEYS: [32],
21499        
21500        
21501        /**
21502        * @property ACTIVATION_KEYS
21503        * @description Array of numbers representing keys that (when presed) 
21504        * toggle the button's "active" state.
21505        * @default [13, 32]
21506        * @final
21507        * @type Array
21508        */
21509        ACTIVATION_KEYS: [13, 32],
21510        
21511        
21512        /**
21513        * @property OPTION_AREA_WIDTH
21514        * @description Width (in pixels) of the area of a split button that  
21515        * when pressed will display a menu.
21516        * @default 20
21517        * @final
21518        * @type Number
21519        */
21520        OPTION_AREA_WIDTH: 20,
21521        
21522        
21523        /**
21524        * @property CSS_CLASS_NAME
21525        * @description String representing the CSS class(es) to be applied to  
21526        * the button's root element.
21527        * @default "button"
21528        * @final
21529        * @type String
21530        */
21531        CSS_CLASS_NAME: "button",
21532        
21533        
21534        
21535        // Protected attribute setter methods
21536        
21537        
21538        /**
21539        * @method _setType
21540        * @description Sets the value of the button's "type" attribute.
21541        * @protected
21542        * @param {String} p_sType String indicating the value for the button's 
21543        * "type" attribute.
21544        */
21545        _setType: function (p_sType) {
21546        
21547            if (p_sType == "split") {
21548        
21549                this.on("option", this._onOption);
21550        
21551            }
21552        
21553        },
21554        
21555        
21556        /**
21557        * @method _setLabel
21558        * @description Sets the value of the button's "label" attribute.
21559        * @protected
21560        * @param {HTML} p_sLabel String indicating the value for the button's 
21561        * "label" attribute.
21562        */
21563        _setLabel: function (p_sLabel) {
21564
21565            this._button.innerHTML = p_sLabel;
21566
21567            
21568            /*
21569                Remove and add the default class name from the root element
21570                for Gecko to ensure that the button shrinkwraps to the label.
21571                Without this the button will not be rendered at the correct 
21572                width when the label changes.  The most likely cause for this 
21573                bug is button's use of the Gecko-specific CSS display type of 
21574                "-moz-inline-box" to simulate "inline-block" supported by IE, 
21575                Safari and Opera.
21576            */
21577            
21578            var sClass,
21579                nGeckoVersion = UA.gecko;
21580				
21581            
21582            if (nGeckoVersion && nGeckoVersion < 1.9 && Dom.inDocument(this.get("element"))) {
21583            
21584                sClass = (this.CLASS_NAME_PREFIX + this.CSS_CLASS_NAME);
21585
21586                this.removeClass(sClass);
21587                
21588                Lang.later(0, this, this.addClass, sClass);
21589
21590            }
21591        
21592        },
21593        
21594        
21595        /**
21596        * @method _setTabIndex
21597        * @description Sets the value of the button's "tabindex" attribute.
21598        * @protected
21599        * @param {Number} p_nTabIndex Number indicating the value for the 
21600        * button's "tabindex" attribute.
21601        */
21602        _setTabIndex: function (p_nTabIndex) {
21603        
21604            this._button.tabIndex = p_nTabIndex;
21605        
21606        },
21607        
21608        
21609        /**
21610        * @method _setTitle
21611        * @description Sets the value of the button's "title" attribute.
21612        * @protected
21613        * @param {String} p_nTabIndex Number indicating the value for 
21614        * the button's "title" attribute.
21615        */
21616        _setTitle: function (p_sTitle) {
21617        
21618            if (this.get("type") != "link") {
21619        
21620                this._button.title = p_sTitle;
21621        
21622            }
21623        
21624        },
21625        
21626        
21627        /**
21628        * @method _setDisabled
21629        * @description Sets the value of the button's "disabled" attribute.
21630        * @protected
21631        * @param {Boolean} p_bDisabled Boolean indicating the value for 
21632        * the button's "disabled" attribute.
21633        */
21634        _setDisabled: function (p_bDisabled) {
21635        
21636            if (this.get("type") != "link") {
21637        
21638                if (p_bDisabled) {
21639        
21640                    if (this._menu) {
21641        
21642                        this._menu.hide();
21643        
21644                    }
21645        
21646                    if (this.hasFocus()) {
21647                    
21648                        this.blur();
21649                    
21650                    }
21651        
21652                    this._button.setAttribute("disabled", "disabled");
21653        
21654                    this.addStateCSSClasses("disabled");
21655
21656                    this.removeStateCSSClasses("hover");
21657                    this.removeStateCSSClasses("active");
21658                    this.removeStateCSSClasses("focus");
21659        
21660                }
21661                else {
21662        
21663                    this._button.removeAttribute("disabled");
21664        
21665                    this.removeStateCSSClasses("disabled");
21666                
21667                }
21668        
21669            }
21670        
21671        },
21672
21673        
21674        /**
21675        * @method _setHref
21676        * @description Sets the value of the button's "href" attribute.
21677        * @protected
21678        * @param {String} p_sHref String indicating the value for the button's 
21679        * "href" attribute.
21680        */
21681        _setHref: function (p_sHref) {
21682        
21683            if (this.get("type") == "link") {
21684        
21685                this._button.href = p_sHref;
21686            
21687            }
21688        
21689        },
21690        
21691        
21692        /**
21693        * @method _setTarget
21694        * @description Sets the value of the button's "target" attribute.
21695        * @protected
21696        * @param {String} p_sTarget String indicating the value for the button's 
21697        * "target" attribute.
21698        */
21699        _setTarget: function (p_sTarget) {
21700        
21701            if (this.get("type") == "link") {
21702        
21703                this._button.setAttribute("target", p_sTarget);
21704            
21705            }
21706        
21707        },
21708        
21709        
21710        /**
21711        * @method _setChecked
21712        * @description Sets the value of the button's "target" attribute.
21713        * @protected
21714        * @param {Boolean} p_bChecked Boolean indicating the value for  
21715        * the button's "checked" attribute.
21716        */
21717        _setChecked: function (p_bChecked) {
21718        
21719            var sType = this.get("type");
21720        
21721            if (sType == "checkbox" || sType == "radio") {
21722        
21723                if (p_bChecked) {
21724                    this.addStateCSSClasses("checked");
21725                }
21726                else {
21727                    this.removeStateCSSClasses("checked");
21728                }
21729        
21730            }
21731        
21732        },
21733
21734        
21735        /**
21736        * @method _setMenu
21737        * @description Sets the value of the button's "menu" attribute.
21738        * @protected
21739        * @param {Object} p_oMenu Object indicating the value for the button's 
21740        * "menu" attribute.
21741        */
21742        _setMenu: function (p_oMenu) {
21743
21744            var bLazyLoad = this.get("lazyloadmenu"),
21745                oButtonElement = this.get("element"),
21746                sMenuCSSClassName,
21747        
21748                /*
21749                    Boolean indicating if the value of p_oMenu is an instance 
21750                    of YAHOO.widget.Menu or YAHOO.widget.Overlay.
21751                */
21752        
21753                bInstance = false,
21754                oMenu,
21755                oMenuElement,
21756                oSrcElement;
21757        
21758
21759			function onAppendTo() {
21760
21761				oMenu.render(oButtonElement.parentNode);
21762				
21763				this.removeListener("appendTo", onAppendTo);
21764			
21765			}
21766			
21767			
21768			function setMenuContainer() {
21769
21770				oMenu.cfg.queueProperty("container", oButtonElement.parentNode);
21771				
21772				this.removeListener("appendTo", setMenuContainer);
21773			
21774			}
21775
21776
21777			function initMenu() {
21778		
21779				var oContainer;
21780		
21781				if (oMenu) {
21782
21783					Dom.addClass(oMenu.element, this.get("menuclassname"));
21784					Dom.addClass(oMenu.element, this.CLASS_NAME_PREFIX + this.get("type") + "-button-menu");
21785
21786					oMenu.showEvent.subscribe(this._onMenuShow, null, this);
21787					oMenu.hideEvent.subscribe(this._onMenuHide, null, this);
21788					oMenu.renderEvent.subscribe(this._onMenuRender, null, this);
21789
21790
21791					if (Menu && oMenu instanceof Menu) {
21792
21793						if (bLazyLoad) {
21794
21795							oContainer = this.get("container");
21796
21797							if (oContainer) {
21798
21799								oMenu.cfg.queueProperty("container", oContainer);
21800
21801							}
21802							else {
21803
21804								this.on("appendTo", setMenuContainer);
21805
21806							}
21807
21808						}
21809
21810						oMenu.cfg.queueProperty("clicktohide", false);
21811
21812						oMenu.keyDownEvent.subscribe(this._onMenuKeyDown, this, true);
21813						oMenu.subscribe("click", this._onMenuClick, this, true);
21814
21815						this.on("selectedMenuItemChange", this._onSelectedMenuItemChange);
21816		
21817						oSrcElement = oMenu.srcElement;
21818		
21819						if (oSrcElement && oSrcElement.nodeName.toUpperCase() == "SELECT") {
21820
21821							oSrcElement.style.display = "none";
21822							oSrcElement.parentNode.removeChild(oSrcElement);
21823		
21824						}
21825		
21826					}
21827					else if (Overlay && oMenu instanceof Overlay) {
21828		
21829						if (!m_oOverlayManager) {
21830		
21831							m_oOverlayManager = new YAHOO.widget.OverlayManager();
21832						
21833						}
21834						
21835						m_oOverlayManager.register(oMenu);
21836						
21837					}
21838		
21839		
21840					this._menu = oMenu;
21841
21842		
21843					if (!bInstance && !bLazyLoad) {
21844		
21845						if (Dom.inDocument(oButtonElement)) {
21846	
21847							oMenu.render(oButtonElement.parentNode);
21848						
21849						}
21850						else {
21851		
21852							this.on("appendTo", onAppendTo);
21853						
21854						}
21855					
21856					}
21857		
21858				}
21859		
21860			}
21861
21862        
21863            if (Overlay) {
21864        
21865				if (Menu) {
21866				
21867					sMenuCSSClassName = Menu.prototype.CSS_CLASS_NAME;
21868				
21869				}
21870			
21871				if (p_oMenu && Menu && (p_oMenu instanceof Menu)) {
21872			
21873					oMenu = p_oMenu;
21874					bInstance = true;
21875			
21876					initMenu.call(this);
21877			
21878				}
21879				else if (Overlay && p_oMenu && (p_oMenu instanceof Overlay)) {
21880			
21881					oMenu = p_oMenu;
21882					bInstance = true;
21883			
21884					oMenu.cfg.queueProperty("visible", false);
21885			
21886					initMenu.call(this);
21887			
21888				}
21889				else if (Menu && Lang.isArray(p_oMenu)) {
21890
21891					oMenu = new Menu(Dom.generateId(), { lazyload: bLazyLoad, itemdata: p_oMenu });
21892						
21893					this._menu = oMenu;
21894			
21895					this.on("appendTo", initMenu);
21896			
21897				}
21898				else if (Lang.isString(p_oMenu)) {
21899			
21900					oMenuElement = Dom.get(p_oMenu);
21901			
21902					if (oMenuElement) {
21903			
21904						if (Menu && Dom.hasClass(oMenuElement, sMenuCSSClassName) || 
21905							oMenuElement.nodeName.toUpperCase() == "SELECT") {
21906				
21907							oMenu = new Menu(p_oMenu, { lazyload: bLazyLoad });
21908				
21909							initMenu.call(this);
21910				
21911						}
21912						else if (Overlay) {
21913			
21914							oMenu = new Overlay(p_oMenu, { visible: false });
21915				
21916							initMenu.call(this);
21917				
21918						}
21919			
21920					}
21921			
21922				}
21923				else if (p_oMenu && p_oMenu.nodeName) {
21924			
21925					if (Menu && Dom.hasClass(p_oMenu, sMenuCSSClassName) || 
21926							p_oMenu.nodeName.toUpperCase() == "SELECT") {
21927			
21928						oMenu = new Menu(p_oMenu, { lazyload: bLazyLoad });
21929					
21930						initMenu.call(this);
21931			
21932					}
21933					else if (Overlay) {
21934			
21935						if (!p_oMenu.id) {
21936						
21937							Dom.generateId(p_oMenu);
21938						
21939						}
21940			
21941						oMenu = new Overlay(p_oMenu, { visible: false });
21942			
21943						initMenu.call(this);
21944					
21945					}
21946				
21947				}
21948            
21949            }
21950        
21951        },
21952        
21953        
21954        /**
21955        * @method _setOnClick
21956        * @description Sets the value of the button's "onclick" attribute.
21957        * @protected
21958        * @param {Object} p_oObject Object indicating the value for the button's 
21959        * "onclick" attribute.
21960        */
21961        _setOnClick: function (p_oObject) {
21962        
21963            /*
21964                Remove any existing listeners if a "click" event handler 
21965                has already been specified.
21966            */
21967        
21968            if (this._onclickAttributeValue && 
21969                (this._onclickAttributeValue != p_oObject)) {
21970        
21971                this.removeListener("click", this._onclickAttributeValue.fn);
21972        
21973                this._onclickAttributeValue = null;
21974        
21975            }
21976        
21977        
21978            if (!this._onclickAttributeValue && 
21979                Lang.isObject(p_oObject) && 
21980                Lang.isFunction(p_oObject.fn)) {
21981        
21982                this.on("click", p_oObject.fn, p_oObject.obj, p_oObject.scope);
21983        
21984                this._onclickAttributeValue = p_oObject;
21985        
21986            }
21987        
21988        },
21989
21990        
21991        
21992        // Protected methods
21993
21994        
21995        
21996        /**
21997        * @method _isActivationKey
21998        * @description Determines if the specified keycode is one that toggles  
21999        * the button's "active" state.
22000        * @protected
22001        * @param {Number} p_nKeyCode Number representing the keycode to 
22002        * be evaluated.
22003        * @return {Boolean}
22004        */
22005        _isActivationKey: function (p_nKeyCode) {
22006        
22007            var sType = this.get("type"),
22008                aKeyCodes = (sType == "checkbox" || sType == "radio") ? 
22009                    this.CHECK_ACTIVATION_KEYS : this.ACTIVATION_KEYS,
22010        
22011                nKeyCodes = aKeyCodes.length,
22012                bReturnVal = false,
22013                i;
22014        
22015
22016            if (nKeyCodes > 0) {
22017        
22018                i = nKeyCodes - 1;
22019        
22020                do {
22021        
22022                    if (p_nKeyCode == aKeyCodes[i]) {
22023        
22024                        bReturnVal = true;
22025                        break;
22026        
22027                    }
22028        
22029                }
22030                while (i--);
22031            
22032            }
22033            
22034            return bReturnVal;
22035        
22036        },
22037        
22038        
22039        /**
22040        * @method _isSplitButtonOptionKey
22041        * @description Determines if the specified keycode is one that toggles  
22042        * the display of the split button's menu.
22043        * @protected
22044        * @param {Event} p_oEvent Object representing the DOM event object  
22045        * passed back by the event utility (YAHOO.util.Event).
22046        * @return {Boolean}
22047        */
22048        _isSplitButtonOptionKey: function (p_oEvent) {
22049
22050			var bShowMenu = (Event.getCharCode(p_oEvent) == 40);
22051
22052
22053			var onKeyPress = function (p_oEvent) {
22054
22055				Event.preventDefault(p_oEvent);
22056
22057				this.removeListener("keypress", onKeyPress);
22058			
22059			};
22060
22061
22062			// Prevent the browser from scrolling the window
22063			if (bShowMenu) {
22064
22065				if (UA.opera) {
22066	
22067					this.on("keypress", onKeyPress);
22068	
22069				}
22070
22071				Event.preventDefault(p_oEvent);
22072			}
22073
22074            return bShowMenu;
22075        
22076        },
22077        
22078        
22079        /**
22080        * @method _addListenersToForm
22081        * @description Adds event handlers to the button's form.
22082        * @protected
22083        */
22084        _addListenersToForm: function () {
22085        
22086            var oForm = this.getForm(),
22087                onFormKeyPress = YAHOO.widget.Button.onFormKeyPress,
22088                bHasKeyPressListener,
22089                oSrcElement,
22090                aListeners,
22091                nListeners,
22092                i;
22093        
22094        
22095            if (oForm) {
22096        
22097                Event.on(oForm, "reset", this._onFormReset, null, this);
22098                Event.on(oForm, "submit", this._onFormSubmit, null, this);
22099        
22100                oSrcElement = this.get("srcelement");
22101        
22102        
22103                if (this.get("type") == "submit" || 
22104                    (oSrcElement && oSrcElement.type == "submit")) 
22105                {
22106                
22107                    aListeners = Event.getListeners(oForm, "keypress");
22108                    bHasKeyPressListener = false;
22109            
22110                    if (aListeners) {
22111            
22112                        nListeners = aListeners.length;
22113        
22114                        if (nListeners > 0) {
22115            
22116                            i = nListeners - 1;
22117                            
22118                            do {
22119               
22120                                if (aListeners[i].fn == onFormKeyPress) {
22121                
22122                                    bHasKeyPressListener = true;
22123                                    break;
22124                                
22125                                }
22126                
22127                            }
22128                            while (i--);
22129                        
22130                        }
22131                    
22132                    }
22133            
22134            
22135                    if (!bHasKeyPressListener) {
22136               
22137                        Event.on(oForm, "keypress", onFormKeyPress);
22138            
22139                    }
22140        
22141                }
22142            
22143            }
22144        
22145        },
22146        
22147        
22148        
22149        /**
22150        * @method _showMenu
22151        * @description Shows the button's menu.
22152        * @protected
22153        * @param {Event} p_oEvent Object representing the DOM event object 
22154        * passed back by the event utility (YAHOO.util.Event) that triggered 
22155        * the display of the menu.
22156        */
22157        _showMenu: function (p_oEvent) {
22158
22159            if (YAHOO.widget.MenuManager) {
22160                YAHOO.widget.MenuManager.hideVisible();
22161            }
22162
22163        
22164            if (m_oOverlayManager) {
22165                m_oOverlayManager.hideAll();
22166            }
22167
22168
22169            var oMenu = this._menu,
22170            	aMenuAlignment = this.get("menualignment"),
22171            	bFocusMenu = this.get("focusmenu"),
22172				fnFocusMethod;
22173
22174
22175			if (this._renderedMenu) {
22176
22177				oMenu.cfg.setProperty("context", 
22178								[this.get("element"), aMenuAlignment[0], aMenuAlignment[1]]);
22179	
22180				oMenu.cfg.setProperty("preventcontextoverlap", true);
22181				oMenu.cfg.setProperty("constraintoviewport", true);
22182
22183			}
22184			else {
22185
22186				oMenu.cfg.queueProperty("context", 
22187								[this.get("element"), aMenuAlignment[0], aMenuAlignment[1]]);
22188	
22189				oMenu.cfg.queueProperty("preventcontextoverlap", true);
22190				oMenu.cfg.queueProperty("constraintoviewport", true);
22191			
22192			}
22193
22194
22195			/*
22196				 Refocus the Button before showing its Menu in case the call to 
22197				 YAHOO.widget.MenuManager.hideVisible() resulted in another element in the 
22198				 DOM being focused after another Menu was hidden.
22199			*/
22200			
22201			this.focus();
22202
22203
22204            if (Menu && oMenu && (oMenu instanceof Menu)) {
22205
22206				// Since Menus automatically focus themselves when made visible, temporarily 
22207				// replace the Menu focus method so that the value of the Button's "focusmenu"
22208				// attribute determines if the Menu should be focus when made visible.
22209
22210				fnFocusMethod = oMenu.focus;
22211
22212				oMenu.focus = function () {};
22213
22214				if (this._renderedMenu) {
22215
22216					oMenu.cfg.setProperty("minscrollheight", this.get("menuminscrollheight"));
22217					oMenu.cfg.setProperty("maxheight", this.get("menumaxheight"));
22218				
22219				}
22220				else {
22221
22222					oMenu.cfg.queueProperty("minscrollheight", this.get("menuminscrollheight"));
22223					oMenu.cfg.queueProperty("maxheight", this.get("menumaxheight"));
22224				
22225				}
22226
22227
22228                oMenu.show();
22229
22230        		oMenu.focus = fnFocusMethod;
22231
22232				oMenu.align();
22233        
22234
22235                /*
22236                    Stop the propagation of the event so that the MenuManager 
22237                    doesn't blur the menu after it gets focus.
22238                */
22239        
22240                if (p_oEvent.type == "mousedown") {
22241                    Event.stopPropagation(p_oEvent);
22242                }
22243
22244        
22245                if (bFocusMenu) { 
22246                    oMenu.focus();
22247                }
22248
22249            }
22250            else if (Overlay && oMenu && (oMenu instanceof Overlay)) {
22251
22252				if (!this._renderedMenu) {
22253		            oMenu.render(this.get("element").parentNode);
22254				}
22255
22256                oMenu.show();
22257				oMenu.align();
22258
22259            }
22260        
22261        },
22262        
22263        
22264        /**
22265        * @method _hideMenu
22266        * @description Hides the button's menu.
22267        * @protected
22268        */
22269        _hideMenu: function () {
22270        
22271            var oMenu = this._menu;
22272        
22273            if (oMenu) {
22274        
22275                oMenu.hide();
22276        
22277            }
22278        
22279        },
22280        
22281        
22282        
22283        
22284        // Protected event handlers
22285        
22286        
22287        /**
22288        * @method _onMouseOver
22289        * @description "mouseover" event handler for the button.
22290        * @protected
22291        * @param {Event} p_oEvent Object representing the DOM event object  
22292        * passed back by the event utility (YAHOO.util.Event).
22293        */
22294        _onMouseOver: function (p_oEvent) {
22295        
22296        	var sType = this.get("type"),
22297        		oElement,
22298				nOptionRegionX;
22299
22300
22301			if (sType === "split") {
22302
22303				oElement = this.get("element");
22304				nOptionRegionX = 
22305					(Dom.getX(oElement) + (oElement.offsetWidth - this.OPTION_AREA_WIDTH));
22306					
22307				this._nOptionRegionX = nOptionRegionX;
22308			
22309			}
22310        
22311
22312            if (!this._hasMouseEventHandlers) {
22313        
22314				if (sType === "split") {
22315        
22316	        		this.on("mousemove", this._onMouseMove);
22317
22318        		}
22319
22320                this.on("mouseout", this._onMouseOut);
22321        
22322                this._hasMouseEventHandlers = true;
22323        
22324            }
22325        
22326
22327            this.addStateCSSClasses("hover");
22328
22329
22330			if (sType === "split" && (Event.getPageX(p_oEvent) > nOptionRegionX)) {
22331	
22332				this.addStateCSSClasses("hoveroption");
22333	
22334			}
22335
22336        
22337            if (this._activationButtonPressed) {
22338        
22339                this.addStateCSSClasses("active");
22340        
22341            }
22342        
22343        
22344            if (this._bOptionPressed) {
22345        
22346                this.addStateCSSClasses("activeoption");
22347            
22348            }
22349
22350
22351            if (this._activationButtonPressed || this._bOptionPressed) {
22352        
22353                Event.removeListener(document, "mouseup", this._onDocumentMouseUp);
22354        
22355            }
22356
22357        },
22358
22359
22360        /**
22361        * @method _onMouseMove
22362        * @description "mousemove" event handler for the button.
22363        * @protected
22364        * @param {Event} p_oEvent Object representing the DOM event object  
22365        * passed back by the event utility (YAHOO.util.Event).
22366        */        
22367        _onMouseMove: function (p_oEvent) {
22368        
22369        	var nOptionRegionX = this._nOptionRegionX;
22370        
22371        	if (nOptionRegionX) {
22372
22373				if (Event.getPageX(p_oEvent) > nOptionRegionX) {
22374					
22375					this.addStateCSSClasses("hoveroption");
22376	
22377				}
22378				else {
22379
22380					this.removeStateCSSClasses("hoveroption");
22381				
22382				}
22383				
22384        	}
22385        
22386        },
22387        
22388        /**
22389        * @method _onMouseOut
22390        * @description "mouseout" event handler for the button.
22391        * @protected
22392        * @param {Event} p_oEvent Object representing the DOM event object  
22393        * passed back by the event utility (YAHOO.util.Event).
22394        */
22395        _onMouseOut: function (p_oEvent) {
22396
22397			var sType = this.get("type");
22398        
22399            this.removeStateCSSClasses("hover");
22400        
22401
22402            if (sType != "menu") {
22403        
22404                this.removeStateCSSClasses("active");
22405        
22406            }
22407        
22408
22409            if (this._activationButtonPressed || this._bOptionPressed) {
22410        
22411                Event.on(document, "mouseup", this._onDocumentMouseUp, null, this);
22412        
22413            }
22414
22415
22416			if (sType === "split" && (Event.getPageX(p_oEvent) > this._nOptionRegionX)) {
22417			
22418				this.removeStateCSSClasses("hoveroption");
22419	
22420			}
22421            
22422        },
22423        
22424        
22425        /**
22426        * @method _onDocumentMouseUp
22427        * @description "mouseup" event handler for the button.
22428        * @protected
22429        * @param {Event} p_oEvent Object representing the DOM event object  
22430        * passed back by the event utility (YAHOO.util.Event).
22431        */
22432        _onDocumentMouseUp: function (p_oEvent) {
22433        
22434            this._activationButtonPressed = false;
22435            this._bOptionPressed = false;
22436        
22437            var sType = this.get("type"),
22438                oTarget,
22439                oMenuElement;
22440        
22441            if (sType == "menu" || sType == "split") {
22442
22443                oTarget = Event.getTarget(p_oEvent);
22444                oMenuElement = this._menu.element;
22445        
22446                if (oTarget != oMenuElement && 
22447                    !Dom.isAncestor(oMenuElement, oTarget)) {
22448
22449                    this.removeStateCSSClasses((sType == "menu" ? 
22450                        "active" : "activeoption"));
22451            
22452                    this._hideMenu();
22453
22454                }
22455        
22456            }
22457        
22458            Event.removeListener(document, "mouseup", this._onDocumentMouseUp);
22459        
22460        },
22461        
22462        
22463        /**
22464        * @method _onMouseDown
22465        * @description "mousedown" event handler for the button.
22466        * @protected
22467        * @param {Event} p_oEvent Object representing the DOM event object  
22468        * passed back by the event utility (YAHOO.util.Event).
22469        */
22470        _onMouseDown: function (p_oEvent) {
22471
22472            var sType,
22473            	bReturnVal = true;
22474        
22475        
22476            function onMouseUp() {
22477            
22478                this._hideMenu();
22479                this.removeListener("mouseup", onMouseUp);
22480            
22481            }
22482        
22483        
22484            if ((p_oEvent.which || p_oEvent.button) == 1) {
22485        
22486        
22487                if (!this.hasFocus()) {
22488                    Lang.later(0, this, this.focus);
22489                    //this.focus();
22490                }
22491        
22492        
22493                sType = this.get("type");
22494        
22495        
22496                if (sType == "split") {
22497                
22498                    if (Event.getPageX(p_oEvent) > this._nOptionRegionX) {
22499                        
22500                        this.fireEvent("option", p_oEvent);
22501						bReturnVal = false;
22502        
22503                    }
22504                    else {
22505        
22506                        this.addStateCSSClasses("active");
22507        
22508                        this._activationButtonPressed = true;
22509        
22510                    }
22511        
22512                }
22513                else if (sType == "menu") {
22514        
22515                    if (this.isActive()) {
22516        
22517                        this._hideMenu();
22518        
22519                        this._activationButtonPressed = false;
22520        
22521                    }
22522                    else {
22523        
22524                        this._showMenu(p_oEvent);
22525        
22526                        this._activationButtonPressed = true;
22527                    
22528                    }
22529        
22530                }
22531                else {
22532        
22533                    this.addStateCSSClasses("active");
22534        
22535                    this._activationButtonPressed = true;
22536                
22537                }
22538        
22539        
22540        
22541                if (sType == "split" || sType == "menu") {
22542
22543                    this._hideMenuTimer = Lang.later(250, this, this.on, ["mouseup", onMouseUp]);
22544        
22545                }
22546        
22547            }
22548            
22549            return bReturnVal;
22550            
22551        },
22552        
22553        
22554        /**
22555        * @method _onMouseUp
22556        * @description "mouseup" event handler for the button.
22557        * @protected
22558        * @param {Event} p_oEvent Object representing the DOM event object  
22559        * passed back by the event utility (YAHOO.util.Event).
22560        */
22561        _onMouseUp: function (p_oEvent) {
22562            this.inMouseDown = false;
22563        
22564            var sType = this.get("type"),
22565            	oHideMenuTimer = this._hideMenuTimer,
22566            	bReturnVal = true;
22567        
22568        
22569            if (oHideMenuTimer) {
22570  
22571  				oHideMenuTimer.cancel();
22572        
22573            }
22574        
22575        
22576            if (sType == "checkbox" || sType == "radio") {
22577                if ((p_oEvent.which || p_oEvent.button) != 1) {
22578                    return;
22579                }
22580
22581                this.set("checked", !(this.get("checked")));
22582            
22583            }
22584        
22585        
22586            this._activationButtonPressed = false;
22587            
22588        
22589            if (sType != "menu") {
22590        
22591                this.removeStateCSSClasses("active");
22592            
22593            }
22594
22595                
22596			if (sType == "split" && Event.getPageX(p_oEvent) > this._nOptionRegionX) {
22597				
22598				bReturnVal = false;
22599
22600			}
22601
22602			return bReturnVal;
22603            
22604        },
22605        
22606        
22607        /**
22608        * @method _onFocus
22609        * @description "focus" event handler for the button.
22610        * @protected
22611        * @param {Event} p_oEvent Object representing the DOM event object  
22612        * passed back by the event utility (YAHOO.util.Event).
22613        */
22614        _onFocus: function (p_oEvent) {
22615        
22616            var oElement;
22617        
22618            this.addStateCSSClasses("focus");
22619        
22620            if (this._activationKeyPressed) {
22621        
22622                this.addStateCSSClasses("active");
22623           
22624            }
22625        
22626            m_oFocusedButton = this;
22627        
22628        
22629            if (!this._hasKeyEventHandlers) {
22630        
22631                oElement = this._button;
22632        
22633                Event.on(oElement, "blur", this._onBlur, null, this);
22634                Event.on(oElement, "keydown", this._onKeyDown, null, this);
22635                Event.on(oElement, "keyup", this._onKeyUp, null, this);
22636        
22637                this._hasKeyEventHandlers = true;
22638        
22639            }
22640        
22641        
22642            this.fireEvent("focus", p_oEvent);
22643        
22644        },
22645        
22646        
22647        /**
22648        * @method _onBlur
22649        * @description "blur" event handler for the button.
22650        * @protected
22651        * @param {Event} p_oEvent Object representing the DOM event object  
22652        * passed back by the event utility (YAHOO.util.Event).
22653        */
22654        _onBlur: function (p_oEvent) {
22655        
22656            this.removeStateCSSClasses("focus");
22657        
22658            if (this.get("type") != "menu") {
22659        
22660                this.removeStateCSSClasses("active");
22661
22662            }    
22663        
22664            if (this._activationKeyPressed) {
22665        
22666                Event.on(document, "keyup", this._onDocumentKeyUp, null, this);
22667        
22668            }
22669        
22670        
22671            m_oFocusedButton = null;
22672        
22673            this.fireEvent("blur", p_oEvent);
22674           
22675        },
22676        
22677        
22678        /**
22679        * @method _onDocumentKeyUp
22680        * @description "keyup" event handler for the document.
22681        * @protected
22682        * @param {Event} p_oEvent Object representing the DOM event object  
22683        * passed back by the event utility (YAHOO.util.Event).
22684        */
22685        _onDocumentKeyUp: function (p_oEvent) {
22686        
22687            if (this._isActivationKey(Event.getCharCode(p_oEvent))) {
22688        
22689                this._activationKeyPressed = false;
22690                
22691                Event.removeListener(document, "keyup", this._onDocumentKeyUp);
22692            
22693            }
22694        
22695        },
22696        
22697        
22698        /**
22699        * @method _onKeyDown
22700        * @description "keydown" event handler for the button.
22701        * @protected
22702        * @param {Event} p_oEvent Object representing the DOM event object  
22703        * passed back by the event utility (YAHOO.util.Event).
22704        */
22705        _onKeyDown: function (p_oEvent) {
22706        
22707            var oMenu = this._menu;
22708        
22709        
22710            if (this.get("type") == "split" && 
22711                this._isSplitButtonOptionKey(p_oEvent)) {
22712        
22713                this.fireEvent("option", p_oEvent);
22714        
22715            }
22716            else if (this._isActivationKey(Event.getCharCode(p_oEvent))) {
22717        
22718                if (this.get("type") == "menu") {
22719        
22720                    this._showMenu(p_oEvent);
22721        
22722                }
22723                else {
22724        
22725                    this._activationKeyPressed = true;
22726                    
22727                    this.addStateCSSClasses("active");
22728                
22729                }
22730            
22731            }
22732        
22733        
22734            if (oMenu && oMenu.cfg.getProperty("visible") && 
22735                Event.getCharCode(p_oEvent) == 27) {
22736            
22737                oMenu.hide();
22738                this.focus();
22739            
22740            }
22741        
22742        },
22743        
22744        
22745        /**
22746        * @method _onKeyUp
22747        * @description "keyup" event handler for the button.
22748        * @protected
22749        * @param {Event} p_oEvent Object representing the DOM event object  
22750        * passed back by the event utility (YAHOO.util.Event).
22751        */
22752        _onKeyUp: function (p_oEvent) {
22753        
22754            var sType;
22755        
22756            if (this._isActivationKey(Event.getCharCode(p_oEvent))) {
22757        
22758                sType = this.get("type");
22759        
22760                if (sType == "checkbox" || sType == "radio") {
22761        
22762                    this.set("checked", !(this.get("checked")));
22763                
22764                }
22765        
22766                this._activationKeyPressed = false;
22767        
22768                if (this.get("type") != "menu") {
22769        
22770                    this.removeStateCSSClasses("active");
22771        
22772                }
22773        
22774            }
22775        
22776        },
22777        
22778        
22779        /**
22780        * @method _onClick
22781        * @description "click" event handler for the button.
22782        * @protected
22783        * @param {Event} p_oEvent Object representing the DOM event object  
22784        * passed back by the event utility (YAHOO.util.Event).
22785        */
22786        _onClick: function (p_oEvent) {
22787        
22788            var sType = this.get("type"),
22789                oForm,
22790                oSrcElement,
22791                bReturnVal;
22792        
22793
22794			switch (sType) {
22795
22796			case "submit":
22797
22798				if (p_oEvent.returnValue !== false) {
22799
22800					this.submitForm();
22801
22802				}
22803
22804				break;
22805
22806			case "reset":
22807
22808				oForm = this.getForm();
22809
22810				if (oForm) {
22811
22812					oForm.reset();
22813
22814				}
22815
22816				break;
22817
22818
22819			case "split":
22820
22821				if (this._nOptionRegionX > 0 && 
22822						(Event.getPageX(p_oEvent) > this._nOptionRegionX)) {
22823
22824					bReturnVal = false;
22825
22826				}
22827				else {
22828
22829					this._hideMenu();
22830
22831					oSrcElement = this.get("srcelement");
22832
22833					if (oSrcElement && oSrcElement.type == "submit" && 
22834							p_oEvent.returnValue !== false) {
22835
22836						this.submitForm();
22837
22838					}
22839
22840				}
22841
22842				break;
22843
22844			}
22845
22846			return bReturnVal;
22847        
22848        },
22849        
22850        
22851        /**
22852        * @method _onDblClick
22853        * @description "dblclick" event handler for the button.
22854        * @protected
22855        * @param {Event} p_oEvent Object representing the DOM event object  
22856        * passed back by the event utility (YAHOO.util.Event).
22857        */
22858        _onDblClick: function (p_oEvent) {
22859        
22860            var bReturnVal = true;
22861    
22862			if (this.get("type") == "split" && Event.getPageX(p_oEvent) > this._nOptionRegionX) {
22863
22864				bReturnVal = false;
22865			
22866			}
22867        
22868        	return bReturnVal;
22869        
22870        },        
22871        
22872        
22873        /**
22874        * @method _onAppendTo
22875        * @description "appendTo" event handler for the button.
22876        * @protected
22877        * @param {Event} p_oEvent Object representing the DOM event object  
22878        * passed back by the event utility (YAHOO.util.Event).
22879        */
22880        _onAppendTo: function (p_oEvent) {
22881        
22882            /*
22883                It is necessary to call "_addListenersToForm" using 
22884                "setTimeout" to make sure that the button's "form" property 
22885                returns a node reference.  Sometimes, if you try to get the 
22886                reference immediately after appending the field, it is null.
22887            */
22888        
22889            Lang.later(0, this, this._addListenersToForm);
22890        
22891        },
22892        
22893        
22894        /**
22895        * @method _onFormReset
22896        * @description "reset" event handler for the button's form.
22897        * @protected
22898        * @param {Event} p_oEvent Object representing the DOM event 
22899        * object passed back by the event utility (YAHOO.util.Event).
22900        */
22901        _onFormReset: function (p_oEvent) {
22902        
22903            var sType = this.get("type"),
22904                oMenu = this._menu;
22905        
22906            if (sType == "checkbox" || sType == "radio") {
22907        
22908                this.resetValue("checked");
22909        
22910            }
22911        
22912        
22913            if (Menu && oMenu && (oMenu instanceof Menu)) {
22914        
22915                this.resetValue("selectedMenuItem");
22916        
22917            }
22918        
22919        },
22920
22921
22922        /**
22923        * @method _onFormSubmit
22924        * @description "submit" event handler for the button's form.
22925        * @protected
22926        * @param {Event} p_oEvent Object representing the DOM event 
22927        * object passed back by the event utility (YAHOO.util.Event).
22928        */        
22929        _onFormSubmit: function (p_oEvent) {
22930        
22931        	this.createHiddenFields();
22932        
22933        },
22934        
22935        
22936        /**
22937        * @method _onDocumentMouseDown
22938        * @description "mousedown" event handler for the document.
22939        * @protected
22940        * @param {Event} p_oEvent Object representing the DOM event object  
22941        * passed back by the event utility (YAHOO.util.Event).
22942        */
22943        _onDocumentMouseDown: function (p_oEvent) {
22944
22945            var oTarget = Event.getTarget(p_oEvent),
22946                oButtonElement = this.get("element"),
22947                oMenuElement = this._menu.element;
22948           
22949            function findTargetInSubmenus(aSubmenus) {
22950                var i, iMax, oSubmenuElement;
22951                if (!aSubmenus) {
22952                    return true;
22953                }
22954                for (i = 0, iMax = aSubmenus.length; i < iMax; i++) {
22955                    oSubmenuElement = aSubmenus[i].element;
22956                    if (oTarget == oSubmenuElement || Dom.isAncestor(oSubmenuElement, oTarget)) {
22957                        return true;
22958                    }
22959                    if (aSubmenus[i] && aSubmenus[i].getSubmenus) {
22960                        if (findTargetInSubmenus(aSubmenus[i].getSubmenus())) {
22961                            return true;
22962                        }
22963                    }
22964                }
22965        
22966                return false;
22967            }
22968
22969            if (oTarget != oButtonElement && 
22970                !Dom.isAncestor(oButtonElement, oTarget) && 
22971                oTarget != oMenuElement && 
22972                !Dom.isAncestor(oMenuElement, oTarget)) {
22973                
22974                
22975                if (this._menu  && this._menu.getSubmenus) {
22976                    if (findTargetInSubmenus(this._menu.getSubmenus())) {
22977                        return;
22978                    }
22979                }
22980                
22981
22982                this._hideMenu();
22983
22984				//	In IE when the user mouses down on a focusable element
22985				//	that element will be focused and become the "activeElement".
22986				//	(http://msdn.microsoft.com/en-us/library/ms533065(VS.85).aspx)
22987				//	However, there is a bug in IE where if there is a  
22988				//	positioned element with a focused descendant that is 
22989				//	hidden in response to the mousedown event, the target of 
22990				//	the mousedown event will appear to have focus, but will 
22991				//	not be set as the activeElement.  This will result 
22992				//	in the element not firing key events, even though it
22993				//	appears to have focus.	The following call to "setActive"
22994				//	fixes this bug.
22995
22996                if (UA.ie && (UA.ie < 9) && oTarget.focus) {
22997					oTarget.setActive();
22998				}
22999        
23000                Event.removeListener(document, "mousedown", 
23001                    this._onDocumentMouseDown);    
23002            
23003            }
23004        
23005        },
23006        
23007        
23008        /**
23009        * @method _onOption
23010        * @description "option" event handler for the button.
23011        * @protected
23012        * @param {Event} p_oEvent Object representing the DOM event object  
23013        * passed back by the event utility (YAHOO.util.Event).
23014        */
23015        _onOption: function (p_oEvent) {
23016        
23017            if (this.hasClass(this.CLASS_NAME_PREFIX + "split-button-activeoption")) {
23018        
23019                this._hideMenu();
23020        
23021                this._bOptionPressed = false;
23022        
23023            }
23024            else {
23025        
23026                this._showMenu(p_oEvent);    
23027        
23028                this._bOptionPressed = true;
23029        
23030            }
23031        
23032        },
23033        
23034        
23035        /**
23036        * @method _onMenuShow
23037        * @description "show" event handler for the button's menu.
23038        * @private
23039        * @param {String} p_sType String representing the name of the event  
23040        * that was fired.
23041        */
23042        _onMenuShow: function (p_sType) {
23043        
23044            Event.on(document, "mousedown", this._onDocumentMouseDown, 
23045                null, this);
23046        
23047            var sState = (this.get("type") == "split") ? "activeoption" : "active";
23048        
23049            this.addStateCSSClasses(sState);
23050        
23051        },
23052        
23053        
23054        /**
23055        * @method _onMenuHide
23056        * @description "hide" event handler for the button's menu.
23057        * @private
23058        * @param {String} p_sType String representing the name of the event  
23059        * that was fired.
23060        */
23061        _onMenuHide: function (p_sType) {
23062            
23063            var sState = (this.get("type") == "split") ? "activeoption" : "active";
23064        
23065            this.removeStateCSSClasses(sState);
23066        
23067        
23068            if (this.get("type") == "split") {
23069        
23070                this._bOptionPressed = false;
23071            
23072            }
23073        
23074        },
23075        
23076        
23077        /**
23078        * @method _onMenuKeyDown
23079        * @description "keydown" event handler for the button's menu.
23080        * @private
23081        * @param {String} p_sType String representing the name of the event  
23082        * that was fired.
23083        * @param {Array} p_aArgs Array of arguments sent when the event 
23084        * was fired.
23085        */
23086        _onMenuKeyDown: function (p_sType, p_aArgs) {
23087        
23088            var oEvent = p_aArgs[0];
23089        
23090            if (Event.getCharCode(oEvent) == 27) {
23091        
23092                this.focus();
23093        
23094                if (this.get("type") == "split") {
23095                
23096                    this._bOptionPressed = false;
23097                
23098                }
23099        
23100            }
23101        
23102        },
23103        
23104        
23105        /**
23106        * @method _onMenuRender
23107        * @description "render" event handler for the button's menu.
23108        * @private
23109        * @param {String} p_sType String representing the name of the  
23110        * event thatwas fired.
23111        */
23112        _onMenuRender: function (p_sType) {
23113        
23114            var oButtonElement = this.get("element"),
23115                oButtonParent = oButtonElement.parentNode,
23116				oMenu = this._menu,
23117                oMenuElement = oMenu.element,
23118				oSrcElement = oMenu.srcElement,
23119				oItem;
23120        
23121        
23122            if (oButtonParent != oMenuElement.parentNode) {
23123        
23124                oButtonParent.appendChild(oMenuElement);
23125            
23126            }
23127
23128			this._renderedMenu = true;
23129
23130			//	If the user has designated an <option> of the Menu's source 
23131			//	<select> element to be selected, sync the selectedIndex with 
23132			//	the "selectedMenuItem" Attribute.
23133
23134			if (oSrcElement && 
23135					oSrcElement.nodeName.toLowerCase() === "select" && 
23136					oSrcElement.value) {
23137				
23138				
23139				oItem = oMenu.getItem(oSrcElement.selectedIndex);
23140				
23141				//	Set the value of the "selectedMenuItem" attribute
23142				//	silently since this is the initial set--synchronizing 
23143				//	the value of the source <SELECT> element in the DOM with 
23144				//	its corresponding Menu instance.
23145
23146				this.set("selectedMenuItem", oItem, true);
23147				
23148				//	Call the "_onSelectedMenuItemChange" method since the 
23149				//	attribute was set silently.
23150
23151				this._onSelectedMenuItemChange({ newValue: oItem });
23152				
23153			}
23154
23155        },
23156
23157        
23158        
23159        /**
23160        * @method _onMenuClick
23161        * @description "click" event handler for the button's menu.
23162        * @private
23163        * @param {String} p_sType String representing the name of the event  
23164        * that was fired.
23165        * @param {Array} p_aArgs Array of arguments sent when the event 
23166        * was fired.
23167        */
23168        _onMenuClick: function (p_sType, p_aArgs) {
23169
23170            var oItem = p_aArgs[1],
23171                oSrcElement;
23172        
23173            if (oItem) {
23174        
23175				this.set("selectedMenuItem", oItem);
23176
23177                oSrcElement = this.get("srcelement");
23178            
23179                if (oSrcElement && oSrcElement.type == "submit") {
23180        
23181                    this.submitForm();
23182            
23183                }
23184            
23185                this._hideMenu();
23186            
23187            }
23188        
23189        },
23190
23191
23192        /**
23193        * @method _onSelectedMenuItemChange
23194        * @description "selectedMenuItemChange" event handler for the Button's
23195		* "selectedMenuItem" attribute.
23196        * @param {Event} event Object representing the DOM event object  
23197        * passed back by the event utility (YAHOO.util.Event).
23198        */
23199		_onSelectedMenuItemChange: function (event) {
23200		
23201			var oSelected = event.prevValue,
23202				oItem = event.newValue,
23203				sPrefix = this.CLASS_NAME_PREFIX;
23204
23205			if (oSelected) {
23206				Dom.removeClass(oSelected.element, (sPrefix + "button-selectedmenuitem"));
23207			}
23208			
23209			if (oItem) {
23210				Dom.addClass(oItem.element, (sPrefix + "button-selectedmenuitem"));
23211			}
23212			
23213		},        
23214        
23215
23216        /**
23217        * @method _onLabelClick
23218        * @description "click" event handler for the Button's
23219		* <code>&#60;label&#62;</code> element.
23220        * @param {Event} event Object representing the DOM event object  
23221        * passed back by the event utility (YAHOO.util.Event).
23222        */
23223		_onLabelClick: function (event) {
23224
23225			this.focus();
23226
23227			var sType = this.get("type");
23228
23229			if (sType == "radio" || sType == "checkbox") {
23230				this.set("checked", (!this.get("checked")));						
23231			}
23232			
23233		},
23234
23235        
23236        // Public methods
23237        
23238        
23239        /**
23240        * @method createButtonElement
23241        * @description Creates the button's HTML elements.
23242        * @param {String} p_sType String indicating the type of element 
23243        * to create.
23244        * @return {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
23245        * level-one-html.html#ID-58190037">HTMLElement</a>}
23246        */
23247        createButtonElement: function (p_sType) {
23248        
23249            var sNodeName = this.NODE_NAME,
23250                oElement = document.createElement(sNodeName);
23251        
23252            oElement.innerHTML =  "<" + sNodeName + " class=\"first-child\">" + 
23253                (p_sType == "link" ? "<a></a>" : 
23254                "<button type=\"button\"></button>") + "</" + sNodeName + ">";
23255        
23256            return oElement;
23257        
23258        },
23259
23260        
23261        /**
23262        * @method addStateCSSClasses
23263        * @description Appends state-specific CSS classes to the button's root 
23264        * DOM element.
23265        */
23266        addStateCSSClasses: function (p_sState) {
23267        
23268            var sType = this.get("type"),
23269				sPrefix = this.CLASS_NAME_PREFIX;
23270        
23271            if (Lang.isString(p_sState)) {
23272        
23273                if (p_sState != "activeoption" && p_sState != "hoveroption") {
23274        
23275                    this.addClass(sPrefix + this.CSS_CLASS_NAME + ("-" + p_sState));
23276        
23277                }
23278        
23279                this.addClass(sPrefix + sType + ("-button-" + p_sState));
23280            
23281            }
23282        
23283        },
23284        
23285        
23286        /**
23287        * @method removeStateCSSClasses
23288        * @description Removes state-specific CSS classes to the button's root 
23289        * DOM element.
23290        */
23291        removeStateCSSClasses: function (p_sState) {
23292        
23293            var sType = this.get("type"),
23294				sPrefix = this.CLASS_NAME_PREFIX;
23295        
23296            if (Lang.isString(p_sState)) {
23297        
23298                this.removeClass(sPrefix + this.CSS_CLASS_NAME + ("-" + p_sState));
23299                this.removeClass(sPrefix + sType + ("-button-" + p_sState));
23300            
23301            }
23302        
23303        },
23304        
23305        
23306        /**
23307        * @method createHiddenFields
23308        * @description Creates the button's hidden form field and appends it 
23309        * to its parent form.
23310        * @return {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
23311        * level-one-html.html#ID-6043025">HTMLInputElement</a>|Array}
23312        */
23313        createHiddenFields: function () {
23314        
23315            this.removeHiddenFields();
23316        
23317            var oForm = this.getForm(),
23318                oButtonField,
23319                sType,
23320                bCheckable,
23321                oMenu,
23322                oMenuItem,
23323                sButtonName,
23324                oValue,
23325                oMenuField,
23326                oReturnVal,
23327				sMenuFieldName,
23328				oMenuSrcElement,
23329				bMenuSrcElementIsSelect = false;
23330        
23331        
23332            if (oForm && !this.get("disabled")) {
23333        
23334                sType = this.get("type");
23335                bCheckable = (sType == "checkbox" || sType == "radio");
23336        
23337        
23338                if ((bCheckable && this.get("checked")) || (m_oSubmitTrigger == this)) {
23339                
23340                    YAHOO.log("Creating hidden field.", "info", this.toString());
23341        
23342                    oButtonField = createInputElement((bCheckable ? sType : "hidden"),
23343                                    this.get("name"), this.get("value"), this.get("checked"));
23344            
23345            
23346                    if (oButtonField) {
23347            
23348                        if (bCheckable) {
23349            
23350                            oButtonField.style.display = "none";
23351            
23352                        }
23353            
23354                        oForm.appendChild(oButtonField);
23355            
23356                    }
23357        
23358                }
23359                    
23360        
23361                oMenu = this._menu;
23362            
23363            
23364                if (Menu && oMenu && (oMenu instanceof Menu)) {
23365        
23366                    YAHOO.log("Creating hidden field for menu.", "info", this.toString());
23367        
23368                    oMenuItem = this.get("selectedMenuItem");
23369					oMenuSrcElement = oMenu.srcElement;
23370					bMenuSrcElementIsSelect = (oMenuSrcElement && 
23371												oMenuSrcElement.nodeName.toUpperCase() == "SELECT");
23372
23373                    if (oMenuItem) {
23374
23375						oValue = (oMenuItem.value === null || oMenuItem.value === "") ? 
23376									oMenuItem.cfg.getProperty("text") : oMenuItem.value;
23377
23378						sButtonName = this.get("name");
23379
23380
23381						if (bMenuSrcElementIsSelect) {
23382						
23383							sMenuFieldName = oMenuSrcElement.name;
23384						
23385						}
23386						else if (sButtonName) {
23387
23388							sMenuFieldName = (sButtonName + "_options");
23389						
23390						}
23391						
23392
23393						if (oValue && sMenuFieldName) {
23394		
23395							oMenuField = createInputElement("hidden", sMenuFieldName, oValue);
23396							oForm.appendChild(oMenuField);
23397		
23398						}
23399                    
23400                    }
23401                    else if (bMenuSrcElementIsSelect) {
23402					
23403						oMenuField = oForm.appendChild(oMenuSrcElement);
23404                    
23405                    }
23406        
23407                }
23408            
23409            
23410                if (oButtonField && oMenuField) {
23411        
23412                    this._hiddenFields = [oButtonField, oMenuField];
23413        
23414                }
23415                else if (!oButtonField && oMenuField) {
23416        
23417                    this._hiddenFields = oMenuField;
23418                
23419                }
23420                else if (oButtonField && !oMenuField) {
23421        
23422                    this._hiddenFields = oButtonField;
23423                
23424                }
23425        
23426        		oReturnVal = this._hiddenFields;
23427        
23428            }
23429
23430			return oReturnVal;
23431        
23432        },
23433        
23434        
23435        /**
23436        * @method removeHiddenFields
23437        * @description Removes the button's hidden form field(s) from its 
23438        * parent form.
23439        */
23440        removeHiddenFields: function () {
23441        
23442            var oField = this._hiddenFields,
23443                nFields,
23444                i;
23445        
23446            function removeChild(p_oElement) {
23447        
23448                if (Dom.inDocument(p_oElement)) {
23449        
23450                    p_oElement.parentNode.removeChild(p_oElement);
23451
23452                }
23453                
23454            }
23455            
23456        
23457            if (oField) {
23458        
23459                if (Lang.isArray(oField)) {
23460        
23461                    nFields = oField.length;
23462                    
23463                    if (nFields > 0) {
23464                    
23465                        i = nFields - 1;
23466                        
23467                        do {
23468        
23469                            removeChild(oField[i]);
23470        
23471                        }
23472                        while (i--);
23473                    
23474                    }
23475                
23476                }
23477                else {
23478        
23479                    removeChild(oField);
23480        
23481                }
23482        
23483                this._hiddenFields = null;
23484            
23485            }
23486        
23487        },
23488        
23489        
23490        /**
23491        * @method submitForm
23492        * @description Submits the form to which the button belongs.  Returns  
23493        * true if the form was submitted successfully, false if the submission 
23494        * was cancelled.
23495        * @protected
23496        * @return {Boolean}
23497        */
23498        submitForm: function () {
23499        
23500            var oForm = this.getForm(),
23501        
23502                oSrcElement = this.get("srcelement"),
23503        
23504                /*
23505                    Boolean indicating if the event fired successfully 
23506                    (was not cancelled by any handlers)
23507                */
23508        
23509                bSubmitForm = false,
23510                
23511                oEvent;
23512        
23513        
23514            if (oForm) {
23515        
23516                if (this.get("type") == "submit" || (oSrcElement && oSrcElement.type == "submit")) {
23517        
23518                    m_oSubmitTrigger = this;
23519                    
23520                }
23521        
23522        
23523                if (UA.ie && (UA.ie < 9)) {
23524        
23525                    bSubmitForm = oForm.fireEvent("onsubmit");
23526        
23527                }
23528                else {  // Gecko, Opera, and Safari
23529        
23530                    oEvent = document.createEvent("HTMLEvents");
23531                    oEvent.initEvent("submit", true, true);
23532        
23533                    bSubmitForm = oForm.dispatchEvent(oEvent);
23534        
23535                }
23536        
23537        
23538                /*
23539                    In IE and Safari, dispatching a "submit" event to a form 
23540                    WILL cause the form's "submit" event to fire, but WILL NOT 
23541                    submit the form.  Therefore, we need to call the "submit" 
23542                    method as well.
23543                */
23544              
23545                if ((UA.ie || UA.webkit) && bSubmitForm) {
23546        
23547                    oForm.submit();
23548                
23549                }
23550            
23551            }
23552        
23553            return bSubmitForm;
23554            
23555        },
23556        
23557        
23558        /**
23559        * @method init
23560        * @description The Button class's initialization method.
23561        * @param {String} p_oElement String specifying the id attribute of the 
23562        * <code>&#60;input&#62;</code>, <code>&#60;button&#62;</code>,
23563        * <code>&#60;a&#62;</code>, or <code>&#60;span&#62;</code> element to 
23564        * be used to create the button.
23565        * @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
23566        * level-one-html.html#ID-6043025">HTMLInputElement</a>|<a href="http://
23567        * www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-html.html
23568        * #ID-34812697">HTMLButtonElement</a>|<a href="http://www.w3.org/TR
23569        * /2000/WD-DOM-Level-1-20000929/level-one-html.html#ID-33759296">
23570        * HTMLElement</a>} p_oElement Object reference for the 
23571        * <code>&#60;input&#62;</code>, <code>&#60;button&#62;</code>, 
23572        * <code>&#60;a&#62;</code>, or <code>&#60;span&#62;</code> element to be 
23573        * used to create the button.
23574        * @param {Object} p_oElement Object literal specifying a set of 
23575        * configuration attributes used to create the button.
23576        * @param {Object} p_oAttributes Optional. Object literal specifying a 
23577        * set of configuration attributes used to create the button.
23578        */
23579        init: function (p_oElement, p_oAttributes) {
23580        
23581            var sNodeName = p_oAttributes.type == "link" ? "a" : "button",
23582                oSrcElement = p_oAttributes.srcelement,
23583                oButton = p_oElement.getElementsByTagName(sNodeName)[0],
23584                oInput;
23585
23586
23587            if (!oButton) {
23588
23589                oInput = p_oElement.getElementsByTagName("input")[0];
23590
23591
23592                if (oInput) {
23593
23594                    oButton = document.createElement("button");
23595                    oButton.setAttribute("type", "button");
23596
23597                    oInput.parentNode.replaceChild(oButton, oInput);
23598                
23599                }
23600
23601            }
23602
23603            this._button = oButton;
23604
23605
23606            YAHOO.widget.Button.superclass.init.call(this, p_oElement, p_oAttributes);
23607
23608
23609			var sId = this.get("id"),
23610				sButtonId = sId + "-button";
23611
23612
23613        	oButton.id = sButtonId;
23614
23615
23616			var aLabels,
23617				oLabel;
23618
23619
23620        	var hasLabel = function (element) {
23621        	
23622				return (element.htmlFor === sId);
23623
23624        	};
23625
23626
23627			var setLabel = function () {
23628
23629				oLabel.setAttribute((UA.ie ? "htmlFor" : "for"), sButtonId);
23630			
23631			};
23632
23633
23634			if (oSrcElement && this.get("type") != "link") {
23635
23636				aLabels = Dom.getElementsBy(hasLabel, "label");
23637
23638				if (Lang.isArray(aLabels) && aLabels.length > 0) {
23639				
23640					oLabel = aLabels[0];
23641				
23642				}
23643
23644			}
23645        
23646
23647            m_oButtons[sId] = this;
23648
23649        	var sPrefix = this.CLASS_NAME_PREFIX;
23650
23651            this.addClass(sPrefix + this.CSS_CLASS_NAME);
23652            this.addClass(sPrefix + this.get("type") + "-button");
23653        
23654            Event.on(this._button, "focus", this._onFocus, null, this);
23655            this.on("mouseover", this._onMouseOver);
23656			this.on("mousedown", this._onMouseDown);
23657			this.on("mouseup", this._onMouseUp);
23658            this.on("click", this._onClick);
23659
23660			//	Need to reset the value of the "onclick" Attribute so that any
23661			//	handlers registered via the "onclick" Attribute are fired after 
23662			//	Button's default "_onClick" listener.
23663
23664			var fnOnClick = this.get("onclick");
23665
23666			this.set("onclick", null);
23667			this.set("onclick", fnOnClick);
23668
23669            this.on("dblclick", this._onDblClick);
23670
23671
23672			var oParentNode;
23673
23674            if (oLabel) {
23675            
23676				if (this.get("replaceLabel")) {
23677
23678					this.set("label", oLabel.innerHTML);
23679					
23680					oParentNode = oLabel.parentNode;
23681					
23682					oParentNode.removeChild(oLabel);
23683					
23684				}
23685				else {
23686
23687					this.on("appendTo", setLabel); 
23688
23689					Event.on(oLabel, "click", this._onLabelClick, null, this);
23690
23691					this._label = oLabel;
23692					
23693				}
23694            
23695            }
23696            
23697            this.on("appendTo", this._onAppendTo);
23698       
23699        
23700
23701            var oContainer = this.get("container"),
23702                oElement = this.get("element"),
23703                bElInDoc = Dom.inDocument(oElement);
23704
23705
23706            if (oContainer) {
23707        
23708                if (oSrcElement && oSrcElement != oElement) {
23709                
23710                    oParentNode = oSrcElement.parentNode;
23711
23712                    if (oParentNode) {
23713                    
23714                        oParentNode.removeChild(oSrcElement);
23715                    
23716                    }
23717
23718                }
23719        
23720                if (Lang.isString(oContainer)) {
23721        
23722                    Event.onContentReady(oContainer, this.appendTo, oContainer, this);
23723        
23724                }
23725                else {
23726        
23727        			this.on("init", function () {
23728        			
23729        				Lang.later(0, this, this.appendTo, oContainer);
23730        			
23731        			});
23732        
23733                }
23734        
23735            }
23736            else if (!bElInDoc && oSrcElement && oSrcElement != oElement) {
23737
23738                oParentNode = oSrcElement.parentNode;
23739        
23740                if (oParentNode) {
23741        
23742                    this.fireEvent("beforeAppendTo", {
23743                        type: "beforeAppendTo",
23744                        target: oParentNode
23745                    });
23746            
23747                    oParentNode.replaceChild(oElement, oSrcElement);
23748            
23749                    this.fireEvent("appendTo", {
23750                        type: "appendTo",
23751                        target: oParentNode
23752                    });
23753                
23754                }
23755        
23756            }
23757            else if (this.get("type") != "link" && bElInDoc && oSrcElement && 
23758                oSrcElement == oElement) {
23759        
23760                this._addListenersToForm();
23761        
23762            }
23763        
23764            YAHOO.log("Initialization completed.", "info", this.toString());
23765        
23766
23767			this.fireEvent("init", {
23768				type: "init",
23769				target: this
23770			});        
23771        
23772        },
23773        
23774        
23775        /**
23776        * @method initAttributes
23777        * @description Initializes all of the configuration attributes used to  
23778        * create the button.
23779        * @param {Object} p_oAttributes Object literal specifying a set of 
23780        * configuration attributes used to create the button.
23781        */
23782        initAttributes: function (p_oAttributes) {
23783        
23784            var oAttributes = p_oAttributes || {};
23785        
23786            YAHOO.widget.Button.superclass.initAttributes.call(this, 
23787                oAttributes);
23788        
23789        
23790            /**
23791            * @attribute type
23792            * @description String specifying the button's type.  Possible 
23793            * values are: "push," "link," "submit," "reset," "checkbox," 
23794            * "radio," "menu," and "split."
23795            * @default "push"
23796            * @type String
23797			* @writeonce
23798            */
23799            this.setAttributeConfig("type", {
23800        
23801                value: (oAttributes.type || "push"),
23802                validator: Lang.isString,
23803                writeOnce: true,
23804                method: this._setType
23805
23806            });
23807        
23808        
23809            /**
23810            * @attribute label
23811            * @description {HTML} specifying the button's text label 
23812            * or innerHTML.
23813            * @default null
23814            * @type String
23815            */
23816            this.setAttributeConfig("label", {
23817        
23818                value: oAttributes.label,
23819                validator: Lang.isString,
23820                method: this._setLabel
23821        
23822            });
23823        
23824        
23825            /**
23826            * @attribute value
23827            * @description Object specifying the value for the button.
23828            * @default null
23829            * @type Object
23830            */
23831            this.setAttributeConfig("value", {
23832        
23833                value: oAttributes.value
23834        
23835            });
23836        
23837        
23838            /**
23839            * @attribute name
23840            * @description String specifying the name for the button.
23841            * @default null
23842            * @type String
23843            */
23844            this.setAttributeConfig("name", {
23845        
23846                value: oAttributes.name,
23847                validator: Lang.isString
23848        
23849            });
23850        
23851        
23852            /**
23853            * @attribute tabindex
23854            * @description Number specifying the tabindex for the button.
23855            * @default null
23856            * @type Number
23857            */
23858            this.setAttributeConfig("tabindex", {
23859        
23860                value: oAttributes.tabindex,
23861                validator: Lang.isNumber,
23862                method: this._setTabIndex
23863        
23864            });
23865        
23866        
23867            /**
23868            * @attribute title
23869            * @description String specifying the title for the button.
23870            * @default null
23871            * @type String
23872            */
23873            this.configureAttribute("title", {
23874        
23875                value: oAttributes.title,
23876                validator: Lang.isString,
23877                method: this._setTitle
23878        
23879            });
23880        
23881        
23882            /**
23883            * @attribute disabled
23884            * @description Boolean indicating if the button should be disabled.  
23885            * (Disabled buttons are dimmed and will not respond to user input 
23886            * or fire events.  Does not apply to button's of type "link.")
23887            * @default false
23888            * @type Boolean
23889            */
23890            this.setAttributeConfig("disabled", {
23891        
23892                value: (oAttributes.disabled || false),
23893                validator: Lang.isBoolean,
23894                method: this._setDisabled
23895        
23896            });
23897        
23898        
23899            /**
23900            * @attribute href
23901            * @description String specifying the href for the button.  Applies
23902            * only to buttons of type "link."
23903            * @type String
23904            */
23905            this.setAttributeConfig("href", {
23906        
23907                value: oAttributes.href,
23908                validator: Lang.isString,
23909                method: this._setHref
23910        
23911            });
23912        
23913        
23914            /**
23915            * @attribute target
23916            * @description String specifying the target for the button.  
23917            * Applies only to buttons of type "link."
23918            * @type String
23919            */
23920            this.setAttributeConfig("target", {
23921        
23922                value: oAttributes.target,
23923                validator: Lang.isString,
23924                method: this._setTarget
23925        
23926            });
23927        
23928        
23929            /**
23930            * @attribute checked
23931            * @description Boolean indicating if the button is checked. 
23932            * Applies only to buttons of type "radio" and "checkbox."
23933            * @default false
23934            * @type Boolean
23935            */
23936            this.setAttributeConfig("checked", {
23937        
23938                value: (oAttributes.checked || false),
23939                validator: Lang.isBoolean,
23940                method: this._setChecked
23941        
23942            });
23943        
23944        
23945            /**
23946            * @attribute container
23947            * @description HTML element reference or string specifying the id 
23948            * attribute of the HTML element that the button's markup should be 
23949            * rendered into.
23950            * @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
23951            * level-one-html.html#ID-58190037">HTMLElement</a>|String
23952            * @default null
23953			* @writeonce
23954            */
23955            this.setAttributeConfig("container", {
23956        
23957                value: oAttributes.container,
23958                writeOnce: true
23959        
23960            });
23961        
23962        
23963            /**
23964            * @attribute srcelement
23965            * @description Object reference to the HTML element (either 
23966            * <code>&#60;input&#62;</code> or <code>&#60;span&#62;</code>) 
23967            * used to create the button.
23968            * @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
23969            * level-one-html.html#ID-58190037">HTMLElement</a>|String
23970            * @default null
23971			* @writeonce
23972            */
23973            this.setAttributeConfig("srcelement", {
23974        
23975                value: oAttributes.srcelement,
23976                writeOnce: true
23977        
23978            });
23979        
23980        
23981            /**
23982            * @attribute menu
23983            * @description Object specifying the menu for the button.  
23984            * The value can be one of the following:
23985            * <ul>
23986            * <li>Object specifying a rendered <a href="YAHOO.widget.Menu.html">
23987            * YAHOO.widget.Menu</a> instance.</li>
23988            * <li>Object specifying a rendered <a href="YAHOO.widget.Overlay.html">
23989            * YAHOO.widget.Overlay</a> instance.</li>
23990            * <li>String specifying the id attribute of the <code>&#60;div&#62;
23991            * </code> element used to create the menu.  By default the menu 
23992            * will be created as an instance of 
23993            * <a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>.  
23994            * If the <a href="YAHOO.widget.Menu.html#CSS_CLASS_NAME">
23995            * default CSS class name for YAHOO.widget.Menu</a> is applied to 
23996            * the <code>&#60;div&#62;</code> element, it will be created as an
23997            * instance of <a href="YAHOO.widget.Menu.html">YAHOO.widget.Menu
23998            * </a>.</li><li>String specifying the id attribute of the 
23999            * <code>&#60;select&#62;</code> element used to create the menu.
24000            * </li><li>Object specifying the <code>&#60;div&#62;</code> element
24001            * used to create the menu.</li>
24002            * <li>Object specifying the <code>&#60;select&#62;</code> element
24003            * used to create the menu.</li>
24004            * <li>Array of object literals, each representing a set of 
24005            * <a href="YAHOO.widget.MenuItem.html">YAHOO.widget.MenuItem</a> 
24006            * configuration attributes.</li>
24007            * <li>
24007Array of strings representing the text labels for each menu 
24008            * item in the menu.</li>
24009            * </ul>
24010            * @type <a href="YAHOO.widget.Menu.html">YAHOO.widget.Menu</a>|<a 
24011            * href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a>|<a 
24012            * href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
24013            * one-html.html#ID-58190037">HTMLElement</a>|String|Array
24014            * @default null
24015			* @writeonce
24016            */
24017            this.setAttributeConfig("menu", {
24018        
24019                value: null,
24020                method: this._setMenu,
24021                writeOnce: true
24022            
24023            });
24024        
24025        
24026            /**
24027            * @attribute lazyloadmenu
24028            * @description Boolean indicating the value to set for the 
24029            * <a href="YAHOO.widget.Menu.html#lazyLoad">"lazyload"</a>
24030            * configuration property of the button's menu.  Setting 
24031            * "lazyloadmenu" to <code>true </code> will defer rendering of 
24032            * the button's menu until the first time it is made visible.  
24033            * If "lazyloadmenu" is set to <code>false</code>, the button's 
24034            * menu will be rendered immediately if the button is in the 
24035            * document, or in response to the button's "appendTo" event if 
24036            * the button is not yet in the document.  In either case, the 
24037            * menu is rendered into the button's parent HTML element.  
24038            * <em>This attribute does not apply if a 
24039            * <a href="YAHOO.widget.Menu.html">YAHOO.widget.Menu</a> or 
24040            * <a href="YAHOO.widget.Overlay.html">YAHOO.widget.Overlay</a> 
24041            * instance is passed as the value of the button's "menu" 
24042            * configuration attribute. <a href="YAHOO.widget.Menu.html">
24043            * YAHOO.widget.Menu</a> or <a href="YAHOO.widget.Overlay.html">
24044            * YAHOO.widget.Overlay</a> instances should be rendered before 
24045            * being set as the value for the "menu" configuration 
24046            * attribute.</em>
24047            * @default true
24048            * @type Boolean
24049			* @writeonce
24050            */
24051            this.setAttributeConfig("lazyloadmenu", {
24052        
24053                value: (oAttributes.lazyloadmenu === false ? false : true),
24054                validator: Lang.isBoolean,
24055                writeOnce: true
24056        
24057            });
24058
24059
24060            /**
24061            * @attribute menuclassname
24062            * @description String representing the CSS class name to be 
24063            * applied to the root element of the button's menu.
24064            * @type String
24065            * @default "yui-button-menu"
24066			* @writeonce
24067            */
24068            this.setAttributeConfig("menuclassname", {
24069        
24070                value: (oAttributes.menuclassname || (this.CLASS_NAME_PREFIX + "button-menu")),
24071                validator: Lang.isString,
24072                method: this._setMenuClassName,
24073                writeOnce: true
24074        
24075            });        
24076
24077
24078			/**
24079			* @attribute menuminscrollheight
24080			* @description Number defining the minimum threshold for the "menumaxheight" 
24081			* configuration attribute.  When set this attribute is automatically applied 
24082			* to all submenus.
24083			* @default 90
24084			* @type Number
24085			*/
24086            this.setAttributeConfig("menuminscrollheight", {
24087        
24088                value: (oAttributes.menuminscrollheight || 90),
24089                validator: Lang.isNumber
24090        
24091            });
24092
24093
24094            /**
24095            * @attribute menumaxheight
24096			* @description Number defining the maximum height (in pixels) for a menu's 
24097			* body element (<code>&#60;div class="bd"&#60;</code>).  Once a menu's body 
24098			* exceeds this height, the contents of the body are scrolled to maintain 
24099			* this value.  This value cannot be set lower than the value of the 
24100			* "minscrollheight" configuration property.
24101            * @type Number
24102            * @default 0
24103            */
24104            this.setAttributeConfig("menumaxheight", {
24105        
24106                value: (oAttributes.menumaxheight || 0),
24107                validator: Lang.isNumber
24108        
24109            });
24110
24111
24112            /**
24113            * @attribute menualignment
24114			* @description Array defining how the Button's Menu is aligned to the Button.  
24115            * The default value of ["tl", "bl"] aligns the Menu's top left corner to the Button's 
24116            * bottom left corner.
24117            * @type Array
24118            * @default ["tl", "bl"]
24119            */
24120            this.setAttributeConfig("menualignment", {
24121        
24122                value: (oAttributes.menualignment || ["tl", "bl"]),
24123                validator: Lang.isArray
24124        
24125            });
24126            
24127
24128            /**
24129            * @attribute selectedMenuItem
24130            * @description Object representing the item in the button's menu 
24131            * that is currently selected.
24132            * @type YAHOO.widget.MenuItem
24133            * @default null
24134            */
24135            this.setAttributeConfig("selectedMenuItem", {
24136        
24137                value: null
24138        
24139            });
24140        
24141        
24142            /**
24143            * @attribute onclick
24144            * @description Object literal representing the code to be executed  
24145            * when the button is clicked.  Format:<br> <code> {<br> 
24146            * <strong>fn:</strong> Function,   &#47;&#47; The handler to call 
24147            * when the event fires.<br> <strong>obj:</strong> Object, 
24148            * &#47;&#47; An object to pass back to the handler.<br> 
24149            * <strong>scope:</strong> Object &#47;&#47;  The object to use 
24150            * for the scope of the handler.<br> } </code>
24151            * @type Object
24152            * @default null
24153            */
24154            this.setAttributeConfig("onclick", {
24155        
24156                value: oAttributes.onclick,
24157                method: this._setOnClick
24158            
24159            });
24160
24161
24162            /**
24163            * @attribute focusmenu
24164            * @description Boolean indicating whether or not the button's menu 
24165            * should be focused when it is made visible.
24166            * @type Boolean
24167            * @default true
24168            */
24169            this.setAttributeConfig("focusmenu", {
24170        
24171                value: (oAttributes.focusmenu === false ? false : true),
24172                validator: Lang.isBoolean
24173        
24174            });
24175
24176
24177            /**
24178            * @attribute replaceLabel
24179            * @description Boolean indicating whether or not the text of the 
24180			* button's <code>&#60;label&#62;</code> element should be used as
24181			* the source for the button's label configuration attribute and 
24182			* removed from the DOM.
24183            * @type Boolean
24184            * @default false
24185            */
24186            this.setAttributeConfig("replaceLabel", {
24187        
24188                value: false,
24189                validator: Lang.isBoolean,
24190                writeOnce: true
24191        
24192            });
24193
24194        },
24195        
24196        
24197        /**
24198        * @method focus
24199        * @description Causes the button to receive the focus and fires the 
24200        * button's "focus" event.
24201        */
24202        focus: function () {
24203        
24204            if (!this.get("disabled")) {
24205                //Adding a try/catch in case the element is not
24206                //  visible by the time it's focus is being called.
24207                //  for example, on a dialog that closes on button click
24208                try {
24209                    this._button.focus();
24210                } catch (e) {}
24211            
24212            }
24213        
24214        },
24215        
24216        
24217        /**
24218        * @method blur
24219        * @description Causes the button to lose focus and fires the button's
24220        * "blur" event.
24221        */
24222        blur: function () {
24223        
24224            if (!this.get("disabled")) {
24225                //Adding a try/catch in case the element is not
24226                //  visible by the time it's focus is being called.
24227                //  for example, on a dialog that closes on button click
24228                try {
24229                    this._button.blur();
24230                } catch (e) {}
24231        
24232            }
24233        
24234        },
24235        
24236        
24237        /**
24238        * @method hasFocus
24239        * @description Returns a boolean indicating whether or not the button 
24240        * has focus.
24241        * @return {Boolean}
24242        */
24243        hasFocus: function () {
24244        
24245            return (m_oFocusedButton == this);
24246        
24247        },
24248        
24249        
24250        /**
24251        * @method isActive
24252        * @description Returns a boolean indicating whether or not the button 
24253        * is active.
24254        * @return {Boolean}
24255        */
24256        isActive: function () {
24257        
24258            return this.hasClass(this.CLASS_NAME_PREFIX + this.CSS_CLASS_NAME + "-active");
24259        
24260        },
24261        
24262        
24263        /**
24264        * @method getMenu
24265        * @description Returns a reference to the button's menu.
24266        * @return {<a href="YAHOO.widget.Overlay.html">
24267        * YAHOO.widget.Overlay</a>|<a 
24268        * href="YAHOO.widget.Menu.html">YAHOO.widget.Menu</a>}
24269        */
24270        getMenu: function () {
24271        
24272            return this._menu;
24273        
24274        },
24275        
24276        
24277        /**
24278        * @method getForm
24279        * @description Returns a reference to the button's parent form.
24280        * @return {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-
24281        * 20000929/level-one-html.html#ID-40002357">HTMLFormElement</a>}
24282        */
24283        getForm: function () {
24284        
24285        	var oButton = this._button,
24286        		oForm;
24287        
24288            if (oButton) {
24289            
24290            	oForm = oButton.form;
24291            
24292            }
24293        
24294        	return oForm;
24295        
24296        },
24297        
24298        
24299        /** 
24300        * @method getHiddenFields
24301        * @description Returns an <code>&#60;input&#62;</code> element or 
24302        * array of form elements used to represent the button when its parent 
24303        * form is submitted.  
24304        * @return {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
24305        * level-one-html.html#ID-6043025">HTMLInputElement</a>|Array}
24306        */
24307        getHiddenFields: function () {
24308        
24309            return this._hiddenFields;
24310        
24311        },
24312        
24313        
24314        /**
24315        * @method destroy
24316        * @description Removes the button's element from its parent element and 
24317        * removes all event handlers.
24318        */
24319        destroy: function () {
24320        
24321            YAHOO.log("Destroying ...", "info", this.toString());
24322        
24323            var oElement = this.get("element"),
24324                oMenu = this._menu,
24325				oLabel = this._label,
24326                oParentNode,
24327                aButtons;
24328        
24329            if (oMenu) {
24330        
24331                YAHOO.log("Destroying menu.", "info", this.toString());
24332
24333                if (m_oOverlayManager && m_oOverlayManager.find(oMenu)) {
24334
24335                    m_oOverlayManager.remove(oMenu);
24336
24337                }
24338        
24339                oMenu.destroy();
24340        
24341            }
24342        
24343            YAHOO.log("Removing DOM event listeners.", "info", this.toString());
24344        
24345            Event.purgeElement(oElement);
24346            Event.purgeElement(this._button);
24347            Event.removeListener(document, "mouseup", this._onDocumentMouseUp);
24348            Event.removeListener(document, "keyup", this._onDocumentKeyUp);
24349            Event.removeListener(document, "mousedown", this._onDocumentMouseDown);
24350
24351
24352			if (oLabel) {
24353
24354            	Event.removeListener(oLabel, "click", this._onLabelClick);
24355				
24356				oParentNode = oLabel.parentNode;
24357				oParentNode.removeChild(oLabel);
24358				
24359			}
24360        
24361        
24362            var oForm = this.getForm();
24363            
24364            if (oForm) {
24365        
24366                Event.removeListener(oForm, "reset", this._onFormReset);
24367                Event.removeListener(oForm, "submit", this._onFormSubmit);
24368        
24369            }
24370
24371            YAHOO.log("Removing CustomEvent listeners.", "info", this.toString());
24372
24373            this.unsubscribeAll();
24374
24375			oParentNode = oElement.parentNode;
24376
24377            if (oParentNode) {
24378
24379                oParentNode.removeChild(oElement);
24380            
24381            }
24382        
24383            YAHOO.log("Removing from document.", "info", this.toString());
24384        
24385            delete m_oButtons[this.get("id")];
24386
24387			var sClass = (this.CLASS_NAME_PREFIX + this.CSS_CLASS_NAME);
24388
24389            aButtons = Dom.getElementsByClassName(sClass, 
24390                                this.NODE_NAME, oForm); 
24391
24392            if (Lang.isArray(aButtons) && aButtons.length === 0) {
24393
24394                Event.removeListener(oForm, "keypress", 
24395                        YAHOO.widget.Button.onFormKeyPress);
24396
24397            }
24398
24399            YAHOO.log("Destroyed.", "info", this.toString());
24400        
24401        },
24402        
24403        
24404        fireEvent: function (p_sType , p_aArgs) {
24405        
24406			var sType = arguments[0];
24407		
24408			//  Disabled buttons should not respond to DOM events
24409		
24410			if (this.DOM_EVENTS[sType] && this.get("disabled")) {
24411		
24412				return false;
24413		
24414			}
24415		
24416			return YAHOO.widget.Button.superclass.fireEvent.apply(this, arguments);
24417        
24418        },
24419        
24420        
24421        /**
24422        * @method toString
24423        * @description Returns a string representing the button.
24424        * @return {String}
24425        */
24426        toString: function () {
24427        
24428            return ("Button " + this.get("id"));
24429        
24430        }
24431    
24432    });
24433    
24434    
24435    /**
24436    * @method YAHOO.widget.Button.onFormKeyPress
24437    * @description "keypress" event handler for the button's form.
24438    * @param {Event} p_oEvent Object representing the DOM event object passed 
24439    * back by the event utility (YAHOO.util.Event).
24440    */
24441    YAHOO.widget.Button.onFormKeyPress = function (p_oEvent) {
24442    
24443        var oTarget = Event.getTarget(p_oEvent),
24444            nCharCode = Event.getCharCode(p_oEvent),
24445            sNodeName = oTarget.nodeName && oTarget.nodeName.toUpperCase(),
24446            sType = oTarget.type,
24447    
24448            /*
24449                Boolean indicating if the form contains any enabled or 
24450                disabled YUI submit buttons
24451            */
24452    
24453            bFormContainsYUIButtons = false,
24454    
24455            oButton,
24456    
24457            oYUISubmitButton,   // The form's first, enabled YUI submit button
24458    
24459            /*
24460                 The form's first, enabled HTML submit button that precedes any 
24461                 YUI submit button
24462            */
24463    
24464            oPrecedingSubmitButton,
24465            
24466            oEvent; 
24467    
24468    
24469        function isSubmitButton(p_oElement) {
24470    
24471            var sId,
24472                oSrcElement;
24473    
24474            switch (p_oElement.nodeName.toUpperCase()) {
24475    
24476            case "INPUT":
24477            case "BUTTON":
24478            
24479                if (p_oElement.type == "submit" && !p_oElement.disabled) {
24480                    
24481                    if (!bFormContainsYUIButtons && !oPrecedingSubmitButton) {
24482
24483                        oPrecedingSubmitButton = p_oElement;
24484
24485                    }
24486                
24487                }
24488
24489                break;
24490            
24491
24492            default:
24493            
24494                sId = p_oElement.id;
24495    
24496                if (sId) {
24497    
24498                    oButton = m_oButtons[sId];
24499        
24500                    if (oButton) {
24501
24502                        bFormContainsYUIButtons = true;
24503        
24504                        if (!oButton.get("disabled")) {
24505
24506                            oSrcElement = oButton.get("srcelement");
24507    
24508                            if (!oYUISubmitButton && (oButton.get("type") == "submit" || 
24509                                (oSrcElement && oSrcElement.type == "submit"))) {
24510
24511                                oYUISubmitButton = oButton;
24512                            
24513                            }
24514                        
24515                        }
24516                        
24517                    }
24518                
24519                }
24520
24521                break;
24522    
24523            }
24524    
24525        }
24526    
24527    
24528        if (nCharCode == 13 && ((sNodeName == "INPUT" && (sType == "text" || 
24529            sType == "password" || sType == "checkbox" || sType == "radio" || 
24530            sType == "file")) || sNodeName == "SELECT")) {
24531    
24532            Dom.getElementsBy(isSubmitButton, "*", this);
24533    
24534    
24535            if (oPrecedingSubmitButton) {
24536    
24537                /*
24538                     Need to set focus to the first enabled submit button
24539                     to make sure that IE includes its name and value 
24540                     in the form's data set.
24541                */
24542    
24543                oPrecedingSubmitButton.focus();
24544            
24545            }
24546            else if (!oPrecedingSubmitButton && oYUISubmitButton) {
24547    
24548				/*
24549					Need to call "preventDefault" to ensure that the form doesn't end up getting
24550					submitted twice.
24551				*/
24552    
24553    			Event.preventDefault(p_oEvent);
24554
24555
24556				if (UA.ie && (UA.ie < 9)) {
24557				
24558					oYUISubmitButton.get("element").fireEvent("onclick");
24559				
24560				}
24561				else {
24562
24563					oEvent = document.createEvent("HTMLEvents");
24564					oEvent.initEvent("click", true, true);
24565			
24566
24567					if (UA.gecko < 1.9) {
24568					
24569						oYUISubmitButton.fireEvent("click", oEvent);
24570					
24571					}
24572					else {
24573
24574						oYUISubmitButton.get("element").dispatchEvent(oEvent);
24575					
24576					}
24577  
24578                }
24579
24580            }
24581            
24582        }
24583    
24584    };
24585    
24586    
24587    /**
24588    * @method YAHOO.widget.Button.addHiddenFieldsToForm
24589    * @description Searches the specified form and adds hidden fields for  
24590    * instances of YAHOO.widget.Button that are of type "radio," "checkbox," 
24591    * "menu," and "split."
24592    * @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
24593    * one-html.html#ID-40002357">HTMLFormElement</a>} p_oForm Object reference 
24594    * for the form to search.
24595    */
24596    YAHOO.widget.Button.addHiddenFieldsToForm = function (p_oForm) {
24597    
24598        var proto = YAHOO.widget.Button.prototype,
24599			aButtons = Dom.getElementsByClassName(
24600							(proto.CLASS_NAME_PREFIX + proto.CSS_CLASS_NAME), 
24601                            "*", 
24602                            p_oForm),
24603    
24604            nButtons = aButtons.length,
24605            oButton,
24606            sId,
24607            i;
24608    
24609        if (nButtons > 0) {
24610    
24611            YAHOO.log("Form contains " + nButtons + " YUI buttons.", "info", this.toString());
24612    
24613            for (i = 0; i < nButtons; i++) {
24614    
24615                sId = aButtons[i].id;
24616    
24617                if (sId) {
24618    
24619                    oButton = m_oButtons[sId];
24620        
24621                    if (oButton) {
24622           
24623                        oButton.createHiddenFields();
24624                        
24625                    }
24626                
24627                }
24628            
24629            }
24630    
24631        }
24632    
24633    };
24634    
24635
24636    /**
24637    * @method YAHOO.widget.Button.getButton
24638    * @description Returns a button with the specified id.
24639    * @param {String} p_sId String specifying the id of the root node of the 
24640    * HTML element representing the button to be retrieved.
24641    * @return {YAHOO.widget.Button}
24642    */
24643    YAHOO.widget.Button.getButton = function (p_sId) {
24644
24645		return m_oButtons[p_sId];
24646
24647    };
24648    
24649    
24650    // Events
24651    
24652    
24653    /**
24654    * @event focus
24655    * @description Fires when the menu item receives focus.  Passes back a  
24656    * single object representing the original DOM event object passed back by 
24657    * the event utility (YAHOO.util.Event) when the event was fired.  See 
24658    * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> 
24659    * for more information on listening for this event.
24660    * @type YAHOO.util.CustomEvent
24661    */
24662    
24663    
24664    /**
24665    * @event blur
24666    * @description Fires when the menu item loses the input focus.  Passes back  
24667    * a single object representing the original DOM event object passed back by 
24668    * the event utility (YAHOO.util.Event) when the event was fired.  See 
24669    * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> for  
24670    * more information on listening for this event.
24671    * @type YAHOO.util.CustomEvent
24672    */
24673    
24674    
24675    /**
24676    * @event option
24677    * @description Fires when the user invokes the button's option.  Passes 
24678    * back a single object representing the original DOM event (either 
24679    * "mousedown" or "keydown") that caused the "option" event to fire.  See 
24680    * <a href="YAHOO.util.Element.html#addListener">Element.addListener</a> 
24681    * for more information on listening for this event.
24682    * @type YAHOO.util.CustomEvent
24683    */
24684
24685})();
24686(function () {
24687
24688    // Shorthard for utilities
24689    
24690    var Dom = YAHOO.util.Dom,
24691        Event = YAHOO.util.Event,
24692        Lang = YAHOO.lang,
24693        Button = YAHOO.widget.Button,  
24694    
24695        // Private collection of radio buttons
24696    
24697        m_oButtons = {};
24698
24699
24700
24701    /**
24702    * The ButtonGroup class creates a set of buttons that are mutually 
24703    * exclusive; checking one button in the set will uncheck all others in the 
24704    * button group.
24705    * @param {String} p_oElement String specifying the id attribute of the 
24706    * <code>&#60;div&#62;</code> element of the button group.
24707    * @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
24708    * level-one-html.html#ID-22445964">HTMLDivElement</a>} p_oElement Object 
24709    * specifying the <code>&#60;div&#62;</code> element of the button group.
24710    * @param {Object} p_oElement Object literal specifying a set of 
24711    * configuration attributes used to create the button group.
24712    * @param {Object} p_oAttributes Optional. Object literal specifying a set 
24713    * of configuration attributes used to create the button group.
24714    * @namespace YAHOO.widget
24715    * @class ButtonGroup
24716    * @constructor
24717    * @extends YAHOO.util.Element
24718    */
24719    YAHOO.widget.ButtonGroup = function (p_oElement, p_oAttributes) {
24720    
24721        var fnSuperClass = YAHOO.widget.ButtonGroup.superclass.constructor,
24722            sNodeName,
24723            oElement,
24724            sId;
24725    
24726        if (arguments.length == 1 && !Lang.isString(p_oElement) && 
24727            !p_oElement.nodeName) {
24728    
24729            if (!p_oElement.id) {
24730    
24731                sId = Dom.generateId();
24732    
24733                p_oElement.id = sId;
24734    
24735                YAHOO.log("No value specified for the button group's \"id\"" +
24736                    " attribute. Setting button group id to \"" + sId + "\".",
24737                    "info");
24738    
24739            }
24740    
24741            this.logger = new YAHOO.widget.LogWriter("ButtonGroup " + sId);
24742    
24743            this.logger.log("No source HTML element.  Building the button " +
24744                    "group using the set of configuration attributes.");
24745    
24746            fnSuperClass.call(this, (this._createGroupElement()), p_oElement);
24747    
24748        }
24749        else if (Lang.isString(p_oElement)) {
24750    
24751            oElement = Dom.get(p_oElement);
24752    
24753            if (oElement) {
24754            
24755                if (oElement.nodeName.toUpperCase() == this.NODE_NAME) {
24756    
24757                    this.logger = 
24758                        new YAHOO.widget.LogWriter("ButtonGroup " + p_oElement);
24759            
24760                    fnSuperClass.call(this, oElement, p_oAttributes);
24761    
24762                }
24763    
24764            }
24765        
24766        }
24767        else {
24768    
24769            sNodeName = p_oElement.nodeName.toUpperCase();
24770    
24771            if (sNodeName && sNodeName == this.NODE_NAME) {
24772        
24773                if (!p_oElement.id) {
24774        
24775                    p_oElement.id = Dom.generateId();
24776        
24777                    YAHOO.log("No value specified for the button group's" +
24778                        " \"id\" attribute. Setting button group id " +
24779                        "to \"" + p_oElement.id + "\".", "warn");
24780        
24781                }
24782        
24783                this.logger = 
24784                    new YAHOO.widget.LogWriter("ButtonGroup " + p_oElement.id);
24785        
24786                fnSuperClass.call(this, p_oElement, p_oAttributes);
24787    
24788            }
24789    
24790        }
24791    
24792    };
24793    
24794    
24795    YAHOO.extend(YAHOO.widget.ButtonGroup, YAHOO.util.Element, {
24796    
24797    
24798        // Protected properties
24799        
24800        
24801        /** 
24802        * @property _buttons
24803        * @description Array of buttons in the button group.
24804        * @default null
24805        * @protected
24806        * @type Array
24807        */
24808        _buttons: null,
24809        
24810        
24811        
24812        // Constants
24813        
24814        
24815        /**
24816        * @property NODE_NAME
24817        * @description The name of the tag to be used for the button 
24818        * group's element. 
24819        * @default "DIV"
24820        * @final
24821        * @type String
24822        */
24823        NODE_NAME: "DIV",
24824
24825
24826        /**
24827        * @property CLASS_NAME_PREFIX
24828        * @description Prefix used for all class names applied to a ButtonGroup.
24829        * @default "yui-"
24830        * @final
24831        * @type String
24832        */
24833        CLASS_NAME_PREFIX: "yui-",
24834        
24835        
24836        /**
24837        * @property CSS_CLASS_NAME
24838        * @description String representing the CSS class(es) to be applied  
24839        * to the button group's element.
24840        * @default "buttongroup"
24841        * @final
24842        * @type String
24843        */
24844        CSS_CLASS_NAME: "buttongroup",
24845    
24846    
24847    
24848        // Protected methods
24849        
24850        
24851        /**
24852        * @method _createGroupElement
24853        * @description Creates the button group's element.
24854        * @protected
24855        * @return {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
24856        * level-one-html.html#ID-22445964">HTMLDivElement</a>}
24857        */
24858        _createGroupElement: function () {
24859        
24860            var oElement = document.createElement(this.NODE_NAME);
24861        
24862            return oElement;
24863        
24864        },
24865        
24866        
24867        
24868        // Protected attribute setter methods
24869        
24870        
24871        /**
24872        * @method _setDisabled
24873        * @description Sets the value of the button groups's 
24874        * "disabled" attribute.
24875        * @protected
24876        * @param {Boolean} p_bDisabled Boolean indicating the value for
24877        * the button group's "disabled" attribute.
24878        */
24879        _setDisabled: function (p_bDisabled) {
24880        
24881            var nButtons = this.getCount(),
24882                i;
24883        
24884            if (nButtons > 0) {
24885        
24886                i = nButtons - 1;
24887                
24888                do {
24889        
24890                    this._buttons[i].set("disabled", p_bDisabled);
24891                
24892                }
24893                while (i--);
24894        
24895            }
24896        
24897        },
24898        
24899        
24900        
24901        // Protected event handlers
24902        
24903        
24904        /**
24905        * @method _onKeyDown
24906        * @description "keydown" event handler for the button group.
24907        * @protected
24908        * @param {Event} p_oEvent Object representing the DOM event object  
24909        * passed back by the event utility (YAHOO.util.Event).
24910        */
24911        _onKeyDown: function (p_oEvent) {
24912        
24913            var oTarget = Event.getTarget(p_oEvent),
24914                nCharCode = Event.getCharCode(p_oEvent),
24915                sId = oTarget.parentNode.parentNode.id,
24916                oButton = m_oButtons[sId],
24917                nIndex = -1;
24918        
24919        
24920            if (nCharCode == 37 || nCharCode == 38) {
24921        
24922                nIndex = (oButton.index === 0) ? 
24923                            (this._buttons.length - 1) : (oButton.index - 1);
24924            
24925            }
24926            else if (nCharCode == 39 || nCharCode == 40) {
24927        
24928                nIndex = (oButton.index === (this._buttons.length - 1)) ? 
24929                            0 : (oButton.index + 1);
24930        
24931            }
24932        
24933        
24934            if (nIndex > -1) {
24935        
24936                this.check(nIndex);
24937                this.getButton(nIndex).focus();
24938            
24939            }        
24940        
24941        },
24942        
24943        
24944        /**
24945        * @method _onAppendTo
24946        * @description "appendTo" event handler for the button group.
24947        * @protected
24948        * @param {Event} p_oEvent Object representing the event that was fired.
24949        */
24950        _onAppendTo: function (p_oEvent) {
24951        
24952            var aButtons = this._buttons,
24953                nButtons = aButtons.length,
24954                i;
24955        
24956            for (i = 0; i < nButtons; i++) {
24957        
24958                aButtons[i].appendTo(this.get("element"));
24959        
24960            }
24961        
24962        },
24963        
24964        
24965        /**
24966        * @method _onButtonCheckedChange
24967        * @description "checkedChange" event handler for each button in the 
24968        * button group.
24969        * @protected
24970        * @param {Event} p_oEvent Object representing the event that was fired.
24971        * @param {<a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a>}  
24972        * p_oButton Object representing the button that fired the event.
24973        */
24974        _onButtonCheckedChange: function (p_oEvent, p_oButton) {
24975        
24976            var bChecked = p_oEvent.newValue,
24977                oCheckedButton = this.get("checkedButton");
24978        
24979            if (bChecked && oCheckedButton != p_oButton) {
24980        
24981                if (oCheckedButton) {
24982        
24983                    oCheckedButton.set("checked", false, true);
24984        
24985                }
24986        
24987                this.set("checkedButton", p_oButton);
24988                this.set("value", p_oButton.get("value"));
24989        
24990            }
24991            else if (oCheckedButton && !oCheckedButton.set("checked")) {
24992        
24993                oCheckedButton.set("checked", true, true);
24994        
24995            }
24996           
24997        },
24998        
24999        
25000        
25001        // Public methods
25002        
25003        
25004        /**
25005        * @method init
25006        * @description The ButtonGroup class's initialization method.
25007        * @param {String} p_oElement String specifying the id attribute of the 
25008        * <code>&#60;div&#62;</code> element of the button group.
25009        * @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
25010        * level-one-html.html#ID-22445964">HTMLDivElement</a>} p_oElement Object 
25011        * specifying the <code>&#60;div&#62;</code> element of the button group.
25012        * @param {Object} p_oElement Object literal specifying a set of  
25013        * configuration attributes used to create the button group.
25014        * @param {Object} p_oAttributes Optional. Object literal specifying a
25015        * set of configuration attributes used to create the button group.
25016        */
25017        init: function (p_oElement, p_oAttributes) {
25018        
25019            this._buttons = [];
25020        
25021            YAHOO.widget.ButtonGroup.superclass.init.call(this, p_oElement, 
25022                    p_oAttributes);
25023        
25024            this.addClass(this.CLASS_NAME_PREFIX + this.CSS_CLASS_NAME);
25025
25026        
25027            var sClass = (YAHOO.widget.Button.prototype.CLASS_NAME_PREFIX + "radio-button"),
25028				aButtons = this.getElementsByClassName(sClass);
25029
25030            this.logger.log("Searching for child nodes with the class name " +
25031                sClass + " to add to the button group.");
25032        
25033        
25034            if (aButtons.length > 0) {
25035        
25036                this.logger.log("Found " + aButtons.length + 
25037                    " child nodes with the class name " + sClass + 
25038                    "  Attempting to add to button group.");
25039        
25040                this.addButtons(aButtons);
25041        
25042            }
25043        
25044        
25045            this.logger.log("Searching for child nodes with the type of " +
25046                " \"radio\" to add to the button group.");
25047        
25048            function isRadioButton(p_oElement) {
25049        
25050                return (p_oElement.type == "radio");
25051        
25052            }
25053        
25054            aButtons = 
25055                Dom.getElementsBy(isRadioButton, "input", this.get("element"));
25056        
25057        
25058            if (aButtons.length > 0) {
25059        
25060                this.logger.log("Found " + aButtons.length + " child nodes" +
25061                    " with the type of \"radio.\"  Attempting to add to" +
25062                    " button group.");
25063        
25064                this.addButtons(aButtons);
25065        
25066            }
25067        
25068            this.on("keydown", this._onKeyDown);
25069            this.on("appendTo", this._onAppendTo);
25070        
25071
25072            var oContainer = this.get("container");
25073
25074            if (oContainer) {
25075        
25076                if (Lang.isString(oContainer)) {
25077        
25078                    Event.onContentReady(oContainer, function () {
25079        
25080                        this.appendTo(oContainer);            
25081                    
25082                    }, null, this);
25083        
25084                }
25085                else {
25086        
25087                    this.appendTo(oContainer);
25088        
25089                }
25090        
25091            }
25092        
25093        
25094            this.logger.log("Initialization completed.");
25095        
25096        },
25097        
25098        
25099        /**
25100        * @method initAttributes
25101        * @description Initializes all of the configuration attributes used to  
25102        * create the button group.
25103        * @param {Object} p_oAttributes Object literal specifying a set of 
25104        * configuration attributes used to create the button group.
25105        */
25106        initAttributes: function (p_oAttributes) {
25107        
25108            var oAttributes = p_oAttributes || {};
25109        
25110            YAHOO.widget.ButtonGroup.superclass.initAttributes.call(
25111                this, oAttributes);
25112        
25113        
25114            /**
25115            * @attribute name
25116            * @description String specifying the name for the button group.  
25117            * This name will be applied to each button in the button group.
25118            * @default null
25119            * @type String
25120            */
25121            this.setAttributeConfig("name", {
25122        
25123                value: oAttributes.name,
25124                validator: Lang.isString
25125        
25126            });
25127        
25128        
25129            /**
25130            * @attribute disabled
25131            * @description Boolean indicating if the button group should be 
25132            * disabled.  Disabling the button group will disable each button 
25133            * in the button group.  Disabled buttons are dimmed and will not 
25134            * respond to user input or fire events.
25135            * @default false
25136            * @type Boolean
25137            */
25138            this.setAttributeConfig("disabled", {
25139        
25140                value: (oAttributes.disabled || false),
25141                validator: Lang.isBoolean,
25142                method: this._setDisabled
25143        
25144            });
25145        
25146        
25147            /**
25148            * @attribute value
25149            * @description Object specifying the value for the button group.
25150            * @default null
25151            * @type Object
25152            */
25153            this.setAttributeConfig("value", {
25154        
25155                value: oAttributes.value
25156        
25157            });
25158        
25159        
25160            /**
25161            * @attribute container
25162            * @description HTML element reference or string specifying the id 
25163            * attribute of the HTML element that the button group's markup
25164            * should be rendered into.
25165            * @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
25166            * level-one-html.html#ID-58190037">HTMLElement</a>|String
25167            * @default null
25168			* @writeonce
25169            */
25170            this.setAttributeConfig("container", {
25171        
25172                value: oAttributes.container,
25173                writeOnce: true
25174        
25175            });
25176        
25177        
25178            /**
25179            * @attribute checkedButton
25180            * @description Reference for the button in the button group that 
25181            * is checked.
25182            * @type {<a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a>}
25183            * @default null
25184            */
25185            this.setAttributeConfig("checkedButton", {
25186        
25187                value: null
25188        
25189            });
25190        
25191        },
25192        
25193        
25194        /**
25195        * @method addButton
25196        * @description Adds the button to the button group.
25197        * @param {<a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a>}  
25198        * p_oButton Object reference for the <a href="YAHOO.widget.Button.html">
25199        * YAHOO.widget.Button</a> instance to be added to the button group.
25200        * @param {String} p_oButton String specifying the id attribute of the 
25201        * <code>&#60;input&#62;</code> or <code>&#60;span&#62;</code> element 
25202        * to be used to create the button to be added to the button group.
25203        * @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
25204        * level-one-html.html#ID-6043025">HTMLInputElement</a>|<a href="
25205        * http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-html.html#
25206        * ID-33759296">HTMLElement</a>} p_oButton Object reference for the 
25207        * <code>&#60;input&#62;</code> or <code>&#60;span&#62;</code> element 
25208        * to be used to create the button to be added to the button group.
25209        * @param {Object} p_oButton Object literal specifying a set of 
25210        * <a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a> 
25211        * configuration attributes used to configure the button to be added to 
25212        * the button group.
25213        * @return {<a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a>} 
25214        */
25215        addButton: function (p_oButton) {
25216        
25217            var oButton,
25218                oButtonElement,
25219                oGroupElement,
25220                nIndex,
25221                sButtonName,
25222                sGroupName;
25223        
25224        
25225            if (p_oButton instanceof Button && 
25226                p_oButton.get("type") == "radio") {
25227        
25228                oButton = p_oButton;
25229        
25230            }
25231            else if (!Lang.isString(p_oButton) && !p_oButton.nodeName) {
25232        
25233                p_oButton.type = "radio";
25234        
25235                oButton = new Button(p_oButton);
25236
25237            }
25238            else {
25239        
25240                oButton = new Button(p_oButton, { type: "radio" });
25241        
25242            }
25243        
25244        
25245            if (oButton) {
25246        
25247                nIndex = this._buttons.length;
25248                sButtonName = oButton.get("name");
25249                sGroupName = this.get("name");
25250        
25251                oButton.index = nIndex;
25252        
25253                this._buttons[nIndex] = oButton;
25254                m_oButtons[oButton.get("id")] = oButton;
25255        
25256        
25257                if (sButtonName != sGroupName) {
25258        
25259                    oButton.set("name", sGroupName);
25260                
25261                }
25262        
25263        
25264                if (this.get("disabled")) {
25265        
25266                    oButton.set("disabled", true);
25267        
25268                }
25269        
25270        
25271                if (oButton.get("checked")) {
25272        
25273                    this.set("checkedButton", oButton);
25274        
25275                }
25276
25277                
25278                oButtonElement = oButton.get("element");
25279                oGroupElement = this.get("element");
25280                
25281                if (oButtonElement.parentNode != oGroupElement) {
25282                
25283                    oGroupElement.appendChild(oButtonElement);
25284                
25285                }
25286        
25287                
25288                oButton.on("checkedChange", 
25289                    this._onButtonCheckedChange, oButton, this);
25290        
25291                this.logger.log("Button " + oButton.get("id") + " added.");
25292        
25293            }
25294
25295			return oButton;
25296        
25297        },
25298        
25299        
25300        /**
25301        * @method addButtons
25302        * @description Adds the array of buttons to the button group.
25303        * @param {Array} p_aButtons Array of <a href="YAHOO.widget.Button.html">
25304        * YAHOO.widget.Button</a> instances to be added 
25305        * to the button group.
25306        * @param {Array} p_aButtons Array of strings specifying the id 
25307        * attribute of the <code>&#60;input&#62;</code> or <code>&#60;span&#62;
25308        * </code> elements to be used to create the buttons to be added to the 
25309        * button group.
25310        * @param {Array} p_aButtons Array of object references for the 
25311        * <code>&#60;input&#62;</code> or <code>&#60;span&#62;</code> elements 
25312        * to be used to create the buttons to be added to the button group.
25313        * @param {Array} p_aButtons Array of object literals, each containing
25314        * a set of <a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a>  
25315        * configuration attributes used to configure each button to be added 
25316        * to the button group.
25317        * @return {Array}
25318        */
25319        addButtons: function (p_aButtons) {
25320    
25321            var nButtons,
25322                oButton,
25323                aButtons,
25324                i;
25325        
25326            if (Lang.isArray(p_aButtons)) {
25327            
25328                nButtons = p_aButtons.length;
25329                aButtons = [];
25330        
25331                if (nButtons > 0) {
25332        
25333                    for (i = 0; i < nButtons; i++) {
25334        
25335                        oButton = this.addButton(p_aButtons[i]);
25336                        
25337                        if (oButton) {
25338        
25339                            aButtons[aButtons.length] = oButton;
25340        
25341                        }
25342                    
25343                    }
25344                
25345                }
25346        
25347            }
25348
25349			return aButtons;
25350        
25351        },
25352        
25353        
25354        /**
25355        * @method removeButton
25356        * @description Removes the button at the specified index from the 
25357        * button group.
25358        * @param {Number} p_nIndex Number specifying the index of the button 
25359        * to be removed from the button group.
25360        */
25361        removeButton: function (p_nIndex) {
25362        
25363            var oButton = this.getButton(p_nIndex),
25364                nButtons,
25365                i;
25366            
25367            if (oButton) {
25368        
25369                this.logger.log("Removing button " + oButton.get("id") + ".");
25370        
25371                this._buttons.splice(p_nIndex, 1);
25372                delete m_oButtons[oButton.get("id")];
25373        
25374                oButton.removeListener("checkedChange", 
25375                    this._onButtonCheckedChange);
25376
25377                oButton.destroy();
25378        
25379        
25380                nButtons = this._buttons.length;
25381                
25382                if (nButtons > 0) {
25383        
25384                    i = this._buttons.length - 1;
25385                    
25386                    do {
25387        
25388                        this._buttons[i].index = i;
25389        
25390                    }
25391                    while (i--);
25392                
25393                }
25394        
25395                this.logger.log("Button " + oButton.get("id") + " removed.");
25396        
25397            }
25398        
25399        },
25400        
25401        
25402        /**
25403        * @method getButton
25404        * @description Returns the button at the specified index.
25405        * @param {Number} p_nIndex The index of the button to retrieve from the 
25406        * button group.
25407        * @return {<a href="YAHOO.widget.Button.html">YAHOO.widget.Button</a>}
25408        */
25409        getButton: function (p_nIndex) {
25410        
25411            return this._buttons[p_nIndex];
25412        
25413        },
25414        
25415        
25416        /**
25417        * @method getButtons
25418        * @description Returns an array of the buttons in the button group.
25419        * @return {Array}
25420        */
25421        getButtons: function () {
25422        
25423            return this._buttons;
25424        
25425        },
25426        
25427        
25428        /**
25429        * @method getCount
25430        * @description Returns the number of buttons in the button group.
25431        * @return {Number}
25432        */
25433        getCount: function () {
25434        
25435            return this._buttons.length;
25436        
25437        },
25438        
25439        
25440        /**
25441        * @method focus
25442        * @description Sets focus to the button at the specified index.
25443        * @param {Number} p_nIndex Number indicating the index of the button 
25444        * to focus. 
25445        */
25446        focus: function (p_nIndex) {
25447        
25448            var oButton,
25449                nButtons,
25450                i;
25451        
25452            if (Lang.isNumber(p_nIndex)) {
25453        
25454                oButton = this._buttons[p_nIndex];
25455                
25456                if (oButton) {
25457        
25458                    oButton.focus();
25459        
25460                }
25461            
25462            }
25463            else {
25464        
25465                nButtons = this.getCount();
25466        
25467                for (i = 0; i < nButtons; i++) {
25468        
25469                    oButton = this._buttons[i];
25470        
25471                    if (!oButton.get("disabled")) {
25472        
25473                        oButton.focus();
25474                        break;
25475        
25476                    }
25477        
25478                }
25479        
25480            }
25481        
25482        },
25483        
25484        
25485        /**
25486        * @method check
25487        * @description Checks the button at the specified index.
25488        * @param {Number} p_nIndex Number indicating the index of the button 
25489        * to check. 
25490        */
25491        check: function (p_nIndex) {
25492        
25493            var oButton = this.getButton(p_nIndex);
25494            
25495            if (oButton) {
25496        
25497                oButton.set("checked", true);
25498            
25499            }
25500        
25501        },
25502        
25503        
25504        /**
25505        * @method destroy
25506        * @description Removes the button group's element from its parent 
25507        * element and removes all event handlers.
25508        */
25509        destroy: function () {
25510        
25511            this.logger.log("Destroying...");
25512        
25513            var nButtons = this._buttons.length,
25514                oElement = this.get("element"),
25515                oParentNode = oElement.parentNode,
25516                i;
25517            
25518            if (nButtons > 0) {
25519        
25520                i = this._buttons.length - 1;
25521        
25522                do {
25523        
25524                    this._buttons[i].destroy();
25525        
25526                }
25527                while (i--);
25528            
25529            }
25530        
25531            this.logger.log("Removing DOM event handlers.");
25532        
25533            Event.purgeElement(oElement);
25534            
25535            this.logger.log("Removing from document.");
25536        
25537            oParentNode.removeChild(oElement);
25538        
25539        },
25540        
25541        
25542        /**
25543        * @method toString
25544        * @description Returns a string representing the button group.
25545        * @return {String}
25546        */
25547        toString: function () {
25548        
25549            return ("ButtonGroup " + this.get("id"));
25550        
25551        }
25552    
25553    });
25554
25555})();
25556YAHOO.register("button", YAHOO.widget.Button, {version: "2.9.0", build: "2800"});
25557(function () {
25558
25559	var Lang = YAHOO.lang,
25560		UA = YAHOO.env.ua,
25561
25562		ButtonPrototype = YAHOO.widget.Button.prototype,
25563		fnButtonInitAttributes = ButtonPrototype.initAttributes,
25564
25565		ButtonGroupPrototype = YAHOO.widget.ButtonGroup.prototype,
25566		fnButtonGroupInitAttributes = ButtonGroupPrototype.initAttributes,
25567		fnButtonGroupAddButton = ButtonGroupPrototype.addButton,
25568
25569		m_bUseARIA = (UA.gecko && UA.gecko >= 1.9) || (UA.ie && UA.ie >= 8),
25570
25571		// Private constants for strings
25572
25573		_ARIA_PREFIX = "aria-",
25574		_USE_ARIA = "usearia",
25575		_CHECKED = "checked",
25576		_TYPE = "type",
25577		_MENU = "menu",
25578		_SPLIT = "split",
25579		_HAS_POPUP = "haspopup",
25580		_RENDER = "render",
25581		_RADIO = "radio",
25582		_CHECKBOX = "checkbox",
25583		_ROLE = "role",
25584		_CHECKED_CHANGE = "checkedChange",
25585		_PRESENTATION = "presentation",
25586		_ELEMENT = "element",
25587		_RADIO_GROUP = "radiogroup",
25588		_CHECKED_BUTTON_CHANGE = "checkedButtonChange",
25589		_APPEND_TO = "appendTo",
25590		_LABELLED_BY = "labelledby",
25591		_DESCRIBED_BY = "describedby",
25592		_ID = "id";
25593
25594
25595	if (m_bUseARIA) {
25596
25597		ButtonPrototype.RADIO_DEFAULT_TITLE = "";
25598		ButtonPrototype.RADIO_CHECKED_TITLE = "";
25599		ButtonPrototype.CHECKBOX_DEFAULT_TITLE = "";
25600		ButtonPrototype.CHECKBOX_CHECKED_TITLE = "";
25601
25602	}
25603		
25604
25605	// Button ARIA plugin
25606
25607	var setARIARole = function (element, role) {
25608	
25609		element.setAttribute(_ROLE, role);
25610	
25611	};
25612
25613
25614	var setARIAProperty = function (element, property, value) {
25615
25616		element.setAttribute((_ARIA_PREFIX + property), value);
25617	
25618	};	
25619
25620
25621	var enableARIAForMenu = function (type, args, button) {
25622
25623		this.cfg.setProperty(_USE_ARIA, true);
25624		this.cfg.setProperty(_LABELLED_BY, button.get(_ID));
25625	
25626	};
25627
25628
25629	var onAppendTo = function () {
25630
25631		this._menu.subscribe(_RENDER, enableARIAForMenu, this);
25632	
25633	};	
25634
25635
25636	var toggleARIACheckedState = function (event) {
25637
25638		setARIAProperty(this._button, _CHECKED, event.newValue);
25639	
25640	};
25641	
25642
25643	Lang.augmentObject(ButtonPrototype, {
25644
25645		_setUseARIA: function (p_bUseARIA) {
25646		
25647			var sType = this.get(_TYPE),
25648				oButtonEl = this._button;
25649		
25650
25651			if (p_bUseARIA) {
25652			
25653				switch (sType) {
25654				
25655					case _MENU:
25656					case _SPLIT:
25657	
25658						setARIAProperty(oButtonEl, _HAS_POPUP, true);
25659
25660						this.on(_APPEND_TO, onAppendTo);
25661					
25662					break;
25663					
25664	
25665					case _RADIO:
25666					case _CHECKBOX:
25667					
25668						setARIARole(oButtonEl, sType);
25669
25670						setARIAProperty(oButtonEl, _CHECKED, this.get(_CHECKED));
25671						
25672						this.on(_CHECKED_CHANGE, toggleARIACheckedState);
25673					
25674					break;
25675				
25676				}
25677			
25678			}
25679		
25680		},
25681	
25682	
25683		initAttributes: function (p_oAttributes) {
25684			
25685            /**
25686            * @attribute usearia
25687            * @description Boolean indicating if use of the WAI-ARIA Roles and States should 
25688            * be enabled.
25689            * @type Boolean
25690            * @default true for Firefox 3 and IE 8, false for all other browsers.
25691            */	
25692			this.setAttributeConfig(_USE_ARIA, {
25693		
25694				value: p_oAttributes.usearia || m_bUseARIA,
25695				validator: Lang.isBoolean,
25696				writeOnce: true,
25697				method: this._setUseARIA
25698		
25699			});
25700
25701			fnButtonInitAttributes.apply(this, arguments);
25702
25703			if (m_bUseARIA) {
25704				this.set(_USE_ARIA, true);			
25705			}
25706	
25707		}
25708	
25709	}, "initAttributes", "_setUseARIA");
25710	
25711
25712
25713	// ButtonGroup ARIA plugin
25714		
25715	var updateTabIndex = function (event) {
25716
25717		var oPreviousButton = event.prevValue;
25718
25719		if (oPreviousButton) {
25720			oPreviousButton._button.tabIndex = -1;
25721		}
25722
25723		event.newValue._button.tabIndex = 0;
25724	
25725	};
25726	
25727
25728	Lang.augmentObject(ButtonGroupPrototype, {
25729	
25730		addButton: function (p_oButton) {
25731		
25732			var oButton = fnButtonGroupAddButton.call(this, p_oButton),
25733				oButtonEl,
25734				oParentNode;
25735			 
25736			if (this.get(_USE_ARIA)) {
25737	
25738				oButton.set(_USE_ARIA, true);
25739				
25740				oButtonEl = oButton._button;
25741				oParentNode = oButtonEl.parentNode;
25742				
25743				setARIARole(oParentNode, _PRESENTATION);
25744				setARIARole(oParentNode.parentNode, _PRESENTATION);
25745				
25746				oButtonEl.tabIndex = oButton.get(_CHECKED) ? 0 : -1;
25747	
25748			}
25749			 
25750			return oButton;
25751		
25752		},
25753
25754		_setUseARIA: function (p_bUseARIA) {	
25755	
25756			if (p_bUseARIA) {
25757	
25758				setARIARole(this.get(_ELEMENT), _RADIO_GROUP);
25759				
25760				this.on(_CHECKED_BUTTON_CHANGE, updateTabIndex);
25761				
25762			}
25763		
25764		},
25765
25766		_setLabelledBy: function (id) {
25767
25768			if (this.get(_USE_ARIA)) {
25769				setARIAProperty(this.get(_ELEMENT), _LABELLED_BY, id);
25770			}
25771			
25772		},
25773		
25774		_setDescribedBy: function (id) {
25775
25776			if (this.get(_USE_ARIA)) {
25777				setARIAProperty(this.get(_ELEMENT), _DESCRIBED_BY, id);
25778			}
25779		
25780		},
25781	
25782		initAttributes: function (p_oAttributes) {
25783
25784            /**
25785            * @attribute usearia
25786            * @description Boolean indicating if use of the WAI-ARIA Roles and States should 
25787            * be enabled.
25788            * @type Boolean
25789            * @default true for Firefox 3 and IE 8, false for all other browsers.
25790            */
25791			this.setAttributeConfig(_USE_ARIA, {
25792		
25793				value: p_oAttributes.usearia || m_bUseARIA,
25794				validator: Lang.isBoolean,
25795				writeOnce: true,
25796				method: this._setUseARIA
25797		
25798			});
25799
25800
25801            /**
25802            * @attribute labelledby
25803            * @description String representing the id of the element that labels the ButtonGroup.
25804            * Maps directly to the <a href="http://www.w3.org/TR/wai-aria/#labelledby">
25805            * <code>aria-labelledby</code></a> attribute.
25806            * @type String
25807            * @default null
25808            */
25809			this.setAttributeConfig(_LABELLED_BY, {
25810		
25811				value: p_oAttributes.labelledby,
25812				validator: Lang.isString,
25813				method: this._setLabelledBy
25814
25815			});
25816
25817
25818            /**
25819            * @attribute describedby
25820            * @description String representing the id of the element that describes the ButtonGroup.
25821            * Maps directly to the <a href="http://www.w3.org/TR/wai-aria/#describedby">
25822            * <code>aria-describedby</code></a> attribute.
25823            * @type String
25824            * @default null
25825            */
25826			this.setAttributeConfig(_DESCRIBED_BY, {
25827		
25828				value: p_oAttributes.describedby,
25829				validator: Lang.isString,
25830				method: this._setDescribedBy
25831
25832			});
25833
25834
25835			fnButtonGroupInitAttributes.apply(this, arguments);
25836
25837
25838			if (m_bUseARIA) {
25839				this.set(_USE_ARIA, true);			
25840			}
25841		
25842		}
25843	
25844	}, "initAttributes", "_setUseARIA", "_setLabelledBy", "_setDescribedBy", "addButton");
25845	
25846
25847}());
25848YAHOO.register("buttonariaplugin", YAHOO.widget.Button, {version: "@VERSION@", build: "@BUILD@"});
25849/*
25850Copyright (c) 2011, Yahoo! Inc. All rights reserved.
25851Code licensed under the BSD License:
25852http://developer.yahoo.com/yui/license.html
25853version: 2.9.0
25854*/
25855(function () {
25856
25857    /**
25858    * Config is a utility used within an Object to allow the implementer to
25859    * maintain a list of local configuration properties and listen for changes 
25860    * to those properties dynamically using CustomEvent. The initial values are 
25861    * also maintained so that the configuration can be reset at any given point 
25862    * to its initial state.
25863    * @namespace YAHOO.util
25864    * @class Config
25865    * @constructor
25866    * @param {Object} owner The owner Object to which this Config Object belongs
25867    */
25868    YAHOO.util.Config = function (owner) {
25869
25870        if (owner) {
25871            this.init(owner);
25872        }
25873
25874        if (!owner) {  YAHOO.log("No owner specified for Config object", "error", "Config"); }
25875
25876    };
25877
25878
25879    var Lang = YAHOO.lang,
25880        CustomEvent = YAHOO.util.CustomEvent,
25881        Config = YAHOO.util.Config;
25882
25883
25884    /**
25885     * Constant representing the CustomEvent type for the config changed event.
25886     * @property YAHOO.util.Config.CONFIG_CHANGED_EVENT
25887     * @private
25888     * @static
25889     * @final
25890     */
25891    Config.CONFIG_CHANGED_EVENT = "configChanged";
25892    
25893    /**
25894     * Constant representing the boolean type string
25895     * @property YAHOO.util.Config.BOOLEAN_TYPE
25896     * @private
25897     * @static
25898     * @final
25899     */
25900    Config.BOOLEAN_TYPE = "boolean";
25901    
25902    Config.prototype = {
25903     
25904        /**
25905        * Object reference to the owner of this Config Object
25906        * @property owner
25907        * @type Object
25908        */
25909        owner: null,
25910        
25911        /**
25912        * Boolean flag that specifies whether a queue is currently 
25913        * being executed
25914        * @property queueInProgress
25915        * @type Boolean
25916        */
25917        queueInProgress: false,
25918        
25919        /**
25920        * Maintains the local collection of configuration property objects and 
25921        * their specified values
25922        * @property config
25923        * @private
25924        * @type Object
25925        */ 
25926        config: null,
25927        
25928        /**
25929        * Maintains the local collection of configuration property objects as 
25930        * they were initially applied.
25931        * This object is used when resetting a property.
25932        * @property initialConfig
25933        * @private
25934        * @type Object
25935        */ 
25936        initialConfig: null,
25937        
25938        /**
25939        * Maintains the local, normalized CustomEvent queue
25940        * @property eventQueue
25941        * @private
25942        * @type Object
25943        */ 
25944        eventQueue: null,
25945        
25946        /**
25947        * Custom Event, notifying subscribers when Config properties are set 
25948        * (setProperty is called without the silent flag
25949        * @event configChangedEvent
25950        */
25951        configChangedEvent: null,
25952    
25953        /**
25954        * Initializes the configuration Object and all of its local members.
25955        * @method init
25956        * @param {Object} owner The owner Object to which this Config 
25957        * Object belongs
25958        */
25959        init: function (owner) {
25960    
25961            this.owner = owner;
25962    
25963            this.configChangedEvent = 
25964                this.createEvent(Config.CONFIG_CHANGED_EVENT);
25965    
25966            this.configChangedEvent.signature = CustomEvent.LIST;
25967            this.queueInProgress = false;
25968            this.config = {};
25969            this.initialConfig = {};
25970            this.eventQueue = [];
25971        
25972        },
25973        
25974        /**
25975        * Validates that the value passed in is a Boolean.
25976        * @method checkBoolean
25977        * @param {Object} val The value to validate
25978        * @return {Boolean} true, if the value is valid
25979        */ 
25980        checkBoolean: function (val) {
25981            return (typeof val == Config.BOOLEAN_TYPE);
25982        },
25983        
25984        /**
25985        * Validates that the value passed in is a number.
25986        * @method checkNumber
25987        * @param {Object} val The value to validate
25988        * @return {Boolean} true, if the value is valid
25989        */
25990        checkNumber: function (val) {
25991            return (!isNaN(val));
25992        },
25993        
25994        /**
25995        * Fires a configuration property event using the specified value. 
25996        * @method fireEvent
25997        * @private
25998        * @param {String} key The configuration property's name
25999        * @param {value} Object The value of the correct type for the property
26000        */ 
26001        fireEvent: function ( key, value ) {
26002            YAHOO.log("Firing Config event: " + key + "=" + value, "info", "Config");
26003            var property = this.config[key];
26004        
26005            if (property && property.event) {
26006                property.event.fire(value);
26007            } 
26008        },
26009        
26010        /**
26011        * Adds a property to the Config Object's private config hash.
26012        * @method addProperty
26013        * @param {String} key The configuration property's name
26014        * @param {Object} propertyObject The Object containing all of this 
26015        * property's arguments
26016        */
26017        addProperty: function ( key, propertyObject ) {
26018            key = key.toLowerCase();
26019            YAHOO.log("Added property: " + key, "info", "Config");
26020        
26021            this.config[key] = propertyObject;
26022        
26023            propertyObject.event = this.createEvent(key, { scope: this.owner });
26024            propertyObject.event.signature = CustomEvent.LIST;
26025            
26026            
26027            propertyObject.key = key;
26028        
26029            if (propertyObject.handler) {
26030                propertyObject.event.subscribe(propertyObject.handler, 
26031                    this.owner);
26032            }
26033        
26034            this.setProperty(key, propertyObject.value, true);
26035            
26036            if (! propertyObject.suppressEvent) {
26037                this.queueProperty(key, propertyObject.value);
26038            }
26039            
26040        },
26041        
26042        /**
26043        * Returns a key-value configuration map of the values currently set in  
26044        * the Config Object.
26045        * @method getConfig
26046        * @return {Object} The current config, represented in a key-value map
26047        */
26048        getConfig: function () {
26049        
26050            var cfg = {},
26051                currCfg = this.config,
26052                prop,
26053                property;
26054                
26055            for (prop in currCfg) {
26056                if (Lang.hasOwnProperty(currCfg, prop)) {
26057                    property = currCfg[prop];
26058                    if (property && property.event) {
26059                        cfg[prop] = property.value;
26060                    }
26061                }
26062            }
26063
26064            return cfg;
26065        },
26066        
26067        /**
26068        * Returns the value of specified property.
26069        * @method getProperty
26070        * @param {String} key The name of the property
26071        * @return {Object}  The value of the specified property
26072        */
26073        getProperty: function (key) {
26074            var property = this.config[key.toLowerCase()];
26075            if (property && property.event) {
26076                return property.value;
26077            } else {
26078                return undefined;
26079            }
26080        },
26081        
26082        /**
26083        * Resets the specified property's value to its initial value.
26084        * @method resetProperty
26085        * @param {String} key The name of the property
26086        * @return {Boolean} True is the property was reset, false if not
26087        */
26088        resetProperty: function (key) {
26089            key = key.toLowerCase();
26090
26091            var property = this.config[key];
26092
26093            if (property && property.event) {
26094                if (key in this.initialConfig) {
26095                    this.setProperty(key, this.initialConfig[key]);
26096                    return true;
26097                }
26098            } else {
26099                return false;
26100            }
26101        },
26102        
26103        /**
26104        * Sets the value of a property. If the silent property is passed as 
26105        * true, the property's event will not be fired.
26106        * @method setProperty
26107        * @param {String} key The name of the property
26108        * @param {String} value The value to set the property to
26109        * @param {Boolean} silent Whether the value should be set silently, 
26110        * without firing the property event.
26111        * @return {Boolean} True, if the set was successful, false if it failed.
26112        */
26113        setProperty: function (key, value, silent) {
26114        
26115            var property;
26116        
26117            key = key.toLowerCase();
26118            YAHOO.log("setProperty: " + key + "=" + value, "info", "Config");
26119        
26120            if (this.queueInProgress && ! silent) {
26121                // Currently running through a queue... 
26122                this.queueProperty(key,value);
26123                return true;
26124    
26125            } else {
26126                property = this.config[key];
26127                if (property && property.event) {
26128                    if (property.validator && !property.validator(value)) {
26129                        return false;
26130                    } else {
26131                        property.value = value;
26132                        if (! silent) {
26133                            this.fireEvent(key, value);
26134                            this.configChangedEvent.fire([key, value]);
26135                        }
26136                        return true;
26137                    }
26138                } else {
26139                    return false;
26140                }
26141            }
26142        },
26143        
26144        /**
26145        * Sets the value of a property and queues its event to execute. If the 
26146        * event is already scheduled to execute, it is
26147        * moved from its current position to the end of the queue.
26148        * @method queueProperty
26149        * @param {String} key The name of the property
26150        * @param {String} value The value to set the property to
26151        * @return {Boolean}  true, if the set was successful, false if 
26152        * it failed.
26153        */ 
26154        queueProperty: function (key, value) {
26155        
26156            key = key.toLowerCase();
26157            YAHOO.log("queueProperty: " + key + "=" + value, "info", "Config");
26158        
26159            var property = this.config[key],
26160                foundDuplicate = false,
26161                iLen,
26162                queueItem,
26163                queueItemKey,
26164                queueItemValue,
26165                sLen,
26166                supercedesCheck,
26167                qLen,
26168                queueItemCheck,
26169                queueItemCheckKey,
26170                queueItemCheckValue,
26171                i,
26172                s,
26173                q;
26174                                
26175            if (property && property.event) {
26176    
26177                if (!Lang.isUndefined(value) && property.validator && 
26178                    !property.validator(value)) { // validator
26179                    return false;
26180                } else {
26181        
26182                    if (!Lang.isUndefined(value)) {
26183                        property.value = value;
26184                    } else {
26185                        value = property.value;
26186                    }
26187        
26188                    foundDuplicate = false;
26189                    iLen = this.eventQueue.length;
26190        
26191                    for (i = 0; i < iLen; i++) {
26192                        queueItem = this.eventQueue[i];
26193        
26194                        if (queueItem) {
26195                            queueItemKey = queueItem[0];
26196                            queueItemValue = queueItem[1];
26197
26198                            if (queueItemKey == key) {
26199    
26200                                /*
26201                                    found a dupe... push to end of queue, null 
26202                                    current item, and break
26203                                */
26204    
26205                                this.eventQueue[i] = null;
26206    
26207                                this.eventQueue.push(
26208                                    [key, (!Lang.isUndefined(value) ? 
26209                                    value : queueItemValue)]);
26210    
26211                                foundDuplicate = true;
26212                                break;
26213                            }
26214                        }
26215                    }
26216                    
26217                    // this is a refire, or a new property in the queue
26218    
26219                    if (! foundDuplicate && !Lang.isUndefined(value)) { 
26220                        this.eventQueue.push([key, value]);
26221                    }
26222                }
26223        
26224                if (property.supercedes) {
26225
26226                    sLen = property.supercedes.length;
26227
26228                    for (s = 0; s < sLen; s++) {
26229
26230                        supercedesCheck = property.supercedes[s];
26231                        qLen = this.eventQueue.length;
26232
26233                        for (q = 0; q < qLen; q++) {
26234                            queueItemCheck = this.eventQueue[q];
26235
26236                            if (queueItemCheck) {
26237                                queueItemCheckKey = queueItemCheck[0];
26238                                queueItemCheckValue = queueItemCheck[1];
26239
26240                                if (queueItemCheckKey == 
26241                                    supercedesCheck.toLowerCase() ) {
26242
26243                                    this.eventQueue.push([queueItemCheckKey, 
26244                                        queueItemCheckValue]);
26245
26246                                    this.eventQueue[q] = null;
26247                                    break;
26248
26249                                }
26250                            }
26251                        }
26252                    }
26253                }
26254
26255                YAHOO.log("Config event queue: " + this.outputEventQueue(), "info", "Config");
26256
26257                return true;
26258            } else {
26259                return false;
26260            }
26261        },
26262        
26263        /**
26264        * Fires the event for a property using the property's current value.
26265        * @method refireEvent
26266        * @param {String} key The name of the property
26267        */
26268        refireEvent: function (key) {
26269    
26270            key = key.toLowerCase();
26271        
26272            var property = this.config[key];
26273    
26274            if (property && property.event && 
26275    
26276                !Lang.isUndefined(property.value)) {
26277    
26278                if (this.queueInProgress) {
26279    
26280                    this.queueProperty(key);
26281    
26282                } else {
26283    
26284                    this.fireEvent(key, property.value);
26285    
26286                }
26287    
26288            }
26289        },
26290        
26291        /**
26292        * Applies a key-value Object literal to the configuration, replacing  
26293        * any existing values, and queueing the property events.
26294        * Although the values will be set, fireQueue() must be called for their 
26295        * associated events to execute.
26296        * @method applyConfig
26297        * @param {Object} userConfig The configuration Object literal
26298        * @param {Boolean} init  When set to true, the initialConfig will 
26299        * be set to the userConfig passed in, so that calling a reset will 
26300        * reset the properties to the passed values.
26301        */
26302        applyConfig: function (userConfig, init) {
26303        
26304            var sKey,
26305                oConfig;
26306
26307            if (init) {
26308                oConfig = {};
26309                for (sKey in userConfig) {
26310                    if (Lang.hasOwnProperty(userConfig, sKey)) {
26311                        oConfig[sKey.toLowerCase()] = userConfig[sKey];
26312                    }
26313                }
26314                this.initialConfig = oConfig;
26315            }
26316
26317            for (sKey in userConfig) {
26318                if (Lang.hasOwnProperty(userConfig, sKey)) {
26319                    this.queueProperty(sKey, userConfig[sKey]);
26320                }
26321            }
26322        },
26323        
26324        /**
26325        * Refires the events for all configuration properties using their 
26326        * current values.
26327        * @method refresh
26328        */
26329        refresh: function () {
26330
26331            var prop;
26332
26333            for (prop in this.config) {
26334                if (Lang.hasOwnProperty(this.config, prop)) {
26335                    this.refireEvent(prop);
26336                }
26337            }
26338        },
26339        
26340        /**
26341        * Fires the normalized list of queued property change events
26342        * @method fireQueue
26343        */
26344        fireQueue: function () {
26345        
26346            var i, 
26347                queueItem,
26348                key,
26349                value,
26350                property;
26351        
26352            this.queueInProgress = true;
26353            for (i = 0;i < this.eventQueue.length; i++) {
26354                queueItem = this.eventQueue[i];
26355                if (queueItem) {
26356        
26357                    key = queueItem[0];
26358                    value = queueItem[1];
26359                    property = this.config[key];
26360
26361                    property.value = value;
26362
26363                    // Clear out queue entry, to avoid it being 
26364                    // re-added to the queue by any queueProperty/supercedes
26365                    // calls which are invoked during fireEvent
26366                    this.eventQueue[i] = null;
26367
26368                    this.fireEvent(key,value);
26369                }
26370            }
26371            
26372            this.queueInProgress = false;
26373            this.eventQueue = [];
26374        },
26375        
26376        /**
26377        * Subscribes an external handler to the change event for any 
26378        * given property. 
26379        * @method subscribeToConfigEvent
26380        * @param {String} key The property name
26381        * @param {Function} handler The handler function to use subscribe to 
26382        * the property's event
26383        * @param {Object} obj The Object to use for scoping the event handler 
26384        * (see CustomEvent documentation)
26385        * @param {Boolean} overrideContext Optional. If true, will override
26386        * "this" within the handler to map to the scope Object passed into the
26387        * method.
26388        * @return {Boolean} True, if the subscription was successful, 
26389        * otherwise false.
26390        */ 
26391        subscribeToConfigEvent: function (key, handler, obj, overrideContext) {
26392    
26393            var property = this.config[key.toLowerCase()];
26394    
26395            if (property && property.event) {
26396                if (!Config.alreadySubscribed(property.event, handler, obj)) {
26397                    property.event.subscribe(handler, obj, overrideContext);
26398                }
26399                return true;
26400            } else {
26401                return false;
26402            }
26403    
26404        },
26405        
26406        /**
26407        * Unsubscribes an external handler from the change event for any 
26408        * given property. 
26409        * @method unsubscribeFromConfigEvent
26410        * @param {String} key The property name
26411        * @param {Function} handler The handler function to use subscribe to 
26412        * the property's event
26413        * @param {Object} obj The Object to use for scoping the event 
26414        * handler (see CustomEvent documentation)
26415        * @return {Boolean} True, if the unsubscription was successful, 
26416        * otherwise false.
26417        */
26418        unsubscribeFromConfigEvent: function (key, handler, obj) {
26419            var property = this.config[key.toLowerCase()];
26420            if (property && property.event) {
26421                return property.event.unsubscribe(handler, obj);
26422            } else {
26423                return false;
26424            }
26425        },
26426        
26427        /**
26428        * Returns a string representation of the Config object
26429        * @method toString
26430        * @return {String} The Config object in string format.
26431        */
26432        toString: function () {
26433            var output = "Config";
26434            if (this.owner) {
26435                output += " [" + this.owner.toString() + "]";
26436            }
26437            return output;
26438        },
26439        
26440        /**
26441        * Returns a string representation of the Config object's current 
26442        * CustomEvent queue
26443        * @method outputEventQueue
26444        * @return {String} The string list of CustomEvents currently queued 
26445        * for execution
26446        */
26447        outputEventQueue: function () {
26448
26449            var output = "",
26450                queueItem,
26451                q,
26452                nQueue = this.eventQueue.length;
26453              
26454            for (q = 0; q < nQueue; q++) {
26455                queueItem = this.eventQueue[q];
26456                if (queueItem) {
26457                    output += queueItem[0] + "=" + queueItem[1] + ", ";
26458                }
26459            }
26460            return output;
26461        },
26462
26463        /**
26464        * Sets all properties to null, unsubscribes all listeners from each 
26465        * property's change event and all listeners from the configChangedEvent.
26466        * @method destroy
26467        */
26468        destroy: function () {
26469
26470            var oConfig = this.config,
26471                sProperty,
26472                oProperty;
26473
26474
26475            for (sProperty in oConfig) {
26476            
26477                if (Lang.hasOwnProperty(oConfig, sProperty)) {
26478
26479                    oProperty = oConfig[sProperty];
26480
26481                    oProperty.event.unsubscribeAll();
26482                    oProperty.event = null;
26483
26484                }
26485            
26486            }
26487            
26488            this.configChangedEvent.unsubscribeAll();
26489            
26490            this.configChangedEvent = null;
26491            this.owner = null;
26492            this.config = null;
26493            this.initialConfig = null;
26494            this.eventQueue = null;
26495        
26496        }
26497
26498    };
26499    
26500    
26501    
26502    /**
26503    * Checks to determine if a particular function/Object pair are already 
26504    * subscribed to the specified CustomEvent
26505    * @method YAHOO.util.Config.alreadySubscribed
26506    * @static
26507    * @param {YAHOO.util.CustomEvent} evt The CustomEvent for which to check 
26508    * the subscriptions
26509    * @param {Function} fn The function to look for in the subscribers list
26510    * @param {Object} obj The execution scope Object for the subscription
26511    * @return {Boolean} true, if the function/Object pair is already subscribed 
26512    * to the CustomEvent passed in
26513    */
26514    Config.alreadySubscribed = function (evt, fn, obj) {
26515    
26516        var nSubscribers = evt.subscribers.length,
26517            subsc,
26518            i;
26519
26520        if (nSubscribers > 0) {
26521            i = nSubscribers - 1;
26522            do {
26523                subsc = evt.subscribers[i];
26524                if (subsc && subsc.obj == obj && subsc.fn == fn) {
26525                    return true;
26526                }
26527            }
26528            while (i--);
26529        }
26530
26531        return false;
26532
26533    };
26534
26535    YAHOO.lang.augmentProto(Config, YAHOO.util.EventProvider);
26536
26537}());
26538(function () {
26539
26540    /**
26541    * The Container family of components is designed to enable developers to 
26542    * create different kinds of content-containing modules on the web. Module 
26543    * and Overlay are the most basic containers, and they can be used directly 
26544    * or extended to build custom containers. Also part of the Container family 
26545    * are four UI controls that extend Module and Overlay: Tooltip, Panel, 
26546    * Dialog, and SimpleDialog.
26547    * @module container
26548    * @title Container
26549    * @requires yahoo, dom, event 
26550    * @optional dragdrop, animation, button
26551    */
26552    
26553    /**
26554    * Module is a JavaScript representation of the Standard Module Format. 
26555    * Standard Module Format is a simple standard for markup containers where 
26556    * child nodes representing the header, body, and footer of the content are 
26557    * denoted using the CSS classes "hd", "bd", and "ft" respectively. 
26558    * Module is the base class for all other classes in the YUI 
26559    * Container package.
26560    * @namespace YAHOO.widget
26561    * @class Module
26562    * @constructor
26563    * @param {String} el The element ID representing the Module <em>OR</em>
26564    * @param {HTMLElement} el The element representing the Module
26565    * @param {Object} userConfig The configuration Object literal containing 
26566    * the configuration that should be set for this module. See configuration 
26567    * documentation for more details.
26568    */
26569    YAHOO.widget.Module = function (el, userConfig) {
26570        if (el) {
26571            this.init(el, userConfig);
26572        } else {
26573            YAHOO.log("No element or element ID specified" + 
26574                " for Module instantiation", "error");
26575        }
26576    };
26577
26578    var Dom = YAHOO.util.Dom,
26579        Config = YAHOO.util.Config,
26580        Event = YAHOO.util.Event,
26581        CustomEvent = YAHOO.util.CustomEvent,
26582        Module = YAHOO.widget.Module,
26583        UA = YAHOO.env.ua,
26584
26585        m_oModuleTemplate,
26586        m_oHeaderTemplate,
26587        m_oBodyTemplate,
26588        m_oFooterTemplate,
26589
26590        /**
26591        * Constant representing the name of the Module's events
26592        * @property EVENT_TYPES
26593        * @private
26594        * @final
26595        * @type Object
26596        */
26597        EVENT_TYPES = {
26598            "BEFORE_INIT": "beforeInit",
26599            "INIT": "init",
26600            "APPEND": "append",
26601            "BEFORE_RENDER": "beforeRender",
26602            "RENDER": "render",
26603            "CHANGE_HEADER": "changeHeader",
26604            "CHANGE_BODY": "changeBody",
26605            "CHANGE_FOOTER": "changeFooter",
26606            "CHANGE_CONTENT": "changeContent",
26607            "DESTROY": "destroy",
26608            "BEFORE_SHOW": "beforeShow",
26609            "SHOW": "show",
26610            "BEFORE_HIDE": "beforeHide",
26611            "HIDE": "hide"
26612        },
26613            
26614        /**
26615        * Constant representing the Module's configuration properties
26616        * @property DEFAULT_CONFIG
26617        * @private
26618        * @final
26619        * @type Object
26620        */
26621        DEFAULT_CONFIG = {
26622        
26623            "VISIBLE": { 
26624                key: "visible", 
26625                value: true, 
26626                validator: YAHOO.lang.isBoolean 
26627            },
26628
26629            "EFFECT": {
26630                key: "effect",
26631                suppressEvent: true,
26632                supercedes: ["visible"]
26633            },
26634
26635            "MONITOR_RESIZE": {
26636                key: "monitorresize",
26637                value: true
26638            },
26639
26640            "APPEND_TO_DOCUMENT_BODY": {
26641                key: "appendtodocumentbody",
26642                value: false
26643            }
26644        };
26645
26646    /**
26647    * Constant representing the prefix path to use for non-secure images
26648    * @property YAHOO.widget.Module.IMG_ROOT
26649    * @static
26650    * @final
26651    * @type String
26652    */
26653    Module.IMG_ROOT = null;
26654    
26655    /**
26656    * Constant representing the prefix path to use for securely served images
26657    * @property YAHOO.widget.Module.IMG_ROOT_SSL
26658    * @static
26659    * @final
26660    * @type String
26661    */
26662    Module.IMG_ROOT_SSL = null;
26663    
26664    /**
26665    * Constant for the default CSS class name that represents a Module
26666    * @property YAHOO.widget.Module.CSS_MODULE
26667    * @static
26668    * @final
26669    * @type String
26670    */
26671    Module.CSS_MODULE = "yui-module";
26672    
26673    /**
26674    * CSS classname representing the module header. NOTE: The classname is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
26675    * @property YAHOO.widget.Module.CSS_HEADER
26676    * @static
26677    * @final
26678    * @type String
26679    */
26680    Module.CSS_HEADER = "hd";
26681
26682    /**
26683    * CSS classname representing the module body. NOTE: The classname is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
26684    * @property YAHOO.widget.Module.CSS_BODY
26685    * @static
26686    * @final
26687    * @type String
26688    */
26689    Module.CSS_BODY = "bd";
26690    
26691    /**
26692    * CSS classname representing the module footer. NOTE: The classname is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
26693    * @property YAHOO.widget.Module.CSS_FOOTER
26694    * @static
26695    * @final
26696    * @type String
26697    */
26698    Module.CSS_FOOTER = "ft";
26699    
26700    /**
26701    * Constant representing the url for the "src" attribute of the iframe 
26702    * used to monitor changes to the browser's base font size
26703    * @property YAHOO.widget.Module.RESIZE_MONITOR_SECURE_URL
26704    * @static
26705    * @final
26706    * @type String
26707    */
26708    Module.RESIZE_MONITOR_SECURE_URL = "javascript:false;";
26709
26710    /**
26711    * Constant representing the buffer amount (in pixels) to use when positioning
26712    * the text resize monitor offscreen. The resize monitor is positioned
26713    * offscreen by an amount eqaul to its offsetHeight + the buffer value.
26714    * 
26715    * @property YAHOO.widget.Module.RESIZE_MONITOR_BUFFER
26716    * @static
26717    * @type Number
26718    */
26719    // Set to 1, to work around pixel offset in IE8, which increases when zoom is used
26720    Module.RESIZE_MONITOR_BUFFER = 1;
26721
26722    /**
26723    * Singleton CustomEvent fired when the font size is changed in the browser.
26724    * Opera's "zoom" functionality currently does not support text 
26725    * size detection.
26726    * @event YAHOO.widget.Module.textResizeEvent
26727    */
26728    Module.textResizeEvent = new CustomEvent("textResize");
26729
26730    /**
26731     * Helper utility method, which forces a document level 
26732     * redraw for Opera, which can help remove repaint
26733     * irregularities after applying DOM changes.
26734     *
26735     * @method YAHOO.widget.Module.forceDocumentRedraw
26736     * @static
26737     */
26738    Module.forceDocumentRedraw = function() {
26739        var docEl = document.documentElement;
26740        if (docEl) {
26741            docEl.className += " ";
26742            docEl.className = YAHOO.lang.trim(docEl.className);
26743        }
26744    };
26745
26746    function createModuleTemplate() {
26747
26748        if (!m_oModuleTemplate) {
26749            m_oModuleTemplate = document.createElement("div");
26750            
26751            m_oModuleTemplate.innerHTML = ("<div class=\"" + 
26752                Module.CSS_HEADER + "\"></div>" + "<div class=\"" + 
26753                Module.CSS_BODY + "\"></div><div class=\"" + 
26754                Module.CSS_FOOTER + "\"></div>");
26755
26756            m_oHeaderTemplate = m_oModuleTemplate.firstChild;
26757            m_oBodyTemplate = m_oHeaderTemplate.nextSibling;
26758            m_oFooterTemplate = m_oBodyTemplate.nextSibling;
26759        }
26760
26761        return m_oModuleTemplate;
26762    }
26763
26764    function createHeader() {
26765        if (!m_oHeaderTemplate) {
26766            createModuleTemplate();
26767        }
26768        return (m_oHeaderTemplate.cloneNode(false));
26769    }
26770
26771    function createBody() {
26772        if (!m_oBodyTemplate) {
26773            createModuleTemplate();
26774        }
26775        return (m_oBodyTemplate.cloneNode(false));
26776    }
26777
26778    function createFooter() {
26779        if (!m_oFooterTemplate) {
26780            createModuleTemplate();
26781        }
26782        return (m_oFooterTemplate.cloneNode(false));
26783    }
26784
26785    Module.prototype = {
26786
26787        /**
26788        * The class's constructor function
26789        * @property contructor
26790        * @type Function
26791        */
26792        constructor: Module,
26793        
26794        /**
26795        * The main module element that contains the header, body, and footer
26796        * @property element
26797        * @type HTMLElement
26798        */
26799        element: null,
26800
26801        /**
26802        * The header element, denoted with CSS class "hd"
26803        * @property header
26804        * @type HTMLElement
26805        */
26806        header: null,
26807
26808        /**
26809        * The body element, denoted with CSS class "bd"
26810        * @property body
26811        * @type HTMLElement
26812        */
26813        body: null,
26814
26815        /**
26816        * The footer element, denoted with CSS class "ft"
26817        * @property footer
26818        * @type HTMLElement
26819        */
26820        footer: null,
26821
26822        /**
26823        * The id of the element
26824        * @property id
26825        * @type String
26826        */
26827        id: null,
26828
26829        /**
26830        * A string representing the root path for all images created by
26831        * a Module instance.
26832        * @deprecated It is recommend that any images for a Module be applied
26833        * via CSS using the "background-image" property.
26834        * @property imageRoot
26835        * @type String
26836        */
26837        imageRoot: Module.IMG_ROOT,
26838
26839        /**
26840        * Initializes the custom events for Module which are fired 
26841        * automatically at appropriate times by the Module class.
26842        * @method initEvents
26843        */
26844        initEvents: function () {
26845
26846            var SIGNATURE = CustomEvent.LIST;
26847
26848            /**
26849            * CustomEvent fired prior to class initalization.
26850            * @event beforeInitEvent
26851            * @param {class} classRef class reference of the initializing 
26852            * class, such as this.beforeInitEvent.fire(Module)
26853            */
26854            this.beforeInitEvent = this.createEvent(EVENT_TYPES.BEFORE_INIT);
26855            this.beforeInitEvent.signature = SIGNATURE;
26856
26857            /**
26858            * CustomEvent fired after class initalization.
26859            * @event initEvent
26860            * @param {class} classRef class reference of the initializing 
26861            * class, such as this.beforeInitEvent.fire(Module)
26862            */  
26863            this.initEvent = this.createEvent(EVENT_TYPES.INIT);
26864            this.initEvent.signature = SIGNATURE;
26865
26866            /**
26867            * CustomEvent fired when the Module is appended to the DOM
26868            * @event appendEvent
26869            */
26870            this.appendEvent = this.createEvent(EVENT_TYPES.APPEND);
26871            this.appendEvent.signature = SIGNATURE;
26872
26873            /**
26874            * CustomEvent fired before the Module is rendered
26875            * @event beforeRenderEvent
26876            */
26877            this.beforeRenderEvent = this.createEvent(EVENT_TYPES.BEFORE_RENDER);
26878            this.beforeRenderEvent.signature = SIGNATURE;
26879        
26880            /**
26881            * CustomEvent fired after the Module is rendered
26882            * @event renderEvent
26883            */
26884            this.renderEvent = this.createEvent(EVENT_TYPES.RENDER);
26885            this.renderEvent.signature = SIGNATURE;
26886        
26887            /**
26888            * CustomEvent fired when the header content of the Module 
26889            * is modified
26890            * @event changeHeaderEvent
26891            * @param {String/HTMLElement} content String/element representing 
26892            * the new header content
26893            */
26894            this.changeHeaderEvent = this.createEvent(EVENT_TYPES.CHANGE_HEADER);
26895            this.changeHeaderEvent.signature = SIGNATURE;
26896            
26897            /**
26898            * CustomEvent fired when the body content of the Module is modified
26899            * @event changeBodyEvent
26900            * @param {String/HTMLElement} content String/element representing 
26901            * the new body content
26902            */  
26903            this.changeBodyEvent = this.createEvent(EVENT_TYPES.CHANGE_BODY);
26904            this.changeBodyEvent.signature = SIGNATURE;
26905            
26906            /**
26907            * CustomEvent fired when the footer content of the Module 
26908            * is modified
26909            * @event changeFooterEvent
26910            * @param {String/HTMLElement} content String/element representing 
26911            * the new footer content
26912            */
26913            this.changeFooterEvent = this.createEvent(EVENT_TYPES.CHANGE_FOOTER);
26914            this.changeFooterEvent.signature = SIGNATURE;
26915        
26916            /**
26917            * CustomEvent fired when the content of the Module is modified
26918            * @event changeContentEvent
26919            */
26920            this.changeContentEvent = this.createEvent(EVENT_TYPES.CHANGE_CONTENT);
26921            this.changeContentEvent.signature = SIGNATURE;
26922
26923            /**
26924            * CustomEvent fired when the Module is destroyed
26925            * @event destroyEvent
26926            */
26927            this.destroyEvent = this.createEvent(EVENT_TYPES.DESTROY);
26928            this.destroyEvent.signature = SIGNATURE;
26929
26930            /**
26931            * CustomEvent fired before the Module is shown
26932            * @event beforeShowEvent
26933            */
26934            this.beforeShowEvent = this.createEvent(EVENT_TYPES.BEFORE_SHOW);
26935            this.beforeShowEvent.signature = SIGNATURE;
26936
26937            /**
26938            * CustomEvent fired after the Module is shown
26939            * @event showEvent
26940            */
26941            this.showEvent = this.createEvent(EVENT_TYPES.SHOW);
26942            this.showEvent.signature = SIGNATURE;
26943
26944            /**
26945            * CustomEvent fired before the Module is hidden
26946            * @event beforeHideEvent
26947            */
26948            this.beforeHideEvent = this.createEvent(EVENT_TYPES.BEFORE_HIDE);
26949            this.beforeHideEvent.signature = SIGNATURE;
26950
26951            /**
26952            * CustomEvent fired after the Module is hidden
26953            * @event hideEvent
26954            */
26955            this.hideEvent = this.createEvent(EVENT_TYPES.HIDE);
26956            this.hideEvent.signature = SIGNATURE;
26957        }, 
26958
26959        /**
26960        * String identifying whether the current platform is windows or mac. This property
26961        * currently only identifies these 2 platforms, and returns false otherwise. 
26962        * @property platform
26963        * @deprecated Use YAHOO.env.ua
26964        * @type {String|Boolean}
26965        */
26966        platform: function () {
26967            var ua = navigator.userAgent.toLowerCase();
26968
26969            if (ua.indexOf("windows") != -1 || ua.indexOf("win32") != -1) {
26970                return "windows";
26971            } else if (ua.indexOf("macintosh") != -1) {
26972                return "mac";
26973            } else {
26974                return false;
26975            }
26976        }(),
26977        
26978        /**
26979        * String representing the user-agent of the browser
26980        * @deprecated Use YAHOO.env.ua
26981        * @property browser
26982        * @type {String|Boolean}
26983        */
26984        browser: function () {
26985            var ua = navigator.userAgent.toLowerCase();
26986            /*
26987                 Check Opera first in case of spoof and check Safari before
26988                 Gecko since Safari's user agent string includes "like Gecko"
26989            */
26990            if (ua.indexOf('opera') != -1) { 
26991                return 'opera';
26992            } else if (ua.indexOf('msie 7') != -1) {
26993                return 'ie7';
26994            } else if (ua.indexOf('msie') != -1) {
26995                return 'ie';
26996            } else if (ua.indexOf('safari') != -1) { 
26997                return 'safari';
26998            } else if (ua.indexOf('gecko') != -1) {
26999                return 'gecko';
27000            } else {
27001                return false;
27002            }
27003        }(),
27004        
27005        /**
27006        * Boolean representing whether or not the current browsing context is 
27007        * secure (https)
27008        * @property isSecure
27009        * @type Boolean
27010        */
27011        isSecure: function () {
27012            if (window.location.href.toLowerCase().indexOf("https") === 0) {
27013                return true;
27014            } else {
27015                return false;
27016            }
27017        }(),
27018        
27019        /**
27020        * Initializes the custom events for Module which are fired 
27021        * automatically at appropriate times by the Module class.
27022        */
27023        initDefaultConfig: function () {
27024            // Add properties //
27025            /**
27026            * Specifies whether the Module is visible on the page.
27027            * @config visible
27028            * @type Boolean
27029            * @default true
27030            */
27031            this.cfg.addProperty(DEFAULT_CONFIG.VISIBLE.key, {
27032                handler: this.configVisible, 
27033                value: DEFAULT_CONFIG.VISIBLE.value, 
27034                validator: DEFAULT_CONFIG.VISIBLE.validator
27035            });
27036
27037            /**
27038            * <p>
27039            * Object or array of objects representing the ContainerEffect 
27040            * classes that are active for animating the container.
27041            * </p>
27042            * <p>
27043            * <strong>NOTE:</strong> Although this configuration 
27044            * property is introduced at the Module level, an out of the box
27045            * implementation is not shipped for the Module class so setting
27046            * the proroperty on the Module class has no effect. The Overlay 
27047            * class is the first class to provide out of the box ContainerEffect 
27048            * support.
27049            * </p>
27050            * @config effect
27051            * @type Object
27052            * @default null
27053            */
27054            this.cfg.addProperty(DEFAULT_CONFIG.EFFECT.key, {
27055                handler: this.configEffect,
27056                suppressEvent: DEFAULT_CONFIG.EFFECT.suppressEvent, 
27057                supercedes: DEFAULT_CONFIG.EFFECT.supercedes
27058            });
27059
27060            /**
27061            * Specifies whether to create a special proxy iframe to monitor 
27062            * for user font resizing in the document
27063            * @config monitorresize
27064            * @type Boolean
27065            * @default true
27066            */
27067            this.cfg.addProperty(DEFAULT_CONFIG.MONITOR_RESIZE.key, {
27068                handler: this.configMonitorResize,
27069                value: DEFAULT_CONFIG.MONITOR_RESIZE.value
27070            });
27071
27072            /**
27073            * Specifies if the module should be rendered as the first child 
27074            * of document.body or appended as the last child when render is called
27075            * with document.body as the "appendToNode".
27076            * <p>
27077            * Appending to the body while the DOM is still being constructed can 
27078            * lead to Operation Aborted errors in IE hence this flag is set to 
27079            * false by default.
27080            * </p>
27081            * 
27082            * @config appendtodocumentbody
27083            * @type Boolean
27084            * @default false
27085            */
27086            this.cfg.addProperty(DEFAULT_CONFIG.APPEND_TO_DOCUMENT_BODY.key, {
27087                value: DEFAULT_CONFIG.APPEND_TO_DOCUMENT_BODY.value
27088            });
27089        },
27090
27091        /**
27092        * The Module class's initialization method, which is executed for
27093        * Module and all of its subclasses. This method is automatically 
27094        * called by the constructor, and  sets up all DOM references for 
27095        * pre-existing markup, and creates required markup if it is not 
27096        * already present.
27097        * <p>
27098        * If the element passed in does not have an id, one will be generated
27099        * for it.
27100        * </p>
27101        * @method init
27102        * @param {String} el The element ID representing the Module <em>OR</em>
27103        * @param {HTMLElement} el The element representing the Module
27104        * @param {Object} userConfig The configuration Object literal 
27105        * containing the configuration that should be set for this module. 
27106        * See configuration documentation for more details.
27107        */
27108        init: function (el, userConfig) {
27109
27110            var elId, child;
27111
27112            this.initEvents();
27113            this.beforeInitEvent.fire(Module);
27114
27115            /**
27116            * The Module's Config object used for monitoring 
27117            * configuration properties.
27118            * @property cfg
27119            * @type YAHOO.util.Config
27120            */
27121            this.cfg = new Config(this);
27122
27123            if (this.isSecure) {
27124                this.imageRoot = Module.IMG_ROOT_SSL;
27125            }
27126
27127            if (typeof el == "string") {
27128                elId = el;
27129                el = document.getElementById(el);
27130                if (! el) {
27131                    el = (createModuleTemplate()).cloneNode(false);
27132                    el.id = elId;
27133                }
27134            }
27135
27136            this.id = Dom.generateId(el);
27137            this.element = el;
27138
27139            child = this.element.firstChild;
27140
27141            if (child) {
27142                var fndHd = false, fndBd = false, fndFt = false;
27143                do {
27144                    // We're looking for elements
27145                    if (1 == child.nodeType) {
27146                        if (!fndHd && Dom.hasClass(child, Module.CSS_HEADER)) {
27147                            this.header = child;
27148                            fndHd = true;
27149                        } else if (!fndBd && Dom.hasClass(child, Module.CSS_BODY)) {
27150                            this.body = child;
27151                            fndBd = true;
27152                        } else if (!fndFt && Dom.hasClass(child, Module.CSS_FOOTER)){
27153                            this.footer = child;
27154                            fndFt = true;
27155                        }
27156                    }
27157                } while ((child = child.nextSibling));
27158            }
27159
27160            this.initDefaultConfig();
27161
27162            Dom.addClass(this.element, Module.CSS_MODULE);
27163
27164            if (userConfig) {
27165                this.cfg.applyConfig(userConfig, true);
27166            }
27167
27168            /*
27169                Subscribe to the fireQueue() method of Config so that any 
27170                queued configuration changes are excecuted upon render of 
27171                the Module
27172            */ 
27173
27174            if (!Config.alreadySubscribed(this.renderEvent, this.cfg.fireQueue, this.cfg)) {
27175                this.renderEvent.subscribe(this.cfg.fireQueue, this.cfg, true);
27176            }
27177
27178            this.initEvent.fire(Module);
27179        },
27180
27181        /**
27182        * Initialize an empty IFRAME that is placed out of the visible area 
27183        * that can be used to detect text resize.
27184        * @method initResizeMonitor
27185        */
27186        initResizeMonitor: function () {
27187
27188            var isGeckoWin = (UA.gecko && this.platform == "windows");
27189            if (isGeckoWin) {
27190                // Help prevent spinning loading icon which 
27191                // started with FireFox 2.0.0.8/Win
27192                var self = this;
27193                setTimeout(function(){self._initResizeMonitor();}, 0);
27194            } else {
27195                this._initResizeMonitor();
27196            }
27197        },
27198
27199        /**
27200         * Create and initialize the text resize monitoring iframe.
27201         * 
27202         * @protected
27203         * @method _initResizeMonitor
27204         */
27205        _initResizeMonitor : function() {
27206
27207            var oDoc, 
27208                oIFrame, 
27209                sHTML;
27210
27211            function fireTextResize() {
27212                Module.textResizeEvent.fire();
27213            }
27214
27215            if (!UA.opera) {
27216                oIFrame = Dom.get("_yuiResizeMonitor");
27217
27218                var supportsCWResize = this._supportsCWResize();
27219
27220                if (!oIFrame) {
27221                    oIFrame = document.createElement("iframe");
27222
27223                    if (this.isSecure && Module.RESIZE_MONITOR_SECURE_URL && UA.ie) {
27224                        oIFrame.src = Module.RESIZE_MONITOR_SECURE_URL;
27225                    }
27226
27227                    if (!supportsCWResize) {
27228                        // Can't monitor on contentWindow, so fire from inside iframe
27229                        sHTML = ["<html><head><script ",
27230                                 "type=\"text/javascript\">",
27231                                 "window.onresize=function(){window.parent.",
27232                                 "YAHOO.widget.Module.textResizeEvent.",
27233                                 "fire();};<",
27234                                 "\/script></head>",
27235                                 "<body></body></html>"].join('');
27236
27237                        oIFrame.src = "data:text/html;charset=utf-8," + encodeURIComponent(sHTML);
27238                    }
27239
27240                    oIFrame.id = "_yuiResizeMonitor";
27241                    oIFrame.title = "Text Resize Monitor";
27242                    oIFrame.tabIndex = -1;
27243                    oIFrame.setAttribute("role", "presentation");
27244
27245                    /*
27246                        Need to set "position" property before inserting the 
27247                        iframe into the document or Safari's status bar will 
27248                        forever indicate the iframe is loading 
27249                        (See YUILibrary bug #1723064)
27250                    */
27251                    oIFrame.style.position = "absolute";
27252                    oIFrame.style.visibility = "hidden";
27253
27254                    var db = document.body,
27255                        fc = db.firstChild;
27256                    if (fc) {
27257                        db.insertBefore(oIFrame, fc);
27258                    } else {
27259                        db.appendChild(oIFrame);
27260                    }
27261
27262                    // Setting the background color fixes an issue with IE6/IE7, where
27263                    // elements in the DOM, with -ve margin-top which positioned them 
27264                    // offscreen (so they would be overlapped by the iframe and its -ve top
27265                    // setting), would have their -ve margin-top ignored, when the iframe 
27266                    // was added.
27267                    oIFrame.style.backgroundColor = "transparent";
27268
27269                    oIFrame.style.borderWidth = "0";
27270                    oIFrame.style.width = "2em";
27271                    oIFrame.style.height = "2em";
27272                    oIFrame.style.left = "0";
27273                    oIFrame.style.top = (-1 * (oIFrame.offsetHeight + Module.RESIZE_MONITOR_BUFFER)) + "px";
27274                    oIFrame.style.visibility = "visible";
27275
27276                    /*
27277                       Don't open/close the document for Gecko like we used to, since it
27278                       leads to duplicate cookies. (See YUILibrary bug #1721755)
27279                    */
27280                    if (UA.webkit) {
27281                        oDoc = oIFrame.contentWindow.document;
27282                        oDoc.open();
27283                        oDoc.close();
27284                    }
27285                }
27286
27287                if (oIFrame && oIFrame.contentWindow) {
27288                    Module.textResizeEvent.subscribe(this.onDomResize, this, true);
27289
27290                    if (!Module.textResizeInitialized) {
27291                        if (supportsCWResize) {
27292                            if (!Event.on(oIFrame.contentWindow, "resize", fireTextResize)) {
27293                                /*
27294                                     This will fail in IE if document.domain has 
27295                                     changed, so we must change the listener to 
27296                                     use the oIFrame element instead
27297                                */
27298                                Event.on(oIFrame, "resize", fireTextResize);
27299                            }
27300                        }
27301                        Module.textResizeInitialized = true;
27302                    }
27303                    this.resizeMonitor = oIFrame;
27304                }
27305            }
27306        },
27307
27308        /**
27309         * Text resize monitor helper method.
27310         * Determines if the browser supports resize events on iframe content windows.
27311         * 
27312         * @private
27313         * @method _supportsCWResize
27314         */
27315        _supportsCWResize : function() {
27316            /*
27317                Gecko 1.8.0 (FF1.5), 1.8.1.0-5 (FF2) won't fire resize on contentWindow.
27318                Gecko 1.8.1.6+ (FF2.0.0.6+) and all other browsers will fire resize on contentWindow.
27319
27320                We don't want to start sniffing for patch versions, so fire textResize the same
27321                way on all FF2 flavors
27322             */
27323            var bSupported = true;
27324            if (UA.gecko && UA.gecko <= 1.8) {
27325                bSupported = false;
27326            }
27327            return bSupported;
27328        },
27329
27330        /**
27331        * Event handler fired when the resize monitor element is resized.
27332        * @method onDomResize
27333        * @param {DOMEvent} e The DOM resize event
27334        * @param {Object} obj The scope object passed to the handler
27335        */
27336        onDomResize: function (e, obj) {
27337
27338            var nTop = -1 * (this.resizeMonitor.offsetHeight + Module.RESIZE_MONITOR_BUFFER);
27339
27340            this.resizeMonitor.style.top = nTop + "px";
27341            this.resizeMonitor.style.left = "0";
27342        },
27343
27344        /**
27345        * Sets the Module's header content to the markup specified, or appends 
27346        * the passed element to the header. 
27347        * 
27348        * If no header is present, one will 
27349        * be automatically created. An empty string can be passed to the method
27350        * to clear the contents of the header.
27351        * 
27352        * @method setHeader
27353        * @param {HTML} headerContent The markup used to set the header content.
27354        * As a convenience, non HTMLElement objects can also be passed into 
27355        * the method, and will be treated as strings, with the header innerHTML
27356        * set to their default toString implementations. 
27357        * 
27358        * <p>NOTE: Markup passed into this method is added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.</p>
27359        * 
27360        * <em>OR</em>
27361        * @param {HTMLElement} headerContent The HTMLElement to append to 
27362        * <em>OR</em>
27363        * @param {DocumentFragment} headerContent The document fragment 
27364        * containing elements which are to be added to the header
27365        */
27366        setHeader: function (headerContent) {
27367            var oHeader = this.header || (this.header = createHeader());
27368
27369            if (headerContent.nodeName) {
27370                oHeader.innerHTML = "";
27371                oHeader.appendChild(headerContent);
27372            } else {
27373                oHeader.innerHTML = headerContent;
27374            }
27375
27376            if (this._rendered) {
27377                this._renderHeader();
27378            }
27379
27380            this.changeHeaderEvent.fire(headerContent);
27381            this.changeContentEvent.fire();
27382
27383        },
27384
27385        /**
27386        * Appends the passed element to the header. If no header is present, 
27387        * one will be automatically created.
27388        * @method appendToHeader
27389        * @param {HTMLElement | DocumentFragment} element The element to 
27390        * append to the header. In the case of a document fragment, the
27391        * children of the fragment will be appended to the header.
27392        */
27393        appendToHeader: function (element) {
27394            var oHeader = this.header || (this.header = createHeader());
27395
27396            oHeader.appendChild(element);
27397
27398            this.changeHeaderEvent.fire(element);
27399            this.changeContentEvent.fire();
27400
27401        },
27402
27403        /**
27404        * Sets the Module's body content to the HTML specified. 
27405        * 
27406        * If no body is present, one will be automatically created. 
27407        * 
27408        * An empty string can be passed to the method to clear the contents of the body.
27409        * @method setBody
27410        * @param {HTML} bodyContent The HTML used to set the body content 
27411        * As a convenience, non HTMLElement objects can also be passed into 
27412        * the method, and will be treated as strings, with the body innerHTML
27413        * set to their default toString implementations.
27414        * 
27415        * <p>NOTE: Markup passed into this method is added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.</p>
27416        * 
27417        * <em>OR</em>
27418        * @param {HTMLElement} bodyContent The HTMLElement to add as the first and only
27419        * child of the body element.
27420        * <em>OR</em>
27421        * @param {DocumentFragment} bodyContent The document fragment 
27422        * containing elements which are to be added to the body
27423        */
27424        setBody: function (bodyContent) {
27425            var oBody = this.body || (this.body = createBody());
27426
27427            if (bodyContent.nodeName) {
27428                oBody.innerHTML = "";
27429                oBody.appendChild(bodyContent);
27430            } else {
27431                oBody.innerHTML = bodyContent;
27432            }
27433
27434            if (this._rendered) {
27435                this._renderBody();
27436            }
27437
27438            this.changeBodyEvent.fire(bodyContent);
27439            this.changeContentEvent.fire();
27440        },
27441
27442        /**
27443        * Appends the passed element to the body. If no body is present, one 
27444        * will be automatically created.
27445        * @method appendToBody
27446        * @param {HTMLElement | DocumentFragment} element The element to 
27447        * append to the body. In the case of a document fragment, the
27448        * children of the fragment will be appended to the body.
27449        * 
27450        */
27451        appendToBody: function (element) {
27452            var oBody = this.body || (this.body = createBody());
27453        
27454            oBody.appendChild(element);
27455
27456            this.changeBodyEvent.fire(element);
27457            this.changeContentEvent.fire();
27458
27459        },
27460
27461        /**
27462        * Sets the Module's footer content to the HTML specified, or appends 
27463        * the passed element to the footer. If no footer is present, one will 
27464        * be automatically created. An empty string can be passed to the method
27465        * to clear the contents of the footer.
27466        * @method setFooter
27467        * @param {HTML} footerContent The HTML used to set the footer 
27468        * As a convenience, non HTMLElement objects can also be passed into 
27469        * the method, and will be treated as strings, with the footer innerHTML
27470        * set to their default toString implementations.
27471        * 
27472        * <p>NOTE: Markup passed into this method is added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.</p>
27473        * 
27474        * <em>OR</em>
27475        * @param {HTMLElement} footerContent The HTMLElement to append to 
27476        * the footer
27477        * <em>OR</em>
27478        * @param {DocumentFragment} footerContent The document fragment containing 
27479        * elements which are to be added to the footer
27480        */
27481        setFooter: function (footerContent) {
27482
27483            var oFooter = this.footer || (this.footer = createFooter());
27484
27485            if (footerContent.nodeName) {
27486                oFooter.innerHTML = "";
27487                oFooter.appendChild(footerContent);
27488            } else {
27489                oFooter.innerHTML = footerContent;
27490            }
27491
27492            if (this._rendered) {
27493                this._renderFooter();
27494            }
27495
27496            this.changeFooterEvent.fire(footerContent);
27497            this.changeContentEvent.fire();
27498        },
27499
27500        /**
27501        * Appends the passed element to the footer. If no footer is present, 
27502        * one will be automatically created.
27503        * @method appendToFooter
27504        * @param {HTMLElement | DocumentFragment} element The element to 
27505        * append to the footer. In the case of a document fragment, the
27506        * children of the fragment will be appended to the footer
27507        */
27508        appendToFooter: function (element) {
27509
27510            var oFooter = this.footer || (this.footer = createFooter());
27511
27512            oFooter.appendChild(element);
27513
27514            this.changeFooterEvent.fire(element);
27515            this.changeContentEvent.fire();
27516
27517        },
27518
27519        /**
27520        * Renders the Module by inserting the elements that are not already 
27521        * in the main Module into their correct places. Optionally appends 
27522        * the Module to the specified node prior to the render's execution. 
27523        * <p>
27524        * For Modules without existing markup, the appendToNode argument 
27525        * is REQUIRED. If this argument is ommitted and the current element is 
27526        * not present in the document, the function will return false, 
27527        * indicating that the render was a failure.
27528        * </p>
27529        * <p>
27530        * NOTE: As of 2.3.1, if the appendToNode is the document's body element
27531        * then the module is rendered as the first child of the body element, 
27532        * and not appended to it, to avoid Operation Aborted errors in IE when 
27533        * rendering the module before window's load event is fired. You can 
27534        * use the appendtodocumentbody configuration property to change this 
27535        * to append to document.body if required.
27536        * </p>
27537        * @method render
27538        * @param {String} appendToNode The element id to which the Module 
27539        * should be appended to prior to rendering <em>OR</em>
27540        * @param {HTMLElement} appendToNode The element to which the Module 
27541        * should be appended to prior to rendering
27542        * @param {HTMLElement} moduleElement OPTIONAL. The element that 
27543        * represents the actual Standard Module container.
27544        * @return {Boolean} Success or failure of the render
27545        */
27546        render: function (appendToNode, moduleElement) {
27547
27548            var me = this;
27549
27550            function appendTo(parentNode) {
27551                if (typeof parentNode == "string") {
27552                    parentNode = document.getElementById(parentNode);
27553                }
27554
27555                if (parentNode) {
27556                    me._addToParent(parentNode, me.element);
27557                    me.appendEvent.fire();
27558                }
27559            }
27560
27561            this.beforeRenderEvent.fire();
27562
27563            if (! moduleElement) {
27564                moduleElement = this.element;
27565            }
27566
27567            if (appendToNode) {
27568                appendTo(appendToNode);
27569            } else { 
27570                // No node was passed in. If the element is not already in the Dom, this fails
27571                if (! Dom.inDocument(this.element)) {
27572                    YAHOO.log("Render failed. Must specify appendTo node if " + " Module isn't already in the DOM.", "error");
27573                    return false;
27574                }
27575            }
27576
27577            this._renderHeader(moduleElement);
27578            this._renderBody(moduleElement);
27579            this._renderFooter(moduleElement);
27580
27581            this._rendered = true;
27582
27583            this.renderEvent.fire();
27584            return true;
27585        },
27586
27587        /**
27588         * Renders the currently set header into it's proper position under the 
27589         * module element. If the module element is not provided, "this.element" 
27590         * is used.
27591         * 
27592         * @method _renderHeader
27593         * @protected
27594         * @param {HTMLElement} moduleElement Optional. A reference to the module element
27595         */
27596        _renderHeader: function(moduleElement){
27597            moduleElement = moduleElement || this.element;
27598
27599            // Need to get everything into the DOM if it isn't already
27600            if (this.header && !Dom.inDocument(this.header)) {
27601                // There is a header, but it's not in the DOM yet. Need to add it.
27602                var firstChild = moduleElement.firstChild;
27603                if (firstChild) {
27604                    moduleElement.insertBefore(this.header, firstChild);
27605                } else {
27606                    moduleElement.appendChild(this.header);
27607                }
27608            }
27609        },
27610
27611        /**
27612         * Renders the currently set body into it's proper position under the 
27613         * module element. If the module element is not provided, "this.element" 
27614         * is used.
27615         * 
27616         * @method _renderBody
27617         * @protected
27618         * @param {HTMLElement} moduleElement Optional. A reference to the module element.
27619         */
27620        _renderBody: function(moduleElement){
27621            moduleElement = moduleElement || this.element;
27622
27623            if (this.body && !Dom.inDocument(this.body)) {
27624                // There is a body, but it's not in the DOM yet. Need to add it.
27625                if (this.footer && Dom.isAncestor(moduleElement, this.footer)) {
27626                    moduleElement.insertBefore(this.body, this.footer);
27627                } else {
27628                    moduleElement.appendChild(this.body);
27629                }
27630            }
27631        },
27632
27633        /**
27634         * Renders the currently set footer into it's proper position under the 
27635         * module element. If the module element is not provided, "this.element" 
27636         * is used.
27637         * 
27638         * @method _renderFooter
27639         * @protected
27640         * @param {HTMLElement} moduleElement Optional. A reference to the module element
27641         */
27642        _renderFooter: function(moduleElement){
27643            moduleElement = moduleElement || this.element;
27644
27645            if (this.footer && !Dom.inDocument(this.footer)) {
27646                // There is a footer, but it's not in the DOM yet. Need to add it.
27647                moduleElement.appendChild(this.footer);
27648            }
27649        },
27650
27651        /**
27652        * Removes the Module element from the DOM, sets all child elements to null, and purges the bounding element of event listeners.
27653        * @method destroy
27654        * @param {boolean} shallowPurge If true, only the parent element's DOM event listeners are purged. If false, or not provided, all children are also purged of DOM event listeners. 
27655        * NOTE: The flag is a "shallowPurge" flag, as opposed to what may be a more intuitive "purgeChildren" flag to maintain backwards compatibility with behavior prior to 2.9.0.
27656        */
27657        destroy: function (shallowPurge) {
27658
27659            var parent,
27660                purgeChildren = !(shallowPurge);
27661
27662            if (this.element) {
27663                Event.purgeElement(this.element, purgeChildren);
27664                parent = this.element.parentNode;
27665            }
27666
27667            if (parent) {
27668                parent.removeChild(this.element);
27669            }
27670        
27671            this.element = null;
27672            this.header = null;
27673            this.body = null;
27674            this.footer = null;
27675
27676            Module.textResizeEvent.unsubscribe(this.onDomResize, this);
27677
27678            this.cfg.destroy();
27679            this.cfg = null;
27680
27681            this.destroyEvent.fire();
27682        },
27683
27684        /**
27685        * Shows the Module element by setting the visible configuration 
27686        * property to true. Also fires two events: beforeShowEvent prior to 
27687        * the visibility change, and showEvent after.
27688        * @method show
27689        */
27690        show: function () {
27691            this.cfg.setProperty("visible", true);
27692        },
27693
27694        /**
27695        * Hides the Module element by setting the visible configuration 
27696        * property to false. Also fires two events: beforeHideEvent prior to 
27697        * the visibility change, and hideEvent after.
27698        * @method hide
27699        */
27700        hide: function () {
27701            this.cfg.setProperty("visible", false);
27702        },
27703        
27704        // BUILT-IN EVENT HANDLERS FOR MODULE //
27705        /**
27706        * Default event handler for changing the visibility property of a 
27707        * Module. By default, this is achieved by switching the "display" style 
27708        * between "block" and "none".
27709        * This method is responsible for firing showEvent and hideEvent.
27710        * @param {String} type The CustomEvent type (usually the property name)
27711        * @param {Object[]} args The CustomEvent arguments. For configuration 
27712        * handlers, args[0] will equal the newly applied value for the property.
27713        * @param {Object} obj The scope object. For configuration handlers, 
27714        * this will usually equal the owner.
27715        * @method configVisible
27716        */
27717        configVisible: function (type, args, obj) {
27718            var visible = args[0];
27719            if (visible) {
27720                if(this.beforeShowEvent.fire()) {
27721                    Dom.setStyle(this.element, "display", "block");
27722                    this.showEvent.fire();
27723                }
27724            } else {
27725                if (this.beforeHideEvent.fire()) {
27726                    Dom.setStyle(this.element, "display", "none");
27727                    this.hideEvent.fire();
27728                }
27729            }
27730        },
27731
27732        /**
27733        * Default event handler for the "effect" configuration property
27734        * @param {String} type The CustomEvent type (usually the property name)
27735        * @param {Object[]} args The CustomEvent arguments. For configuration 
27736        * handlers, args[0] will equal the newly applied value for the property.
27737        * @param {Object} obj The scope object. For configuration handlers, 
27738        * this will usually equal the owner.
27739        * @method configEffect
27740        */
27741        configEffect: function (type, args, obj) {
27742            this._cachedEffects = (this.cacheEffects) ? this._createEffe
27742cts(args[0]) : null;
27743        },
27744
27745        /**
27746         * If true, ContainerEffects (and Anim instances) are cached when "effect" is set, and reused. 
27747         * If false, new instances are created each time the container is hidden or shown, as was the 
27748         * behavior prior to 2.9.0. 
27749         *
27750         * @property cacheEffects
27751         * @since 2.9.0
27752         * @default true
27753         * @type boolean
27754         */
27755        cacheEffects : true,
27756
27757        /**
27758         * Creates an array of ContainerEffect instances from the provided configs
27759         * 
27760         * @method _createEffects
27761         * @param {Array|Object} effectCfg An effect configuration or array of effect configurations
27762         * @return {Array} An array of ContainerEffect instances.
27763         * @protected
27764         */
27765        _createEffects: function(effectCfg) {
27766            var effectInstances = null,
27767                n, 
27768                i,
27769                eff;
27770
27771            if (effectCfg) {
27772                if (effectCfg instanceof Array) {
27773                    effectInstances = [];
27774                    n = effectCfg.length;
27775                    for (i = 0; i < n; i++) {
27776                        eff = effectCfg[i];
27777                        if (eff.effect) {
27778                            effectInstances[effectInstances.length] = eff.effect(this, eff.duration);
27779                        }
27780                    }
27781                } else if (effectCfg.effect) {
27782                    effectInstances = [effectCfg.effect(this, effectCfg.duration)];
27783                }
27784            }
27785
27786            return effectInstances;
27787        },
27788
27789        /**
27790        * Default event handler for the "monitorresize" configuration property
27791        * @param {String} type The CustomEvent type (usually the property name)
27792        * @param {Object[]} args The CustomEvent arguments. For configuration 
27793        * handlers, args[0] will equal the newly applied value for the property.
27794        * @param {Object} obj The scope object. For configuration handlers, 
27795        * this will usually equal the owner.
27796        * @method configMonitorResize
27797        */
27798        configMonitorResize: function (type, args, obj) {
27799            var monitor = args[0];
27800            if (monitor) {
27801                this.initResizeMonitor();
27802            } else {
27803                Module.textResizeEvent.unsubscribe(this.onDomResize, this, true);
27804                this.resizeMonitor = null;
27805            }
27806        },
27807
27808        /**
27809         * This method is a protected helper, used when constructing the DOM structure for the module 
27810         * to account for situations which may cause Operation Aborted errors in IE. It should not 
27811         * be used for general DOM construction.
27812         * <p>
27813         * If the parentNode is not document.body, the element is appended as the last element.
27814         * </p>
27815         * <p>
27816         * If the parentNode is document.body the element is added as the first child to help
27817         * prevent Operation Aborted errors in IE.
27818         * </p>
27819         *
27820         * @param {parentNode} The HTML element to which the element will be added
27821         * @param {element} The HTML element to be added to parentNode's children
27822         * @method _addToParent
27823         * @protected
27824         */
27825        _addToParent: function(parentNode, element) {
27826            if (!this.cfg.getProperty("appendtodocumentbody") && parentNode === document.body && parentNode.firstChild) {
27827                parentNode.insertBefore(element, parentNode.firstChild);
27828            } else {
27829                parentNode.appendChild(element);
27830            }
27831        },
27832
27833        /**
27834        * Returns a String representation of the Object.
27835        * @method toString
27836        * @return {String} The string representation of the Module
27837        */
27838        toString: function () {
27839            return "Module " + this.id;
27840        }
27841    };
27842
27843    YAHOO.lang.augmentProto(Module, YAHOO.util.EventProvider);
27844
27845}());
27846(function () {
27847
27848    /**
27849    * Overlay is a Module that is absolutely positioned above the page flow. It 
27850    * has convenience methods for positioning and sizing, as well as options for 
27851    * controlling zIndex and constraining the Overlay's position to the current 
27852    * visible viewport. Overlay also contains a dynamicly generated IFRAME which 
27853    * is placed beneath it for Internet Explorer 6 and 5.x so that it will be 
27854    * properly rendered above SELECT elements.
27855    * @namespace YAHOO.widget
27856    * @class Overlay
27857    * @extends YAHOO.widget.Module
27858    * @param {String} el The element ID representing the Overlay <em>OR</em>
27859    * @param {HTMLElement} el The element representing the Overlay
27860    * @param {Object} userConfig The configuration object literal containing 
27861    * the configuration that should be set for this Overlay. See configuration 
27862    * documentation for more details.
27863    * @constructor
27864    */
27865    YAHOO.widget.Overlay = function (el, userConfig) {
27866        YAHOO.widget.Overlay.superclass.constructor.call(this, el, userConfig);
27867    };
27868
27869    var Lang = YAHOO.lang,
27870        CustomEvent = YAHOO.util.CustomEvent,
27871        Module = YAHOO.widget.Module,
27872        Event = YAHOO.util.Event,
27873        Dom = YAHOO.util.Dom,
27874        Config = YAHOO.util.Config,
27875        UA = YAHOO.env.ua,
27876        Overlay = YAHOO.widget.Overlay,
27877
27878        _SUBSCRIBE = "subscribe",
27879        _UNSUBSCRIBE = "unsubscribe",
27880        _CONTAINED = "contained",
27881
27882        m_oIFrameTemplate,
27883
27884        /**
27885        * Constant representing the name of the Overlay's events
27886        * @property EVENT_TYPES
27887        * @private
27888        * @final
27889        * @type Object
27890        */
27891        EVENT_TYPES = {
27892            "BEFORE_MOVE": "beforeMove",
27893            "MOVE": "move"
27894        },
27895
27896        /**
27897        * Constant representing the Overlay's configuration properties
27898        * @property DEFAULT_CONFIG
27899        * @private
27900        * @final
27901        * @type Object
27902        */
27903        DEFAULT_CONFIG = {
27904
27905            "X": { 
27906                key: "x", 
27907                validator: Lang.isNumber, 
27908                suppressEvent: true, 
27909                supercedes: ["iframe"]
27910            },
27911
27912            "Y": { 
27913                key: "y", 
27914                validator: Lang.isNumber, 
27915                suppressEvent: true, 
27916                supercedes: ["iframe"]
27917            },
27918
27919            "XY": { 
27920                key: "xy", 
27921                suppressEvent: true, 
27922                supercedes: ["iframe"] 
27923            },
27924
27925            "CONTEXT": { 
27926                key: "context", 
27927                suppressEvent: true, 
27928                supercedes: ["iframe"] 
27929            },
27930
27931            "FIXED_CENTER": { 
27932                key: "fixedcenter", 
27933                value: false, 
27934                supercedes: ["iframe", "visible"] 
27935            },
27936
27937            "WIDTH": { 
27938                key: "width",
27939                suppressEvent: true,
27940                supercedes: ["context", "fixedcenter", "iframe"]
27941            }, 
27942
27943            "HEIGHT": { 
27944                key: "height", 
27945                suppressEvent: true, 
27946                supercedes: ["context", "fixedcenter", "iframe"] 
27947            },
27948
27949            "AUTO_FILL_HEIGHT" : {
27950                key: "autofillheight",
27951                supercedes: ["height"],
27952                value:"body"
27953            },
27954
27955            "ZINDEX": { 
27956                key: "zindex", 
27957                value: null 
27958            },
27959
27960            "CONSTRAIN_TO_VIEWPORT": { 
27961                key: "constraintoviewport", 
27962                value: false, 
27963                validator: Lang.isBoolean, 
27964                supercedes: ["iframe", "x", "y", "xy"]
27965            }, 
27966
27967            "IFRAME": { 
27968                key: "iframe", 
27969                value: (UA.ie == 6 ? true : false), 
27970                validator: Lang.isBoolean, 
27971                supercedes: ["zindex"] 
27972            },
27973
27974            "PREVENT_CONTEXT_OVERLAP": {
27975                key: "preventcontextoverlap",
27976                value: false,
27977                validator: Lang.isBoolean,  
27978                supercedes: ["constraintoviewport"]
27979            }
27980
27981        };
27982
27983    /**
27984    * The URL that will be placed in the iframe
27985    * @property YAHOO.widget.Overlay.IFRAME_SRC
27986    * @static
27987    * @final
27988    * @type String
27989    */
27990    Overlay.IFRAME_SRC = "javascript:false;";
27991
27992    /**
27993    * Number representing how much the iframe shim should be offset from each 
27994    * side of an Overlay instance, in pixels.
27995    * @property YAHOO.widget.Overlay.IFRAME_SRC
27996    * @default 3
27997    * @static
27998    * @final
27999    * @type Number
28000    */
28001    Overlay.IFRAME_OFFSET = 3;
28002
28003    /**
28004    * Number representing the minimum distance an Overlay instance should be 
28005    * positioned relative to the boundaries of the browser's viewport, in pixels.
28006    * @property YAHOO.widget.Overlay.VIEWPORT_OFFSET
28007    * @default 10
28008    * @static
28009    * @final
28010    * @type Number
28011    */
28012    Overlay.VIEWPORT_OFFSET = 10;
28013
28014    /**
28015    * Constant representing the top left corner of an element, used for 
28016    * configuring the context element alignment
28017    * @property YAHOO.widget.Overlay.TOP_LEFT
28018    * @static
28019    * @final
28020    * @type String
28021    */
28022    Overlay.TOP_LEFT = "tl";
28023
28024    /**
28025    * Constant representing the top right corner of an element, used for 
28026    * configuring the context element alignment
28027    * @property YAHOO.widget.Overlay.TOP_RIGHT
28028    * @static
28029    * @final
28030    * @type String
28031    */
28032    Overlay.TOP_RIGHT = "tr";
28033
28034    /**
28035    * Constant representing the top bottom left corner of an element, used for 
28036    * configuring the context element alignment
28037    * @property YAHOO.widget.Overlay.BOTTOM_LEFT
28038    * @static
28039    * @final
28040    * @type String
28041    */
28042    Overlay.BOTTOM_LEFT = "bl";
28043
28044    /**
28045    * Constant representing the bottom right corner of an element, used for 
28046    * configuring the context element alignment
28047    * @property YAHOO.widget.Overlay.BOTTOM_RIGHT
28048    * @static
28049    * @final
28050    * @type String
28051    */
28052    Overlay.BOTTOM_RIGHT = "br";
28053
28054    Overlay.PREVENT_OVERLAP_X = {
28055        "tltr": true,
28056        "blbr": true,
28057        "brbl": true,
28058        "trtl": true
28059    };
28060            
28061    Overlay.PREVENT_OVERLAP_Y = {
28062        "trbr": true,
28063        "tlbl": true,
28064        "bltl": true,
28065        "brtr": true
28066    };
28067
28068    /**
28069    * Constant representing the default CSS class used for an Overlay
28070    * @property YAHOO.widget.Overlay.CSS_OVERLAY
28071    * @static
28072    * @final
28073    * @type Str
28073ing
28074    */
28075    Overlay.CSS_OVERLAY = "yui-overlay";
28076
28077    /**
28078    * Constant representing the default hidden CSS class used for an Overlay. This class is 
28079    * applied to the overlay's outer DIV whenever it's hidden.
28080    *
28081    * @property YAHOO.widget.Overlay.CSS_HIDDEN
28082    * @static
28083    * @final
28084    * @type String
28085    */
28086    Overlay.CSS_HIDDEN = "yui-overlay-hidden";
28087
28088    /**
28089    * Constant representing the default CSS class used for an Overlay iframe shim.
28090    * 
28091    * @property YAHOO.widget.Overlay.CSS_IFRAME
28092    * @static
28093    * @final
28094    * @type String
28095    */
28096    Overlay.CSS_IFRAME = "yui-overlay-iframe";
28097
28098    /**
28099     * Constant representing the names of the standard module elements
28100     * used in the overlay.
28101     * @property YAHOO.widget.Overlay.STD_MOD_RE
28102     * @static
28103     * @final
28104     * @type RegExp
28105     */
28106    Overlay.STD_MOD_RE = /^\s*?(body|footer|header)\s*?$/i;
28107
28108    /**
28109    * A singleton CustomEvent used for reacting to the DOM event for 
28110    * window scroll
28111    * @event YAHOO.widget.Overlay.windowScrollEvent
28112    */
28113    Overlay.windowScrollEvent = new CustomEvent("windowScroll");
28114
28115    /**
28116    * A singleton CustomEvent used for reacting to the DOM event for
28117    * window resize
28118    * @event YAHOO.widget.Overlay.windowResizeEvent
28119    */
28120    Overlay.windowResizeEvent = new CustomEvent("windowResize");
28121
28122    /**
28123    * The DOM event handler used to fire the CustomEvent for window scroll
28124    * @method YAHOO.widget.Overlay.windowScrollHandler
28125    * @static
28126    * @param {DOMEvent} e The DOM scroll event
28127    */
28128    Overlay.windowScrollHandler = function (e) {
28129        var t = Event.getTarget(e);
28130
28131        // - Webkit (Safari 2/3) and Opera 9.2x bubble scroll events from elements to window
28132        // - FF2/3 and IE6/7, Opera 9.5x don't bubble scroll events from elements to window
28133        // - IE doesn't recognize scroll registered on the document.
28134        //
28135        // Also, when document view is scrolled, IE doesn't provide a target, 
28136        // rest of the browsers set target to window.document, apart from opera 
28137        // which sets target to window.
28138        if (!t || t === window || t === window.document) {
28139            if (UA.ie) {
28140
28141                if (! window.scrollEnd) {
28142                    window.scrollEnd = -1;
28143                }
28144
28145                clearTimeout(window.scrollEnd);
28146        
28147                window.scrollEnd = setTimeout(function () { 
28148                    Overlay.windowScrollEvent.fire(); 
28149                }, 1);
28150        
28151            } else {
28152                Overlay.windowScrollEvent.fire();
28153            }
28154        }
28155    };
28156
28157    /**
28158    * The DOM event handler used to fire the CustomEvent for window resize
28159    * @method YAHOO.widget.Overlay.windowResizeHandler
28160    * @static
28161    * @param {DOMEvent} e The DOM resize event
28162    */
28163    Overlay.windowResizeHandler = function (e) {
28164
28165        if (UA.ie) {
28166            if (! window.resizeEnd) {
28167                window.resizeEnd = -1;
28168            }
28169
28170            clearTimeout(window.resizeEnd);
28171
28172            window.resizeEnd = setTimeout(function () {
28173                Overlay.windowResizeEvent.fire(); 
28174            }, 100);
28175        } else {
28176            Overlay.windowResizeEvent.fire();
28177        }
28178    };
28179
28180    /**
28181    * A boolean that indicated whether the window resize and scroll events have 
28182    * already been subscribed to.
28183    * @property YAHOO.widget.Overlay._initialized
28184    * @private
28185    * @type Boolean
28186    */
28187    Overlay._initialized = null;
28188
28189    if (Overlay._initialized === null) {
28190        Event.on(window, "scroll", Overlay.windowScrollHandler);
28191        Event.on(window, "resize", Overlay.windowResizeHandler);
28192        Overlay._initialized = true;
28193    }
28194
28195    /**
28196     * Internal map of special event types, which are provided
28197     * by the instance. It maps the event type to the custom event 
28198     * instance. Contains entries for the "windowScroll", "windowResize" and
28199     * "textResize" static container events.
28200     *
28201     * @property YAHOO.widget.Overlay._TRIGGER_MAP
28202     * @type Object
28203     * @static
28204     * @private
28205     */
28206    Overlay._TRIGGER_MAP = {
28207        "windowScroll" : Overlay.windowScrollEvent,
28208        "windowResize" : Overlay.windowResizeEvent,
28209        "textResize"   : Module.textResizeEvent
28210    };
28211
28212    YAHOO.extend(Overlay, Module, {
28213
28214        /**
28215         * <p>
28216         * Array of default event types which will trigger
28217         * context alignment for the Overlay class.
28218         * </p>
28219         * <p>The array is empty by default for Overlay,
28220         * but maybe populated in future releases, so classes extending
28221         * Overlay which need to define their own set of CONTEXT_TRIGGERS
28222         * should concatenate their super class's prototype.CONTEXT_TRIGGERS 
28223         * value with their own array of values.
28224         * </p>
28225         * <p>
28226         * E.g.:
28227         * <code>CustomOverlay.prototype.CONTEXT_TRIGGERS = YAHOO.widget.Overlay.prototype.CONTEXT_TRIGGERS.concat(["windowScroll"]);</code>
28228         * </p>
28229         * 
28230         * @property CONTEXT_TRIGGERS
28231         * @type Array
28232         * @final
28233         */
28234        CONTEXT_TRIGGERS : [],
28235
28236        /**
28237        * The Overlay initialization method, which is executed for Overlay and  
28238        * all of its subclasses. This method is automatically called by the 
28239        * constructor, and  sets up all DOM references for pre-existing markup, 
28240        * and creates required markup if it is not already present.
28241        * @method init
28242        * @param {String} el The element ID representing the Overlay <em>OR</em>
28243        * @param {HTMLElement} el The element representing the Overlay
28244        * @param {Object} userConfig The configuration object literal 
28245        * containing the configuration that should be set for this Overlay. 
28246        * See configuration documentation for more details.
28247        */
28248        init: function (el, userConfig) {
28249
28250            /*
28251                 Note that we don't pass the user config in here yet because we
28252                 only want it executed once, at the lowest subclass level
28253            */
28254
28255            Overlay.superclass.init.call(this, el/*, userConfig*/);
28256
28257            this.beforeInitEvent.fire(Overlay);
28258
28259            Dom.addClass(this.element, Overlay.CSS_OVERLAY);
28260
28261            if (userConfig) {
28262                this.cfg.applyConfig(userConfig, true);
28263            }
28264
28265            if (this.platform == "mac" && UA.gecko) {
28266
28267                if (! Config.alreadySubscribed(this.showEvent,
28268                    this.showMacGeckoScrollbars, this)) {
28269
28270                    this.showEvent.subscribe(this.showMacGeckoScrollbars, 
28271                        this, true);
28272
28273                }
28274
28275                if (! Config.alreadySubscribed(this.hideEvent, 
28276                    this.hideMacGeckoScrollbars, this)) {
28277
28278                    this.hideEvent.subscribe(this.hideMacGeckoScrollbars, 
28279                        this, true);
28280
28281                }
28282            }
28283
28284            this.initEvent.fire(Overlay);
28285        },
28286        
28287        /**
28288        * Initializes the custom events for Overlay which are fired  
28289        * automatically at appropriate times by the Overlay class.
28290        * @method initEvents
28291        */
28292        initEvents: function () {
28293
28294            Overlay.superclass.initEvents.call(this);
28295
28296            var SIGNATURE = CustomEvent.LIST;
28297
28298            /**
28299            * CustomEvent fired before the Overlay is moved.
28300            * @event beforeMoveEvent
28301            * @param {Number} x x coordinate
28302            * @param {Number} y y coordinate
28303            */
28304            this.beforeMoveEvent = this.createEvent(EVENT_TYPES.BEFORE_MOVE);
28305            this.beforeMoveEvent.signature = SIGNATURE;
28306
28307            /**
28308            * CustomEvent fired after the Overlay is moved.
28309            * @event moveEvent
28310            * @param {Number} x x coordinate
28311            * @param {Number} y y coordinate
28312            */
28313            this.moveEvent = this.createEvent(EVENT_TYPES.MOVE);
28314            this.moveEvent.signature = SIGNATURE;
28315
28316        },
28317        
28318        /**
28319        * Initializes the class's configurable properties which can be changed 
28320        * using the Overlay's Config object (cfg).
28321        * @method initDefaultConfig
28322        */
28323        initDefaultConfig: function () {
28324    
28325            Overlay.superclass.initDefaultConfig.call(this);
28326
28327            var cfg = this.cfg;
28328
28329            // Add overlay config properties //
28330            
28331            /**
28332            * The absolute x-coordinate position of the Overlay
28333            * @config x
28334            * @type Number
28335            * @default null
28336            */
28337            cfg.addProperty(DEFAULT_CONFIG.X.key, { 
28338    
28339                handler: this.configX, 
28340                validator: DEFAULT_CONFIG.X.validator, 
28341                suppressEvent: DEFAULT_CONFIG.X.suppressEvent, 
28342                supercedes: DEFAULT_CONFIG.X.supercedes
28343    
28344            });
28345
28346            /**
28347            * The absolute y-coordinate position of the Overlay
28348            * @config y
28349            * @type Number
28350            * @default null
28351            */
28352            cfg.addProperty(DEFAULT_CONFIG.Y.key, {
28353
28354                handler: this.configY, 
28355                validator: DEFAULT_CONFIG.Y.validator, 
28356                suppressEvent: DEFAULT_CONFIG.Y.suppressEvent, 
28357                supercedes: DEFAULT_CONFIG.Y.supercedes
28358
28359            });
28360
28361            /**
28362            * An array with the absolute x and y positions of the Overlay
28363            * @config xy
28364            * @type Number[]
28365            * @default null
28366            */
28367            cfg.addProperty(DEFAULT_CONFIG.XY.key, {
28368                handler: this.configXY, 
28369                suppressEvent: DEFAULT_CONFIG.XY.suppressEvent, 
28370                supercedes: DEFAULT_CONFIG.XY.supercedes
28371            });
28372
28373            /**
28374            * <p>
28375            * The array of context arguments for context-sensitive positioning. 
28376            * </p>
28377            *
28378            * <p>
28379            * The format of the array is: <code>[contextElementOrId, overlayCorner, contextCorner, arrayOfTriggerEvents (optional), xyOffset 
28379(optional)]</code>, the
28380            * the 5 array elements described in detail below:
28381            * </p>
28382            *
28383            * <dl>
28384            * <dt>contextElementOrId &#60;String|HTMLElement&#62;</dt>
28385            * <dd>A reference to the context element to which the overlay should be aligned (or it's id).</dd>
28386            * <dt>overlayCorner &#60;String&#62;</dt>
28387            * <dd>The corner of the overlay which is to be used for alignment. This corner will be aligned to the 
28388            * corner of the context element defined by the "contextCorner" entry which follows. Supported string values are: 
28389            * "tr" (top right), "tl" (top left), "br" (bottom right), or "bl" (bottom left).</dd>
28390            * <dt>contextCorner &#60;String&#62;</dt>
28391            * <dd>The corner of the context element which is to be used for alignment. Supported string values are the same ones listed for the "overlayCorner" entry above.</dd>
28392            * <dt>arrayOfTriggerEvents (optional) &#60;Array[String|CustomEvent]&#62;</dt>
28393            * <dd>
28394            * <p>
28395            * By default, context alignment is a one time operation, aligning the Overlay to the context element when context configuration property is set, or when the <a href="#method_align">align</a> 
28396            * method is invoked. However, you can use the optional "arrayOfTriggerEvents" entry to define the list of events which should force the overlay to re-align itself with the context element. 
28397            * This is useful in situations where the layout of the document may change, resulting in the context element's position being modified.
28398            * </p>
28399            * <p>
28400            * The array can contain either event type strings for events the instance publishes (e.g. "beforeShow") or CustomEvent instances. Additionally the following
28401            * 3 static container event types are also currently supported : <code>"windowResize", "windowScroll", "textResize"</code> (defined in <a href="#property__TRIGGER_MAP">_TRIGGER_MAP</a> private property).
28402            * </p>
28403            * </dd>
28404            * <dt>xyOffset &#60;Number[]&#62;</dt>
28405            * <dd>
28406            * A 2 element Array specifying the X and Y pixel amounts by which the Overlay should be offset from the aligned corner. e.g. [5,0] offsets the Overlay 5 pixels to the left, <em>after</em> aligning the given context corners.
28407            * NOTE: If using this property and no triggers need to be defined, the arrayOfTriggerEvents property should be set to null to maintain correct array positions for the arguments. 
28408            * </dd>
28409            * </dl>
28410            *
28411            * <p>
28412            * For example, setting this property to <code>["img1", "tl", "bl"]</code> will 
28413            * align the Overlay's top left corner to the bottom left corner of the
28414            * context element with id "img1".
28415            * </p>
28416            * <p>
28417            * Setting this property to <code>["img1", "tl", "bl", null, [0,5]</code> will 
28418            * align the Overlay's top left corner to the bottom left corner of the
28419            * context element with id "img1", and then offset it by 5 pixels on the Y axis (providing a 5 pixel gap between the bottom of the context element and top of the overlay).
28420            * </p>
28421            * <p>
28422            * Adding the optional trigger values: <code>["img1", "tl", "bl", ["beforeShow", "windowResize"], [0,5]]</code>,
28423            * will re-align the overlay position, whenever the "beforeShow" or "windowResize" events are fired.
28424            * </p>
28425            *
28426            * @config context
28427            * @type Array
28428            * @default null
28429            */
28430            cfg.addProperty(DEFAULT_CONFIG.CONTEXT.key, {
28431                handler: this.configContext, 
28432                suppressEvent: DEFAULT_CONFIG.CONTEXT.suppressEvent, 
28433                supercedes: DEFAULT_CONFIG.CONTEXT.supercedes
28434            });
28435
28436            /**
28437            * Determines whether or not the Overlay should be anchored 
28438            * to the center of the viewport.
28439            * 
28440            * <p>This property can be set to:</p>
28441            * 
28442            * <dl>
28443            * <dt>true</dt>
28444            * <dd>
28445            * To enable fixed center positioning
28446            * <p>
28447            * When enabled, the overlay will 
28448            * be positioned in the center of viewport when initially displayed, and 
28449            * will remain in the center of the viewport whenever the window is 
28450            * scrolled or resized.
28451            * </p>
28452            * <p>
28453            * If the overlay is too big for the viewport, 
28454            * it's top left corner will be aligned with the top left corner of the viewport.
28455            * </p>
28456            * </dd>
28457            * <dt>false</dt>
28458            * <dd>
28459            * To disable fixed center positioning.
28460            * <p>In this case the overlay can still be 
28461            * centered as a one-off operation, by invoking the <code>center()</code> method,
28462            * however it will not remain centered when the window is scrolled/resized.
28463            * </dd>
28464            * <dt>"contained"<dt>
28465            * <dd>To enable fixed center positioning, as with the <code>true</code> option.
28466            * <p>However, unlike setting the property to <code>true</code>, 
28467            * when the property is set to <code>"contained"</code>, if the overlay is 
28468            * too big for the viewport, it will not get automatically centered when the 
28469            * user scrolls or resizes the window (until the window is large enough to contain the 
28470            * overlay). This is useful in cases where the Overlay has both header and footer 
28471            * UI controls which the user may need to access.
28472            * </p>
28473            * </dd>
28474            * </dl>
28475            *
28476            * @config fixedcenter
28477            * @type Boolean | String
28478            * @default false
28479            */
28480            cfg.addProperty(DEFAULT_CONFIG.FIXED_CENTER.key, {
28481                handler: this.configFixedCenter,
28482                value: DEFAULT_CONFIG.FIXED_CENTER.value, 
28483                validator: DEFAULT_CONFIG.FIXED_CENTER.validator, 
28484                supercedes: DEFAULT_CONFIG.FIXED_CENTER.supercedes
28485            });
28486    
28487            /**
28488            * CSS width of the Overlay.
28489            * @config width
28490            * @type String
28491            * @default null
28492            */
28493            cfg.addProperty(DEFAULT_CONFIG.WIDTH.key, {
28494                handler: this.configWidth, 
28495                suppressEvent: DEFAULT_CONFIG.WIDTH.suppressEvent, 
28496                supercedes: DEFAULT_CONFIG.WIDTH.supercedes
28497            });
28498
28499            /**
28500            * CSS height of the Overlay.
28501            * @config height
28502            * @type String
28503            * @default null
28504            */
28505            cfg.addProperty(DEFAULT_CONFIG.HEIGHT.key, {
28506                handler: this.configHeight, 
28507                suppressEvent: DEFAULT_CONFIG.HEIGHT.suppressEvent, 
28508                supercedes: DEFAULT_CONFIG.HEIGHT.supercedes
28509            });
28510
28511            /**
28512            * Standard module element which should auto fill out the height of the Overlay if the height config property is set.
28513            * Supported values are "header", "body", "footer".
28514            *
28515            * @config autofillheight
28516            * @type String
28517            * @default null
28518            */
28519            cfg.addProperty(DEFAULT_CONFIG.AUTO_FILL_HEIGHT.key, {
28520                handler: this.configAutoFillHeight, 
28521                value : DEFAULT_CONFIG.AUTO_FILL_HEIGHT.value,
28522                validator : this._validateAutoFill,
28523                supercedes: DEFAULT_CONFIG.AUTO_FILL_HEIGHT.supercedes
28524            });
28525
28526            /**
28527            * CSS z-index of the Overlay.
28528            * @config zIndex
28529            * @type Number
28530            * @default null
28531            */
28532            cfg.addProperty(DEFAULT_CONFIG.ZINDEX.key, {
28533                handler: this.configzIndex,
28534                value: DEFAULT_CONFIG.ZINDEX.value
28535            });
28536
28537            /**
28538            * True if the Overlay should be prevented from being positioned 
28539            * out of the viewport.
28540            * @config constraintoviewport
28541            * @type Boolean
28542            * @default false
28543            */
28544            cfg.addProperty(DEFAULT_CONFIG.CONSTRAIN_TO_VIEWPORT.key, {
28545
28546                handler: this.configConstrainToViewport, 
28547                value: DEFAULT_CONFIG.CONSTRAIN_TO_VIEWPORT.value, 
28548                validator: DEFAULT_CONFIG.CONSTRAIN_TO_VIEWPORT.validator, 
28549                supercedes: DEFAULT_CONFIG.CONSTRAIN_TO_VIEWPORT.supercedes
28550
28551            });
28552
28553            /**
28554            * @config iframe
28555            * @description Boolean indicating whether or not the Overlay should 
28556            * have an IFRAME shim; used to prevent SELECT elements from 
28557            * poking through an Overlay instance in IE6.  When set to "true", 
28558            * the iframe shim is created when the Overlay instance is intially
28559            * made visible.
28560            * @type Boolean
28561            * @default true for IE6 and below, false for all other browsers.
28562            */
28563            cfg.addProperty(DEFAULT_CONFIG.IFRAME.key, {
28564
28565                handler: this.configIframe, 
28566                value: DEFAULT_CONFIG.IFRAME.value, 
28567                validator: DEFAULT_CONFIG.IFRAME.validator, 
28568                supercedes: DEFAULT_CONFIG.IFRAME.supercedes
28569
28570            });
28571
28572            /**
28573            * @config preventcontextoverlap
28574            * @description Boolean indicating whether or not the Overlay should overlap its 
28575            * context element (defined using the "context" configuration property) when the 
28576            * "constraintoviewport" configuration property is set to "true".
28577            * @type Boolean
28578            * @default false
28579            */
28580            cfg.addProperty(DEFAULT_CONFIG.PREVENT_CONTEXT_OVERLAP.key, {
28581                value: DEFAULT_CONFIG.PREVENT_CONTEXT_OVERLAP.value, 
28582                validator: DEFAULT_CONFIG.PREVENT_CONTEXT_OVERLAP.validator, 
28583                supercedes: DEFAULT_CONFIG.PREVENT_CONTEXT_OVERLAP.supercedes
28584            });
28585        },
28586
28587        /**
28588        * Moves the Overlay to the specified position. This function is  
28589        * identical to calling this.cfg.setProperty("xy", [x,y]);
28590        * @method moveTo
28591        * @param {Number} x The Overlay's new x position
28592        * @param {Number} y The Overlay's new y position
28593        */
28594        moveTo: function (x, y) {
28595            this.cfg.setProperty("xy", [x, y]);
28596        },
28597
28598        /**
28599        * Adds a CSS class ("hide-scrollbars") and removes a CSS class 
28600        * ("show-scrollbars") to the Overlay to fix a bug in Gecko on Mac OS X 
28601        * (https://bugzilla.mozilla.org/show_bug.cgi?id=187435)
28602        * @method hideMacGeckoScrollbars
28603        */
28604        hideMacGeckoScrollbars: function () {
28605            Dom.replaceClass(this.element, "show-scrollbars", "hide-scrollbars");
28606        },
28607
28608        /**
28609        * Adds a CSS class ("show-scrollbars") and removes a CSS class 
28610        * ("hide-scrollbars") to the Overlay to fix a bug in Gecko on Mac OS X 
28611        * (https://bugzilla.mozilla.org/show_bug.cgi?id=187435)
28612        * @method showMacGeckoScrollbars
28613        */
28614        showMacGeckoScrollbars: function () {
28615            Dom.replaceClass(this.element, "hide-scrollbars", "show-scrollbars");
28616        },
28617
28618        /**
28619         * Internal implementation to set the visibility of the overlay in the DOM.
28620         *
28621         * @method _setDomVisibility
28622         * @param {boolean} visible Whether to show or hide the Overlay's outer element
28623         * @protected
28624         */
28625        _setDomVisibility : function(show) {
28626            Dom.setStyle(this.element, "visibility", (show) ? "visible" : "hidden");
28627            var hiddenClass = Overlay.CSS_HIDDEN;
28628
28629            if (show) {
28630                Dom.removeClass(this.element, hiddenClass);
28631            } else {
28632                Dom.addClass(this.element, hiddenClass);
28633            }
28634        },
28635
28636        // BEGIN BUILT-IN PROPERTY EVENT HANDLERS //
28637        /**
28638        * The default event handler fired when the "visible" property is 
28639        * changed.  This method is responsible for firing showEvent
28640        * and hideEvent.
28641        * @method configVisible
28642        * @param {String} type The CustomEvent type (usually the property name)
28643        * @param {Object[]} args The CustomEvent arguments. For configuration
28644        * handlers, args[0] will equal the newly applied value for the property.
28645        * @param {Object} obj The scope object. For configuration handlers, 
28646        * this will usually equal the owner.
28647        */
28648        configVisible: function (type, args, obj) {
28649
28650            var visible = args[0],
28651                currentVis = Dom.getStyle(this.element, "visibility"),
28652                effects = this._cachedEffects || this._createEffe
28652cts(this.cfg.getProperty("effect")),
28653                isMacGecko = (this.platform == "mac" && UA.gecko),
28654                alreadySubscribed = Config.alreadySubscribed,
28655                ei, e, j, k, h,
28656                nEffectInstances;
28657
28658            if (currentVis == "inherit") {
28659                e = this.element.parentNode;
28660
28661                while (e.nodeType != 9 && e.nodeType != 11) {
28662                    currentVis = Dom.getStyle(e, "visibility");
28663
28664                    if (currentVis != "inherit") {
28665                        break;
28666                    }
28667
28668                    e = e.parentNode;
28669                }
28670
28671                if (currentVis == "inherit") {
28672                    currentVis = "visible";
28673                }
28674            }
28675
28676            if (visible) { // Show
28677
28678                if (isMacGecko) {
28679                    this.showMacGeckoScrollbars();
28680                }
28681
28682                if (effects) { // Animate in
28683                    if (visible) { // Animate in if not showing
28684
28685                         // Fading out is a bit of a hack, but didn't want to risk doing 
28686                         // something broader (e.g a generic this._animatingOut) for 2.9.0
28687
28688                        if (currentVis != "visible" || currentVis === "" || this._fadingOut) {
28689                            if (this.beforeShowEvent.fire()) {
28690
28691                                nEffectInstances = effects.length;
28692
28693                                for (j = 0; j < nEffectInstances; j++) {
28694                                    ei = effects[j];
28695                                    if (j === 0 && !alreadySubscribed(ei.animateInCompleteEvent, this.showEvent.fire, this.showEvent)) {
28696                                        ei.animateInCompleteEvent.subscribe(this.showEvent.fire, this.showEvent, true);
28697                                    }
28698                                    ei.animateIn();
28699                                }
28700                            }
28701                        }
28702                    }
28703                } else { // Show
28704                    if (currentVis != "visible" || currentVis === "") {
28705                        if (this.beforeShowEvent.fire()) {
28706                            this._setDomVisibility(true);
28707                            this.cfg.refireEvent("iframe");
28708                            this.showEvent.fire();
28709                        }
28710                    } else {
28711                        this._setDomVisibility(true);
28712                    }
28713                }
28714            } else { // Hide
28715
28716                if (isMacGecko) {
28717                    this.hideMacGeckoScrollbars();
28718                }
28719
28720                if (effects) { // Animate out if showing
28721                    if (currentVis == "visible" || this._fadingIn) {
28722                        if (this.beforeHideEvent.fire()) {
28723                            nEffectInstances = effects.length;
28724                            for (k = 0; k < nEffectInstances; k++) {
28725                                h = effects[k];
28726        
28727                                if (k === 0 && !alreadySubscribed(h.animateOutCompleteEvent, this.hideEvent.fire, this.hideEvent)) {
28728                                    h.animateOutCompleteEvent.subscribe(this.hideEvent.fire, this.hideEvent, true);
28729                                }
28730                                h.animateOut();
28731                            }
28732                        }
28733
28734                    } else if (currentVis === "") {
28735                        this._setDomVisibility(false);
28736                    }
28737
28738                } else { // Simple hide
28739
28740                    if (currentVis == "visible" || currentVis === "") {
28741                        if (this.beforeHideEvent.fire()) {
28742                            this._setDomVisibility(false);
28743                            this.hideEvent.fire();
28744                        }
28745                    } else {
28746                        this._setDomVisibility(false);
28747                    }
28748                }
28749            }
28750        },
28751
28752        /**
28753        * Fixed center event handler used for centering on scroll/resize, but only if 
28754        * the overlay is visible and, if "fixedcenter" is set to "contained", only if 
28755        * the overlay fits within the viewport.
28756        *
28757        * @method doCenterOnDOMEvent
28758        */
28759        doCenterOnDOMEvent: function () {
28760            var cfg = this.cfg,
28761                fc = cfg.getProperty("fixedcenter");
28762
28763            if (cfg.getProperty("visible")) {
28764                if (fc && (fc !== _CONTAINED || this.fitsInViewport())) {
28765                    this.center();
28766                }
28767            }
28768        },
28769
28770        /**
28771         * Determines if the Overlay (including the offset value defined by Overlay.VIEWPORT_OFFSET) 
28772         * will fit entirely inside the viewport, in both dimensions - width and height.
28773         * 
28774         * @method fitsInViewport
28775         * @return boolean true if the Overlay will fit, false if not
28776         */
28777        fitsInViewport : function() {
28778            var nViewportOffset = Overlay.VIEWPORT_OFFSET,
28779                element = this.element,
28780                elementWidth = element.offsetWidth,
28781                elementHeight = element.offsetHeight,
28782                viewportWidth = Dom.getViewportWidth(),
28783                viewportHeight = Dom.getViewportHeight();
28784
28785            return ((elementWidth + nViewportOffset < viewportWidth) && (elementHeight + nViewportOffset < viewportHeight));
28786        },
28787
28788        /**
28789        * The default event handler fired when the "fixedcenter" property 
28790        * is changed.
28791        * @method configFixedCenter
28792        * @param {String} type The CustomEvent type (usually the property name)
28793        * @param {Object[]} args The CustomEvent arguments. For configuration 
28794        * handlers, args[0] will equal the newly applied value for the property.
28795        * @param {Object} obj The scope object. For configuration handlers, 
28796        * this will usually equal the owner.
28797        */
28798        configFixedCenter: function (type, args, obj) {
28799
28800            var val = args[0],
28801                alreadySubscribed = Config.alreadySubscribed,
28802                windowResizeEvent = Overlay.windowResizeEvent,
28803                windowScrollEvent = Overlay.windowScrollEvent;
28804
28805            if (val) {
28806                this.center();
28807
28808                if (!alreadySubscribed(this.beforeShowEvent, this.center)) {
28809                    this.beforeShowEvent.subscribe(this.center);
28810                }
28811
28812                if (!alreadySubscribed(windowResizeEvent, this.doCenterOnDOMEvent, this)) {
28813                    windowResizeEvent.subscribe(this.doCenterOnDOMEvent, this, true);
28814                }
28815
28816                if (!alreadySubscribed(windowScrollEvent, this.doCenterOnDOMEvent, this)) {
28817                    windowScrollEvent.subscribe(this.doCenterOnDOMEvent, this, true);
28818                }
28819
28820            } else {
28821                this.beforeShowEvent.unsubscribe(this.center);
28822
28823                windowResizeEvent.unsubscribe(this.doCenterOnDOMEvent, this);
28824                windowScrollEvent.unsubscribe(this.doCenterOnDOMEvent, this);
28825            }
28826        },
28827
28828        /**
28829        * The default event handler fired when the "height" property is changed.
28830        * @method configHeight
28831        * @param {String} type The CustomEvent type (usually the property name)
28832        * @param {Object[]} args The CustomEvent arguments. For configuration 
28833        * handlers, args[0] will equal the newly applied value for the property.
28834        * @param {Object} obj The scope object. For configuration handlers, 
28835        * this will usually equal the owner.
28836        */
28837        configHeight: function (type, args, obj) {
28838
28839            var height = args[0],
28840                el = this.element;
28841
28842            Dom.setStyle(el, "height", height);
28843            this.cfg.refireEvent("iframe");
28844        },
28845
28846        /**
28847         * The default event handler fired when the "autofillheight" property is changed.
28848         * @method configAutoFillHeight
28849         *
28850         * @param {String} type The CustomEvent type (usually the property name)
28851         * @param {Object[]} args The CustomEvent arguments. For configuration 
28852         * handlers, args[0] will equal the newly applied value for the property.
28853         * @param {Object} obj The scope object. For configuration handlers, 
28854         * this will usually equal the owner.
28855         */
28856        configAutoFillHeight: function (type, args, obj) {
28857            var fillEl = args[0],
28858                cfg = this.cfg,
28859                autoFillHeight = "autofillheight",
28860                height = "height",
28861                currEl = cfg.getProperty(autoFillHeight),
28862                autoFill = this._autoFillOnHeightChange;
28863
28864            cfg.unsubscribeFromConfigEvent(height, autoFill);
28865            Module.textResizeEvent.unsubscribe(autoFill);
28866            this.changeContentEvent.unsubscribe(autoFill);
28867
28868            if (currEl && fillEl !== currEl && this[currEl]) {
28869                Dom.setStyle(this[currEl], height, "");
28870            }
28871
28872            if (fillEl) {
28873                fillEl = Lang.trim(fillEl.toLowerCase());
28874
28875                cfg.subscribeToConfigEvent(height, autoFill, this[fillEl], this);
28876                Module.textResizeEvent.subscribe(autoFill, this[fillEl], this);
28877                this.changeContentEvent.subscribe(autoFill, this[fillEl], this);
28878
28879                cfg.setProperty(autoFillHeight, fillEl, true);
28880            }
28881        },
28882
28883        /**
28884        * The default event handler fired when the "width" property is changed.
28885        * @method configWidth
28886        * @param {String} type The CustomEvent type (usually the property name)
28887        * @param {Object[]} args The CustomEvent arguments. For configuration 
28888        * handlers, args[0] will equal the newly applied value for the property.
28889        * @param {Object} obj The scope object. For configuration handlers, 
28890        * this will usually equal the owner.
28891        */
28892        configWidth: function (type, args, obj) {
28893
28894            var width = args[0],
28895                el = this.element;
28896
28897            Dom.setStyle(el, "width", width);
28898            this.cfg.refireEvent("iframe");
28899        },
28900
28901        /**
28902        * The default event handler fired when the "zIndex" property is changed.
28903        * @method configzIndex
28904        * @param {String} type The CustomEvent type (usually the property name)
28905        * @param {Object[]} args The CustomEvent arguments. For configuration 
28906        * handlers, args[0] will equal the newly applied value for the property.
28907        * @param {Object} obj The scope object. For configuration handlers, 
28908        * this will usually equal the owner.
28909        */
28910        configzIndex: function (type, args, obj) {
28911
28912            var zIndex = args[0],
28913                el = this.element;
28914
28915            if (! zIndex) {
28916                zIndex = Dom.getStyle(el, "zIndex");
28917                if (! zIndex || isNaN(zIndex)) {
28918                    zIndex = 0;
28919                }
28920            }
28921
28922            if (this.iframe || this.cfg.getProperty("iframe") === true) {
28923                if (zIndex <= 0) {
28924                    zIndex = 1;
28925                }
28926            }
28927
28928            Dom.setStyle(el, "zIndex", zIndex);
28929            this.cfg.setProperty("zIndex", zIndex, true);
28930
28931            if (this.iframe) {
28932                this.stackIframe();
28933            }
28934        },
28935
28936        /**
28937        * The default event handler fired when the "xy" property is changed.
28938        * @method configXY
28939        * @param {String} type The CustomEvent type (usually the property name)
28940        * @param {Object[]} args The CustomEvent arguments. For configuration 
28941        * handlers, args[0] will equal the newly applied value for the property.
28942        * @param {Object} obj The scope object. For configuration handlers, 
28943        * this will usually equal the owner.
28944        */
28945        configXY: function (type, args, obj) {
28946
28947            var pos = args[0],
28948                x = pos[0],
28949                y = pos[1];
28950
28951            this.cfg.setProperty("x", x);
28952            this.cfg.setProperty("y", y);
28953
28954            this.beforeMoveEvent.fire([x, y]);
28955
28956            x = this.cfg.getProperty("x");
28957            y = this.cfg.getProperty("y");
28958
28959            YAHOO.log(("xy: " + [x, y]), "iframe");
28960
28961            this.cfg.refireEvent("iframe");
28962            this.moveEvent.fire([x, y]);
28963        },
28964
28965        /**
28966        * The default event handler fired when the "x" property is changed.
28967        * @method configX
28968        * @param {String} type The CustomEvent type (usually the property name)
28969        * @param {Object[]} args The CustomEvent arguments. For configuration 
28970        * handlers, args[0] will equal the newly applied value for the property.
28971        * @param {Object} obj The scope object. For configuration handlers, 
28972        * this will usually equal the owner.
28973        */
28974        configX: function (type, args, obj) {
28975
28976            var x = args[0],
28977                y = this.cfg.getProperty("y");
28978
28979            this.cfg.setProperty("x", x, true);
28980            this.cfg.setProperty("y", y, true);
28981
28982            this.beforeMoveEvent.fire([x, y]);
28983
28984            x = this.cfg.getProperty("x");
28985            y = this.cfg.getProperty("y");
28986
28987            Dom.setX(this.element, x, true);
28988
28989            this.cfg.setProperty("xy", [x, y], true);
28990
28991            this.cfg.refireEvent("iframe");
28992            this.moveEvent.fire([x, y]);
28993        },
28994
28995        /**
28996        * The default event handler fired when the "y" property is changed.
28997        * @method configY
28998        * @param {String} type The CustomEvent type (usually the property name)
28999        * @param {Object[]} args The CustomEvent arguments. For configuration 
29000        * handlers, args[0] will equal the newly applied value for the property.
29001        * @param {Object} obj The scope object. For configuration handlers, 
29002        * this will usually equal the owner.
29003        */
29004        configY: function (type, args, obj) {
29005
29006            var x = this.cfg.getProperty("x"),
29007                y = args[0];
29008
29009            this.cfg.setProperty("x", x, true);
29010            this.cfg.setProperty("y", y, true);
29011
29012            this.beforeMoveEvent.fire([x, y]);
29013
29014            x = this.cfg.getProperty("x");
29015            y = this.cfg.getProperty("y");
29016
29017            Dom.setY(this.element, y, true);
29018
29019            this.cfg.setProperty("xy", [x, y], true);
29020
29021            this.cfg.refireEvent("iframe");
29022            this.moveEvent.fire([x, y]);
29023        },
29024        
29025        /**
29026        * Shows the iframe shim, if it has been enabled.
29027        * @method showIframe
29028        */
29029        showIframe: function () {
29030
29031            var oIFrame = this.iframe,
29032                oParentNode;
29033
29034            if (oIFrame) {
29035                oParentNode = this.element.parentNode;
29036
29037                if (oParentNode != oIFrame.parentNode) {
29038                    this._addToParent(oParentNode, oIFrame);
29039                }
29040                oIFrame.style.display = "block";
29041            }
29042        },
29043
29044        /**
29045        * Hides the iframe shim, if it has been enabled.
29046        * @method hideIframe
29047        */
29048        hideIframe: function () {
29049            if (this.iframe) {
29050                this.iframe.style.display = "none";
29051            }
29052        },
29053
29054        /**
29055        * Syncronizes the size and position of iframe shim to that of its 
29056        * corresponding Overlay instance.
29057        * @method syncIframe
29058        */
29059        syncIframe: function () {
29060
29061            var oIFrame = this.iframe,
29062                oElement = this.element,
29063                nOffset = Overlay.IFRAME_OFFSET,
29064                nDimensionOffset = (nOffset * 2),
29065                aXY;
29066
29067            if (oIFrame) {
29068                // Size <iframe>
29069                oIFrame.style.width = (oElement.offsetWidth + nDimensionOffset + "px");
29070                oIFrame.style.height = (oElement.offsetHeight + nDimensionOffset + "px");
29071
29072                // Position <iframe>
29073                aXY = this.cfg.getProperty("xy");
29074
29075                if (!Lang.isArray(aXY) || (isNaN(aXY[0]) || isNaN(aXY[1]))) {
29076                    this.syncPosition();
29077                    aXY = this.cfg.getProperty("xy");
29078                }
29079                Dom.setXY(oIFrame, [(aXY[0] - nOffset), (aXY[1] - nOffset)]);
29080            }
29081        },
29082
29083        /**
29084         * Sets the zindex of the iframe shim, if it exists, based on the zindex of
29085         * the Overlay element. The zindex of the iframe is set to be one less 
29086         * than the Overlay element's zindex.
29087         * 
29088         * <p>NOTE: This method will not bump up the zindex of the Overlay element
29089         * to ensure that the iframe shim has a non-negative zindex.
29090         * If you require the iframe zindex to be 0 or higher, the zindex of 
29091         * the Overlay element should be set to a value greater than 0, before 
29092         * this method is called.
29093         * </p>
29094         * @method stackIframe
29095         */
29096        stackIframe: function () {
29097            if (this.iframe) {
29098                var overlayZ = Dom.getStyle(this.element, "zIndex");
29099                if (!YAHOO.lang.isUndefined(overlayZ) && !isNaN(overlayZ)) {
29100                    Dom.setStyle(this.iframe, "zIndex", (overlayZ - 1));
29101                }
29102            }
29103        },
29104
29105        /**
29106        * The default event handler fired when the "iframe" property is changed.
29107        * @method configIframe
29108        * @param {String} type The CustomEvent type (usually the property name)
29109        * @param {Object[]} args The CustomEvent arguments. For configuration 
29110        * handlers, args[0] will equal the newly applied value for the property.
29111        * @param {Object} obj The scope object. For configuration handlers, 
29112        * this will usually equal the owner.
29113        */
29114        configIframe: function (type, args, obj) {
29115
29116            var bIFrame = args[0];
29117
29118            function createIFrame() {
29119
29120                var oIFrame = this.iframe,
29121                    oElement = this.element,
29122                    oParent;
29123
29124                if (!oIFrame) {
29125                    if (!m_oIFrameTemplate) {
29126                        m_oIFrameTemplate = document.createElement("iframe");
29127
29128                        if (this.isSecure) {
29129                            m_oIFrameTemplate.src = Overlay.IFRAME_SRC;
29130                        }
29131
29132                        /*
29133                            Set the opacity of the <iframe> to 0 so that it 
29134                            doesn't modify the opacity of any transparent 
29135                            elements that may be on top of it (like a shadow).
29136                        */
29137                        if (UA.ie) {
29138                            m_oIFrameTemplate.style.filter = "alpha(opacity=0)";
29139                            /*
29140                                 Need to set the "frameBorder" property to 0 
29141                                 supress the default <iframe> border in IE.  
29142                                 Setting the CSS "border" property alone 
29143                                 doesn't supress it.
29144                            */
29145                            m_oIFrameTemplate.frameBorder = 0;
29146                        }
29147                        else {
29148                            m_oIFrameTemplate.style.opacity = "0";
29149                        }
29150
29151                        m_oIFrameTemplate.style.position = "absolute";
29152                        m_oIFrameTemplate.style.border = "none";
29153                        m_oIFrameTemplate.style.margin = "0";
29154                        m_oIFrameTemplate.style.padding = "0";
29155                        m_oIFrameTemplate.style.display = "none";
29156                        m_oIFrameTemplate.tabIndex = -1;
29157                        m_oIFrameTemplate.className = Overlay.CSS_IFRAME;
29158                    }
29159
29160                    oIFrame = m_oIFrameTemplate.cloneNode(false);
29161                    oIFrame.id = this.id + "_f";
29162                    oParent = oElement.parentNode;
29163
29164                    var parentNode = oParent || document.body;
29165
29166                    this._addToParent(parentNode, oIFrame);
29167                    this.iframe = oIFrame;
29168                }
29169
29170                /*
29171                     Show the <iframe> before positioning it since the "setXY" 
29172                     method of DOM requires the element be in the document 
29173                     and visible.
29174                */
29175                this.showIframe();
29176
29177                /*
29178                     Syncronize the size and position of the <iframe> to that 
29179                     of the Overlay.
29180                */
29181                this.syncIframe();
29182                this.stackIframe();
29183
29184                // Add event listeners to update the <iframe> when necessary
29185                if (!this._hasIframeEventListeners) {
29186                    this.showEvent.subscribe(this.showIframe);
29187                    this.hideEvent.subscribe(this.hideIframe);
29188                    this.changeContentEvent.subscribe(this.syncIframe);
29189
29190                    this._hasIframeEventListeners = true;
29191                }
29192            }
29193
29194            function onBeforeShow() {
29195                createIFrame.call(this);
29196                this.beforeShowEvent.unsubscribe(onBeforeShow);
29197                this._iframeDeferred = false;
29198            }
29199
29200            if (bIFrame) { // <iframe> shim is enabled
29201
29202                if (this.cfg.getProperty("visible")) {
29203                    createIFrame.call(this);
29204                } else {
29205                    if (!this._iframeDeferred) {
29206                        this.beforeShowEvent.subscribe(onBeforeShow);
29207                        this._iframeDeferred = true;
29208                    }
29209                }
29210
29211            } else {    // <iframe> shim is disabled
29212                this.hideIframe();
29213
29214                if (this._hasIframeEventListeners) {
29215                    this.showEvent.unsubscribe(this.showIframe);
29216                    this.hideEvent.unsubscribe(this.hideIframe);
29217                    this.changeContentEvent.unsubscribe(this.syncIframe);
29218
29219                    this._hasIframeEventListeners = false;
29220                }
29221            }
29222        },
29223
29224        /**
29225         * Set's the container's XY value from DOM if not already set.
29226         * 
29227         * Differs from syncPosition, in that the XY value is only sync'd with DOM if 
29228         * not already set. The method also refire's the XY config property event, so any
29229         * beforeMove, Move event listeners are invoked.
29230         * 
29231         * @method _primeXYFromDOM
29232         * @protected
29233         */
29234        _primeXYFromDOM : function() {
29235            if (YAHOO.lang.isUndefined(this.cfg.getProperty("xy"))) {
29236                // Set CFG XY based on DOM XY
29237                this.syncPosition();
29238                // Account for XY being set silently in syncPosition (no moveTo fired/called)
29239                this.cfg.refireEvent("xy");
29240                this.beforeShowEvent.unsubscribe(this._primeXYFromDOM);
29241            }
29242        },
29243
29244        /**
29245        * The default event handler fired when the "constraintoviewport" 
29246        * property is changed.
29247        * @method configConstrainToViewport
29248        * @param {String} type The CustomEvent type (usually the property name)
29249        * @param {Object[]} args The CustomEvent arguments. For configuration 
29250        * handlers, args[0] will equal the newly applied value for 
29251        * the property.
29252        * @param {Object} obj The scope object. For configuration handlers, 
29253        * this will usually equal the owner.
29254        */
29255        configConstrainToViewport: function (type, args, obj) {
29256            var val = args[0];
29257
29258            if (val) {
29259                if (! Config.alreadySubscribed(this.beforeMoveEvent, this.enforceConstraints, this)) {
29260                    this.beforeMoveEvent.subscribe(this.enforceConstraints, this, true);
29261                }
29262                if (! Config.alreadySubscribed(this.beforeShowEvent, this._primeXYFromDOM)) {
29263                    this.beforeShowEvent.subscribe(this._primeXYFromDOM);
29264                }
29265            } else {
29266                this.beforeShowEvent.unsubscribe(this._primeXYFromDOM);
29267                this.beforeMoveEvent.unsubscribe(this.enforceConstraints, this);
29268            }
29269        },
29270
29271         /**
29272        * The default event handler fired when the "context" property
29273        * is changed.
29274        *
29275        * @method configContext
29276        * @param {String} type The CustomEvent type (usually the property name)
29277        * @param {Object[]} args The CustomEvent arguments. For configuration 
29278        * handlers, args[0] will equal the newly applied value for the property.
29279        * @param {Object} obj The scope object. For configuration handlers, 
29280        * this will usually equal the owner.
29281        */
29282        configContext: function (type, args, obj) {
29283
29284            var contextArgs = args[0],
29285                contextEl,
29286                elementMagnetCorner,
29287                contextMagnetCorner,
29288                triggers,
29289                offset,
29290                defTriggers = this.CONTEXT_TRIGGERS;
29291
29292            if (contextArgs) {
29293
29294                contextEl = contextArgs[0];
29295                elementMagnetCorner = contextArgs[1];
29296                contextMagnetCorner = contextArgs[2];
29297                triggers = contextArgs[3];
29298                offset = contextArgs[4];
29299
29300                if (defTriggers && defTriggers.length > 0) {
29301                    triggers = (triggers || []).concat(defTriggers);
29302                }
29303
29304                if (contextEl) {
29305                    if (typeof contextEl == "string") {
29306                        this.cfg.setProperty("context", [
29307                                document.getElementById(contextEl), 
29308                                elementMagnetCorner,
29309                                contextMagnetCorner,
29310                                triggers,
29311                                offset],
29312                                true);
29313                    }
29314
29315                    if (elementMagnetCorner && contextMagnetCorner) {
29316                        this.align(elementMagnetCorner, contextMagnetCorner, offset);
29317                    }
29318
29319                    if (this._contextTriggers) {
29320                        // Unsubscribe Old Set
29321                        this._processTriggers(this._contextTriggers, _UNSUBSCRIBE, this._alignOnTrigger);
29322                    }
29323
29324                    if (triggers) {
29325                        // Subscribe New Set
29326                        this._processTriggers(triggers, _SUBSCRIBE, this._alignOnTrigger);
29327                        this._contextTriggers = triggers;
29328                    }
29329                }
29330            }
29331        },
29332
29333        /**
29334         * Custom Event handler for context alignment triggers. Invokes the align method
29335         * 
29336         * @method _alignOnTrigger
29337         * @protected
29338         * 
29339         * @param {String} type The event type (not used by the default implementation)
29340         * @param {Any[]} args The array of arguments for the trigger event (not used by the default implementation)
29341         */
29342        _alignOnTrigger: function(type, args) {
29343            this.align();
29344        },
29345
29346        /**
29347         * Helper method to locate the custom event instance for the event name string
29348         * passed in. As a convenience measure, any custom events passed in are returned.
29349         *
29350         * @method _findTriggerCE
29351         * @private
29352         *
29353         * @param {String|CustomEvent} t Either a CustomEvent, or event type (e.g. "windowScroll") for which a 
29354         * custom event instance needs to be looked up from the Overlay._TRIGGER_MAP.
29355         */
29356        _findTriggerCE : function(t) {
29357            var tce = null;
29358            if (t instanceof CustomEvent) {
29359                tce = t;
29360            } else if (Overlay._TRIGGER_MAP[t]) {
29361                tce = Overlay._TRIGGER_MAP[t];
29362            }
29363            return tce;
29364        },
29365
29366        /**
29367         * Utility method that subscribes or unsubscribes the given 
29368         * function from the list of trigger events provided.
29369         *
29370         * @method _processTriggers
29371         * @protected 
29372         *
29373         * @param {Array[String|CustomEvent]} triggers An array of either CustomEvents, event type strings 
29374         * (e.g. "beforeShow", "windowScroll") to/from which the provided function should be 
29375         * subscribed/unsubscribed respectively.
29376         *
29377         * @param {String} mode Either "subscribe" or "unsubscribe", specifying whether or not
29378         * we are subscribing or unsubscribing trigger listeners
29379         * 
29380         * @param {Function} fn The function to be subscribed/unsubscribed to/from the trigger event.
29381         * Context is always set to the overlay instance, and no additional object argument 
29382         * get passed to the subscribed function.
29383         */
29384        _processTriggers : function(triggers, mode, fn) {
29385            var t, tce;
29386
29387            for (var i = 0, l = triggers.length; i < l; ++i) {
29388                t = triggers[i];
29389                tce = this._findTriggerCE(t);
29390                if (tce) {
29391                    tce[mode](fn, this, true);
29392                } else {
29393                    this[mode](t, fn);
29394                }
29395            }
29396        },
29397
29398        // END BUILT-IN PROPERTY EVENT HANDLERS //
29399        /**
29400        * Aligns the Overlay to its context element using the specified corner 
29401        * points (represented by the constants TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, 
29402        * and BOTTOM_RIGHT.
29403        * @method align
29404        * @param {String} elementAlign  The String representing the corner of 
29405        * the Overlay that should be aligned to the context element
29406        * @param {String} contextAlign  The corner of the context element 
29407        * that the elementAlign corner should stick to.
29408        * @param {Number[]} xyOffset Optional. A 2 element array specifying the x and y pixel offsets which should be applied
29409        * after aligning the element and context corners. For example, passing in [5, -10] for this value, would offset the 
29410        * Overlay by 5 pixels along the X axis (horizontally) and -10 pixels along the Y axis (vertically) after aligning the specified corners.
29411        */
29412        align: function (elementAlign, contextAlign, xyOffset) {
29413
29414            var contextArgs = this.cfg.getProperty("context"),
29415                me = this,
29416                context,
29417                element,
29418                contextRegion;
29419
29420            function doAlign(v, h) {
29421
29422                var alignX = null, alignY = null;
29423
29424                switch (elementAlign) {
29425    
29426                    case Overlay.TOP_LEFT:
29427                        alignX = h;
29428                        alignY = v;
29429                        break;
29430        
29431                    case Overlay.TOP_RIGHT:
29432                        alignX = h - element.offsetWidth;
29433                        alignY = v;
29434                        break;
29435        
29436                    case Overlay.BOTTOM_LEFT:
29437                        alignX = h;
29438                        alignY = v - element.offsetHeight;
29439                        break;
29440        
29441                    case Overlay.BOTTOM_RIGHT:
29442                        alignX = h - element.offsetWidth; 
29443                        alignY = v - element.offsetHeight;
29444                        break;
29445                }
29446
29447                if (alignX !== null && alignY !== null) {
29448                    if (xyOffset) {
29449                        alignX += xyOffset[0];
29450                        alignY += xyOffset[1];
29451                    }
29452                    me.moveTo(alignX, alignY);
29453                }
29454            }
29455
29456            if (contextArgs) {
29457                context = contextArgs[0];
29458                element = this.element;
29459                me = this;
29460
29461                if (! elementAlign) {
29462                    elementAlign = contextArgs[1];
29463                }
29464
29465                if (! contextAlign) {
29466                    contextAlign = contextArgs[2];
29467                }
29468
29469                if (!xyOffset && contextArgs[4]) {
29470                    xyOffset = contextArgs[4];
29471                }
29472
29473                if (element && context) {
29474                    contextRegion = Dom.getRegion(context);
29475
29476                    switch (contextAlign) {
29477    
29478                        case Overlay.TOP_LEFT:
29479                            doAlign(contextRegion.top, contextRegion.left);
29480                            break;
29481        
29482                        case Overlay.TOP_RIGHT:
29483                            doAlign(contextRegion.top, contextRegion.right);
29484                            break;
29485        
29486                        case Overlay.BOTTOM_LEFT:
29487                            doAlign(contextRegion.bottom, contextRegion.left);
29488                            break;
29489        
29490                        case Overlay.BOTTOM_RIGHT:
29491                            doAlign(contextRegion.bottom, contextRegion.right);
29492                            break;
29493                    }
29494                }
29495            }
29496        },
29497
29498        /**
29499        * The default event handler executed when the moveEvent is fired, if the 
29500        * "constraintoviewport" is set to true.
29501        * @method enforceConstraints
29502        * @param {String} type The CustomEvent type (usually the property name)
29503        * @param {Object[]} args The CustomEvent arguments. For configuration 
29504        * handlers, args[0] will equal the newly applied value for the property.
29505        * @param {Object} obj The scope object. For configuration handlers, 
29506        * this will usually equal the owner.
29507        */
29508        enforceConstraints: function (type, args, obj) {
29509            var pos = args[0];
29510
29511            var cXY = this.getConstrainedXY(pos[0], pos[1]);
29512            this.cfg.setProperty("x", cXY[0], true);
29513            this.cfg.setProperty("y", cXY[1], true);
29514            this.cfg.setProperty("xy", cXY, true);
29515        },
29516
29517        /**
29518         * Shared implementation method for getConstrainedX and getConstrainedY.
29519         * 
29520         * <p>
29521         * Given a coordinate value, returns the calculated coordinate required to 
29522         * position the Overlay if it is to be constrained to the viewport, based on the 
29523         * current element size, viewport dimensions, scroll values and preventoverlap 
29524         * settings
29525         * </p>
29526         *
29527         * @method _getConstrainedPos
29528         * @protected
29529         * @param {String} pos The coordinate which needs to be constrained, either "x" or "y"
29530         * @param {Number} The coordinate value which needs to be constrained
29531         * @return {Number} The constrained coordinate value
29532         */
29533        _getConstrainedPos: function(pos, val) {
29534
29535            var overlayEl = this.element,
29536
29537                buffer = Overlay.VIEWPORT_OFFSET,
29538
29539                x = (pos == "x"),
29540
29541                overlaySize      = (x) ? overlayEl.offsetWidth : overlayEl.offsetHeight,
29542                viewportSize     = (x) ? Dom.getViewportWidth() : Dom.getViewportHe
29542ight(),
29543                docScroll        = (x) ? Dom.getDocumentScrollLeft() : Dom.getDocumentScrollTop(),
29544                overlapPositions = (x) ? Overlay.PREVENT_OVERLAP_X : Overlay.PREVENT_OVERLAP_Y,
29545
29546                context = this.cfg.getProperty("context"),
29547
29548                bOverlayFitsInViewport = (overlaySize + buffer < viewportSize),
29549                bPreventContextOverlap = this.cfg.getProperty("preventcontextoverlap") && context && overlapPositions[(context[1] + context[2])],
29550
29551                minConstraint = docScroll + buffer,
29552                maxConstraint = docScroll + viewportSize - overlaySize - buffer,
29553
29554                constrainedVal = val;
29555
29556            if (val < minConstraint || val > maxConstraint) {
29557                if (bPreventContextOverlap) {
29558                    constrainedVal = this._preventOverlap(pos, context[0], overlaySize, viewportSize, docScroll);
29559                } else {
29560                    if (bOverlayFitsInViewport) {
29561                        if (val < minConstraint) {
29562                            constrainedVal = minConstraint;
29563                        } else if (val > maxConstraint) {
29564                            constrainedVal = maxConstraint;
29565                        }
29566                    } else {
29567                        constrainedVal = minConstraint;
29568                    }
29569                }
29570            }
29571
29572            return constrainedVal;
29573        },
29574
29575        /**
29576         * Helper method, used to position the Overlap to prevent overlap with the 
29577         * context element (used when preventcontextoverlap is enabled)
29578         *
29579         * @method _preventOverlap
29580         * @protected
29581         * @param {String} pos The coordinate to prevent overlap for, either "x" or "y".
29582         * @param {HTMLElement} contextEl The context element
29583         * @param {Number} overlaySize The related overlay dimension value (for "x", the width, for "y", the height)
29584         * @param {Number} viewportSize The related viewport dimension value (for "x", the width, for "y", the height)
29585         * @param {Object} docScroll  The related document scroll value (for "x", the scrollLeft, for "y", the scrollTop)
29586         *
29587         * @return {Number} The new coordinate value which was set to prevent overlap
29588         */
29589        _preventOverlap : function(pos, contextEl, overlaySize, viewportSize, docScroll) {
29590            
29591            var x = (pos == "x"),
29592
29593                buffer = Overlay.VIEWPORT_OFFSET,
29594
29595                overlay = this,
29596
29597                contextElPos   = ((x) ? Dom.getX(contextEl) : Dom.getY(contextEl)) - docScroll,
29598                contextElSize  = (x) ? contextEl.offsetWidth : contextEl.offsetHeight,
29599
29600                minRegionSize = contextElPos - buffer,
29601                maxRegionSize = (viewportSize - (contextElPos + contextElSize)) - buffer,
29602
29603                bFlipped = false,
29604
29605                flip = function () {
29606                    var flippedVal;
29607
29608                    if ((overlay.cfg.getProperty(pos) - docScroll) > contextElPos) {
29609                        flippedVal = (contextElPos - overlaySize);
29610                    } else {
29611                        flippedVal = (contextElPos + contextElSize);
29612                    }
29613
29614                    overlay.cfg.setProperty(pos, (flippedVal + docScroll), true);
29615
29616                    return flippedVal;
29617                },
29618
29619                setPosition = function () {
29620
29621                    var displayRegionSize = ((overlay.cfg.getProperty(pos) - docScroll) > contextElPos) ? maxRegionSize : minRegionSize,
29622                        position;
29623
29624                    if (overlaySize > displayRegionSize) {
29625                        if (bFlipped) {
29626                            /*
29627                                 All possible positions and values have been 
29628                                 tried, but none were successful, so fall back 
29629                                 to the original size and position.
29630                            */
29631                            flip();
29632                        } else {
29633                            flip();
29634                            bFlipped = true;
29635                            position = setPosition();
29636                        }
29637                    }
29638
29639                    return position;
29640                };
29641
29642            setPosition();
29643
29644            return this.cfg.getProperty(pos);
29645        },
29646
29647        /**
29648         * Given x coordinate value, returns the calculated x coordinate required to 
29649         * position the Overlay if it is to be constrained to the viewport, based on the 
29650         * current element size, viewport dimensions and scroll values.
29651         *
29652         * @param {Number} x The X coordinate value to be constrained
29653         * @return {Number} The constrained x coordinate
29654         */		
29655        getConstrainedX: function (x) {
29656            return this._getConstrainedPos("x", x);
29657        },
29658
29659        /**
29660         * Given y coordinate value, returns the calculated y co
29660ordinate required to 
29661         * position the Overlay if it is to be constrained to the viewport, based on the 
29662         * current element size, viewport dimensions and scroll values.
29663         *
29664         * @param {Number} y The Y coordinate value to be constrained
29665         * @return {Number} The constrained y coordinate
29666         */		
29667        getConstrainedY : function (y) {
29668            return this._getConstrainedPos("y", y);
29669        },
29670
29671        /**
29672         * Given x, y coordinate values, returns the calculated coordinates required to 
29673         * position the Overlay if it is to be constrained to the viewport, based on the 
29674         * current element size, viewport dimensions and scroll values.
29675         *
29676         * @param {Number} x The X coordinate value to be constrained
29677         * @param {Number} y The Y coordinate value to be constrained
29678         * @return {Array} The constrained x and y coordinates at index 0 and 1 respectively;
29679         */
29680        getConstrainedXY: function(x, y) {
29681            return [this.getConstrainedX(x), this.getConstrainedY(y)];
29682        },
29683
29684        /**
29685        * Centers the container in the viewport.
29686        * @method center
29687        */
29688        center: function () {
29689
29690            var nViewportOffset = Overlay.VIEWPORT_OFFSET,
29691                elementWidth = this.element.offsetWidth,
29692                elementHeight = this.element.offsetHeight,
29693                viewPortWidth = Dom.getViewportWidth(),
29694                viewPortHeight = Dom.getViewportHeight(),
29695                x,
29696                y;
29697
29698            if (elementWidth < viewPortWidth) {
29699                x = (viewPortWidth / 2) - (elementWidth / 2) + Dom.getDocumentScrollLeft();
29700            } else {
29701                x = nViewportOffset + Dom.getDocumentScrollLeft();
29702            }
29703
29704            if (elementHeight < viewPortHeight) {
29705                y = (viewPortHeight / 2) - (elementHeight / 2) + Dom.getDocumentScrollTop();
29706            } else {
29707                y = nViewportOffset + Dom.getDocumentScrollTop();
29708            }
29709
29710            this.cfg.setProperty("xy", [parseInt(x, 10), parseInt(y, 10)]);
29711            this.cfg.refireEvent("iframe");
29712
29713            if (UA.webkit) {
29714                this.forceContainerRedraw();
29715            }
29716        },
29717
29718        /**
29719        * Synchronizes the Panel's "xy", "x", and "y" properties with the 
29720        * Panel's position in the DOM. This is primarily used to update  
29721        * position information during drag & drop.
29722        * @method syncPosition
29723        */
29724        syncPosition: function () {
29725
29726            var pos = Dom.getXY(this.element);
29727
29728            this.cfg.setProperty("x", pos[0], true);
29729            this.cfg.setProperty("y", pos[1], true);
29730            this.cfg.setProperty("xy", pos, true);
29731
29732        },
29733
29734        /**
29735        * Event handler fired when the resize monitor element is resized.
29736        * @method onDomResize
29737        * @param {DOMEvent} e The resize DOM event
29738        * @param {Object} obj The scope object
29739        */
29740        onDomResize: function (e, obj) {
29741
29742            var me = this;
29743
29744            Overlay.superclass.onDomResize.call(this, e, obj);
29745
29746            setTimeout(function () {
29747                me.syncPosition();
29748                me.cfg.refireEvent("iframe");
29749                me.cfg.refireEvent("context");
29750            }, 0);
29751        },
29752
29753        /**
29754         * Determines the content box height of the given element (height of the element, without padding or borders) in pixels.
29755         *
29756         * @method _getComputedHeight
29757         * @private
29758         * @param {HTMLElement} el The element for which the content height needs to be determined
29759         * @return {Number} The content box height of the given element, or null if it could not be determined.
29760         */
29761        _getComputedHeight : (function() {
29762
29763            if (document.defaultView && document.defaultView.getComputedStyle) {
29764                return function(el) {
29765                    var height = null;
29766                    if (el.ownerDocument && el.ownerDocument.defaultView) {
29767                        var computed = el.ownerDocument.defaultView.getComputedStyle(el, '');
29768                        if (computed) {
29769                            height = parseInt(computed.height, 10);
29770                        }
29771                    }
29772                    return (Lang.isNumber(height)) ? height : null;
29773                };
29774            } else {
29775                return function(el) {
29776                    var height = null;
29777                    if (el.style.pixelHeight) {
29778                        height = el.style.pixelHeight;
29779                    }
29780                    return (Lang.isNumber(height)) ? height : null;
29781                };
29782            }
29783        })(),
29784
29785        /**
29786         * autofillheight validator. Verifies that the autofill value is either null 
29787         * or one of the strings : "body", "header" or "footer".
29788         *
29789         * @method _validateAutoFillHeight
29790         * @protected
29791         * @param {String} val
29792         * @return true, if valid, false otherwise
29793         */
29794        _validateAutoFillHeight : function(val) {
29795            return (!val) || (Lang.isString(val) && Overlay.STD_MOD_RE.test(val));
29796        },
29797
29798        /**
29799         * The default custom event handler executed when the overlay's height is changed, 
29800         * if the autofillheight property has been set.
29801         *
29802         * @method _autoFillOnHeightChange
29803         * @protected
29804         * @param {String} type The event type
29805         * @param {Array} args The array of arguments passed to event subscribers
29806         * @param {HTMLElement} el The header, body or footer element which is to be resized to fill
29807         * out the containers height
29808         */
29809        _autoFillOnHeightChange : function(type, args, el) {
29810            var height = this.cfg.getProperty("height");
29811            if ((height && height !== "auto") || (height === 0)) {
29812                this.fillHeight(el);
29813            }
29814        },
29815
29816        /**
29817         * Returns the sub-pixel height of the el, using getBoundingClientRect, if available,
29818         * otherwise returns the offsetHeight
29819         * @method _getPreciseHeight
29820         * @private
29821         * @param {HTMLElement} el
29822         * @return {Float} The sub-pixel height if supported by the browser, else the rounded height.
29823         */
29824        _getPreciseHeight : function(el) {
29825            var height = el.offsetHeight;
29826
29827            if (el.getBoundingClientRect) {
29828                var rect = el.getBoundingClientRect();
29829                height = rect.bottom - rect.top;
29830            }
29831
29832            return height;
29833        },
29834
29835        /**
29836         * <p>
29837         * Sets the height on the provided header, body or footer element to 
29838         * fill out the height of the container. It determines the height of the 
29839         * containers content box, based on it's configured height value, and 
29840         * sets the height of the autofillheight element to fill out any 
29841         * space remaining after the other standard module element heights 
29842         * have been accounted for.
29843         * </p>
29844         * <p><strong>NOTE:</strong> This method is not designed to work if an explicit 
29845         * height has not been set on the container, since for an "auto" height container, 
29846         * the heights of the header/body/footer will drive the height of the container.</p>
29847         *
29848         * @method fillHeight
29849         * @param {HTMLElement} el The element which should be resized to fill out the height
29850         * of the container element.
29851         */
29852        fillHeight : function(el) {
29853            if (el) {
29854                var container = this.innerElement || this.element,
29855                    containerEls = [this.header, this.body, this.footer],
29856                    containerEl,
29857                    total = 0,
29858                    filled = 0,
29859                    remaining = 0,
29860                    validEl = false;
29861
29862                for (var i = 0, l = containerEls.length; i < l; i++) {
29863                    containerEl = containerEls[i];
29864                    if (containerEl) {
29865                        if (el !== containerEl) {
29866                            filled += this._getPreciseHeight(containerEl);
29867                        } else {
29868                            validEl = true;
29869                        }
29870                    }
29871                }
29872
29873                if (validEl) {
29874
29875                    if (UA.ie || UA.opera) {
29876                        // Need to set height to 0, to allow height to be reduced
29877                        Dom.setStyle(el, 'height', 0 + 'px');
29878                    }
29879
29880                    total = this._getComputedHeight(container);
29881
29882                    // Fallback, if we can't get computed value for content height
29883                    if (total === null) {
29884                        Dom.addClass(container, "yui-override-padding");
29885                        total = container.clientHeight; // Content, No Border, 0 Padding (set by yui-override-padding)
29886                        Dom.removeClass(container, "yui-override-padding");
29887                    }
29888    
29889                    remaining = Math.max(total - filled, 0);
29890    
29891                    Dom.setStyle(el, "height", remaining + "px");
29892    
29893                    // Re-adjust height if required, to account for el padding and border
29894                    if (el.offsetHeight != remaining) {
29895                        remaining = Math.max(remaining - (el.offsetHeight - remaining), 0);
29896                    }
29897                    Dom.setStyle(el, "height", remaining + "px");
29898                }
29899            }
29900        },
29901
29902        /**
29903        * Places the Overlay on top of all other instances of 
29904        * YAHOO.widget.Overlay.
29905        * @method bringToTop
29906        */
29907        bringToTop: function () {
29908
29909            var aOverlays = [],
29910                oElement = this.element;
29911
29912            function compareZIndexDesc(p_oOverlay1, p_oOverlay2) {
29913
29914                var sZIndex1 = Dom.getStyle(p_oOverlay1, "zIndex"),
29915                    sZIndex2 = Dom.getStyle(p_oOverlay2, "zIndex"),
29916
29917                    nZIndex1 = (!sZIndex1 || isNaN(sZIndex1)) ? 0 : parseInt(sZIndex1, 10),
29918                    nZIndex2 = (!sZIndex2 || isNaN(sZIndex2)) ? 0 : parseInt(sZIndex2, 10);
29919
29920                if (nZIndex1 > nZIndex2) {
29921                    return -1;
29922                } else if (nZIndex1 < nZIndex2) {
29923                    return 1;
29924                } else {
29925                    return 0;
29926                }
29927            }
29928
29929            function isOverlayElement(p_oElement) {
29930
29931                var isOverlay = Dom.hasClass(p_oElement, Overlay.CSS_OVERLAY),
29932                    Panel = YAHOO.widget.Panel;
29933
29934                if (isOverlay && !Dom.isAncestor(oElement, p_oElement)) {
29935                    if (Panel && Dom.hasClass(p_oElement, Panel.CSS_PANEL)) {
29936                        aOverlays[aOverlays.length] = p_oElement.parentNode;
29937                    } else {
29938                        aOverlays[aOverlays.length] = p_oElement;
29939                    }
29940                }
29941            }
29942
29943            Dom.getElementsBy(isOverlayElement, "div", document.body);
29944
29945            aOverlays.sort(compareZIndexDesc);
29946
29947            var oTopOverlay = aOverlays[0],
29948                nTopZIndex;
29949
29950            if (oTopOverlay) {
29951                nTopZIndex = Dom.getStyle(oTopOverlay, "zIndex");
29952
29953                if (!isNaN(nTopZIndex)) {
29954                    var bRequiresBump = false;
29955
29956                    if (oTopOverlay != oElement) {
29957                        bRequiresBump = true;
29958                    } else if (aOverlays.length > 1) {
29959                        var nNextZIndex = Dom.getStyle(aOverlays[1], "zIndex");
29960                        // Don't rely on DOM order to stack if 2 overlays are at the same zindex.
29961                        if (!isNaN(nNextZIndex) && (nTopZIndex == nNextZIndex)) {
29962                            bRequiresBump = true;
29963                        }
29964                    }
29965                    if (bRequiresBump) {
29966                        this.cfg.setProperty("zindex", (parseInt(nTopZIndex, 10) + 2));
29967                    }
29968                }
29969            }
29970        },
29971
29972        /**
29973        * Removes the Overlay element from the DOM and sets all child 
29974        * elements to null.
29975        * @method destroy
29976        * @param {boolean} shallowPurge If true, only the parent element's DOM event listeners are purged. If false, or not provided, all children are also purged of DOM event listeners. 
29977        * NOTE: The flag is a "shallowPurge" flag, as opposed to what may be a more intuitive "purgeChildren" flag to maintain backwards compatibility with behavior prior to 2.9.0.
29978        */
29979        destroy: function (shallowPurge) {
29980
29981            if (this.iframe) {
29982                this.iframe.parentNode.removeChild(this.iframe);
29983            }
29984
29985            this.iframe = null;
29986
29987            Overlay.windowResizeEvent.unsubscribe(
29988                this.doCenterOnDOMEvent, this);
29989    
29990            Overlay.windowScrollEvent.unsubscribe(
29991                this.doCenterOnDOMEvent, this);
29992
29993            Module.textResizeEvent.unsubscribe(this._autoFillOnHeightChange);
29994
29995            if (this._contextTriggers) {
29996                // Unsubscribe context triggers - to cover context triggers which listen for global
29997                // events such as windowResize and windowScroll. Easier just to unsubscribe all
29998                this._processTriggers(this._contextTriggers, _UNSUBSCRIBE, this._alignOnTrigger);
29999            }
30000
30001            Overlay.superclass.destroy.call(this, shallowPurge);
30002        },
30003
30004        /**
30005         * Can be used to force the container to repaint/redraw it's contents.
30006         * <p>
30007         * By default applies and then removes a 1px bottom margin through the 
30008         * application/removal of a "yui-force-redraw" class.
30009         * </p>
30010         * <p>
30011         * It is currently used by Overlay to force a repaint for webkit 
30012         * browsers, when centering.
30013         * </p>
30014         * @method forceContainerRedraw
30015         */
30016        forceContainerRedraw : function() {
30017            var c = this;
30018            Dom.addClass(c.element, "yui-force-redraw");
30019            setTimeout(function() {
30020                Dom.removeClass(c.element, "yui-force-redraw");
30021            }, 0);
30022        },
30023
30024        /**
30025        * Returns a String representation of the object.
30026        * @method toString
30027        * @return {String} The string representation of the Overlay.
30028        */
30029        toString: function () {
30030            return "Overlay " + this.id;
30031        }
30032
30033    });
30034}());
30035(function () {
30036
30037    /**
30038    * OverlayManager is used for maintaining the focus status of 
30039    * multiple Overlays.
30040    * @namespace YAHOO.widget
30041    * @namespace YAHOO.widget
30042    * @class OverlayManager
30043    * @constructor
30044    * @param {Array} overlays Optional. A collection of Overlays to register 
30045    * with the manager.
30046    * @param {Object} userConfig  The object literal representing the user 
30047    * configuration of the OverlayManager
30048    */
30049    YAHOO.widget.OverlayManager = function (userConfig) {
30050        this.init(userConfig);
30051    };
30052
30053    var Overlay = YAHOO.widget.Overlay,
30054        Event = YAHOO.util.Event,
30055        Dom = YAHOO.util.Dom,
30056        Config = YAHOO.util.Config,
30057        CustomEvent = YAHOO.util.CustomEvent,
30058        OverlayManager = YAHOO.widget.OverlayManager;
30059
30060    /**
30061    * The CSS class representing a focused Overlay
30062    * @property OverlayManager.CSS_FOCUSED
30063    * @static
30064    * @final
30065    * @type String
30066    */
30067    OverlayManager.CSS_FOCUSED = "focused";
30068
30069    OverlayManager.prototype = {
30070
30071        /**
30072        * The class's constructor function
30073        * @property contructor
30074        * @type Function
30075        */
30076        constructor: OverlayManager,
30077
30078        /**
30079        * The array of Overlays that are currently registered
30080        * @property overlays
30081        * @type YAHOO.widget.Overlay[]
30082        */
30083        overlays: null,
30084
30085        /**
30086        * Initializes the default configuration of the OverlayManager
30087        * @method initDefaultConfig
30088        */
30089        initDefaultConfig: function () {
30090            /**
30091            * The collection of registered Overlays in use by 
30092            * the OverlayManager
30093            * @config overlays
30094            * @type YAHOO.widget.Overlay[]
30095            * @default null
30096            */
30097            this.cfg.addProperty("overlays", { suppressEvent: true } );
30098
30099            /**
30100            * The default DOM event that should be used to focus an Overlay
30101            * @config focusevent
30102            * @type String
30103            * @default "mousedown"
30104            */
30105            this.cfg.addProperty("focusevent", { value: "mousedown" } );
30106        },
30107
30108        /**
30109        * Initializes the OverlayManager
30110        * @method init
30111        * @param {Overlay[]} overlays Optional. A collection of Overlays to 
30112        * register with the manager.
30113        * @param {Object} userConfig  The object literal representing the user 
30114        * configuration of the OverlayManager
30115        */
30116        init: function (userConfig) {
30117
30118            /**
30119            * The OverlayManager's Config object used for monitoring 
30120            * configuration properties.
30121            * @property cfg
30122            * @type Config
30123            */
30124            this.cfg = new Config(this);
30125
30126            this.initDefaultConfig();
30127
30128            if (userConfig) {
30129                this.cfg.applyConfig(userConfig, true);
30130            }
30131            this.cfg.fireQueue();
30132
30133            /**
30134            * The currently activated Overlay
30135            * @property activeOverlay
30136            * @private
30137            * @type YAHOO.widget.Overlay
30138            */
30139            var activeOverlay = null;
30140
30141            /**
30142            * Returns the currently focused Overlay
30143            * @method getActive
30144            * @return {Overlay} The currently focused Overlay
30145            */
30146            this.getActive = function () {
30147                return this.activeOverlay;
30148            };
30149
30150            /**
30151            * Focuses the specified Overlay
30152            * @method focus
30153            * @param {Overlay} overlay The Overlay to focus
30154            * @param {String} overlay The id of the Overlay to focus
30155            */
30156            this.focus = function (overlay) {
30157                var o = this.find(overlay);
30158                if (o) {
30159                    o.focus();
30160                }
30161            };
30162
30163            /**
30164            * Removes the specified Overlay from the manager
30165            * @method remove
30166            * @param {Overlay} overlay The Overlay to remove
30167            * @param {String} overlay The id of the Overlay to remove
30168            */
30169            this.remove = function (overlay) {
30170
30171                var o = this.find(overlay), 
30172                        originalZ;
30173
30174                if (o) {
30175                    if (this.activeOverlay == o) {
30176                        this.activeOverlay = null;
30177                    }
30178
30179                    var bDestroyed = (o.element === null && o.cfg === null) ? true : false;
30180
30181                    if (!bDestroyed) {
30182                        // Set it's zindex so that it's sorted to the end.
30183                        originalZ = Dom.getStyle(o.element, "zIndex");
30184                        o.cfg.setProperty("zIndex", -1000, true);
30185                    }
30186
30187                    this.overlays.sort(this.compareZIndexDesc);
30188                    this.overlays = this.overlays.slice(0, (this.overlays.length - 1));
30189
30190                    o.hideEvent.unsubscribe(o.blur);
30191                    o.destroyEvent.unsubscribe(this._onOverlayDestroy, o);
30192                    o.focusEvent.unsubscribe(this._onOverlayFocusHandler, o);
30193                    o.blurEvent.unsubscribe(this._onOverlayBlurHandler, o);
30194
30195                    if (!bDestroyed) {
30196                        Event.removeListener(o.element, this.cfg.getProperty("focusevent"), this._onOverlayElementFocus);
30197                        o.cfg.setProperty("zIndex", originalZ, true);
30198                        o.cfg.setProperty("manager", null);
30199                    }
30200
30201                    /* _managed Flag for custom or existing. Don't want to remove existing */
30202                    if (o.focusEvent._managed) { o.focusEvent = null; }
30203                    if (o.blurEvent._managed) { o.blurEvent = null; }
30204
30205                    if (o.focus._managed) { o.focus = null; }
30206                    if (o.blur._managed) { o.blur = null; }
30207                }
30208            };
30209
30210            /**
30211            * Removes focus from all registered Overlays in the manager
30212            * @method blurAll
30213            */
30214            this.blurAll = function () {
30215
30216                var nOverlays = this.overlays.length,
30217                    i;
30218
30219                if (nOverlays > 0) {
30220                    i = nOverlays - 1;
30221                    do {
30222                        this.overlays[i].blur();
30223                    }
30224                    while(i--);
30225                }
30226            };
30227
30228            /**
30229             * Updates the state of the OverlayManager and overlay, as a result of the overlay
30230             * being blurred.
30231             * 
30232             * @method _manageBlur
30233             * @param {Overlay} overlay The overlay instance which got blurred.
30234             * @protected
30235             */
30236            this._manageBlur = function (overlay) {
30237                var changed = false;
30238                if (this.activeOverlay == overlay) {
30239                    Dom.removeClass(this.activeOverlay.element, OverlayManager.CSS_FOCUSED);
30240
30241                    changed = true;
30242                    activeOverlay = null;
30243                }
30244                return changed;
30245            };
30246
30247            /**
30248             * Updates the state of the OverlayManager and overlay, as a result of the overlay 
30249             * receiving focus.
30250             *
30251             * @method _manageFocus
30252             * @param {Overlay} overlay The overlay instance which got focus.
30253             * @protected
30254             */
30255            this._manageFocus = function(overlay) {
30256                var changed = false;
30257                if (this.activeOverlay != overlay) {
30258                    if (this.activeOverlay) {
30259                        this.activeOverlay.blur();
30260                    }
30261                    this.activeOverlay = overlay;
30262                    this.bringToTop(this.activeOverlay);
30263                    Dom.addClass(this.activeOverlay.element, OverlayManager.CSS_FOCUSED);
30264                    changed = true;
30265                }
30266                return changed;
30267            };
30268
30269            var overlays = this.cfg.getProperty("overlays");
30270
30271            if (! this.overlays) {
30272                this.overlays = [];
30273            }
30274
30275            if (overlays) {
30276                this.register(overlays);
30277                this.overlays.sort(this.compareZIndexDesc);
30278            }
30279        },
30280
30281        /**
30282        * @method _onOverlayElementFocus
30283        * @description Event handler for the DOM event that is used to focus 
30284        * the Overlay instance as specified by the "focusevent" 
30285        * configuration property.
30286        * @private
30287        * @param {Event} p_oEvent Object representing the DOM event 
30288        * object passed back by the event utility (Event).
30289        */
30290        _onOverlayElementFocus: function (p_oEvent) {
30291
30292            var oTarget = Event.getTarget(p_oEvent),
30293                oClose = this.close;
30294
30295            if (oClose && (oTarget == oClose || Dom.isAncestor(oClose, oTarget))) {
30296                this.blur();
30297            } else {
30298                this.focus();
30299            }
30300        },
30301
30302        /**
30303        * @method _onOverlayDestroy
30304        * @description "destroy" event handler for the Overlay.
30305        * @private
30306        * @param {String} p_sType String representing the name of the event  
30307        * that was fired.
30308        * @param {Array} p_aArgs Array of arguments sent when the event 
30309        * was fired.
30310        * @param {Overlay} p_oOverlay Object representing the overlay that 
30311        * fired the event.
30312        */
30313        _onOverlayDestroy: function (p_sType, p_aArgs, p_oOverlay) {
30314            this.remove(p_oOverlay);
30315        },
30316
30317        /**
30318        * @method _onOverlayFocusHandler
30319        *
30320        * @description focusEvent Handler, used to delegate to _manageFocus with the correct arguments.
30321        *
30322        * @private
30323        * @param {String} p_sType String representing the name of the event  
30324        * that was fired.
30325        * @param {Array} p_aArgs Array of arguments sent when the event 
30326        * was fired.
30327        * @param {Overlay} p_oOverlay Object representing the overlay that 
30328        * fired the event.
30329        */
30330        _onOverlayFocusHandler: function(p_sType, p_aArgs, p_oOverlay) {
30331            this._manageFocus(p_oOverlay);
30332        },
30333
30334        /**
30335        * @method _onOverlayBlurHandler
30336        * @description blurEvent Handler, used to delegate to _manageBlur with the correct arguments.
30337        *
30338        * @private
30339        * @param {String} p_sType String representing the name of the event  
30340        * that was fired.
30341        * @param {Array} p_aArgs Array of arguments sent when the event 
30342        * was fired.
30343        * @param {Overlay} p_oOverlay Object representing the overlay that 
30344        * fired the event.
30345        */
30346        _onOverlayBlurHandler: function(p_sType, p_aArgs, p_oOverlay) {
30347            this._manageBlur(p_oOverlay);
30348        },
30349
30350        /**
30351         * Subscribes to the Overlay based instance focusEvent, to allow the OverlayManager to
30352         * monitor focus state.
30353         * 
30354         * If the instance already has a focusEvent (e.g. Menu), OverlayManager will subscribe 
30355         * to the existing focusEvent, however if a focusEvent or focus method does not exist
30356         * on the instance, the _bindFocus method will add them, and the focus method will 
30357         * update the OverlayManager's state directly.
30358         * 
30359         * @method _bindFocus
30360         * @param {Overlay}
30360 overlay The overlay for which focus needs to be managed
30361         * @protected
30362         */
30363        _bindFocus : function(overlay) {
30364            var mgr = this;
30365
30366            if (!overlay.focusEvent) {
30367                overlay.focusEvent = overlay.createEvent("focus");
30368                overlay.focusEvent.signature = CustomEvent.LIST;
30369                overlay.focusEvent._managed = true;
30370            } else {
30371                overlay.focusEvent.subscribe(mgr._onOverlayFocusHandler, overlay, mgr);
30372            }
30373
30374            if (!overlay.focus) {
30375                Event.on(overlay.element, mgr.cfg.getProperty("focusevent"), mgr._onOverlayElementFocus, null, overlay);
30376                overlay.focus = function () {
30377                    if (mgr._manageFocus(this)) {
30378                        // For Panel/Dialog
30379                        if (this.cfg.getProperty("visible") && this.focusFirst) {
30380                            this.focusFirst();
30381                        }
30382                        this.focusEvent.fire();
30383                    }
30384                };
30385                overlay.focus._managed = true;
30386            }
30387        },
30388
30389        /**
30390         * Subscribes to the Overlay based instance's blurEvent to allow the OverlayManager to
30391         * monitor blur state.
30392         *
30393         * If the instance already has a blurEvent (e.g. Menu), OverlayManager will subscribe 
30394         * to the existing blurEvent, however if a blurEvent or blur method does not exist
30395         * on the instance, the _bindBlur method will add them, and the blur method 
30396         * update the OverlayManager's state directly.
30397         *
30398         * @method _bindBlur
30399         * @param {Overlay} overlay The overlay for which blur needs to be managed
30400         * @protected
30401         */
30402        _bindBlur : function(overlay) {
30403            var mgr = this;
30404
30405            if (!overlay.blurEvent) {
30406                overlay.blurEvent = overlay.createEvent("blur");
30407                overlay.blurEvent.signature = CustomEvent.LIST;
30408                overlay.focusEvent._managed = true;
30409            } else {
30410                overlay.blurEvent.subscribe(mgr._onOverlayBlurHandler, overlay, mgr);
30411            }
30412
30413            if (!overlay.blur) {
30414                overlay.blur = function () {
30415                    if (mgr._manageBlur(this)) {
30416                        this.blurEvent.fire();
30417                    }
30418                };
30419                overlay.blur._managed = true;
30420            }
30421
30422            overlay.hideEvent.subscribe(overlay.blur);
30423        },
30424
30425        /**
30426         * Subscribes to the Overlay based instance's destroyEvent, to allow the Overlay
30427         * to be removed for the OverlayManager when destroyed.
30428         * 
30429         * @method _bindDestroy
30430         * @param {Overlay} overlay The overlay instance being managed
30431         * @protected
30432         */
30433        _bindDestroy : function(overlay) {
30434            var mgr = this;
30435            overlay.destroyEvent.subscribe(mgr._onOverlayDestroy, overlay, mgr);
30436        },
30437
30438        /**
30439         * Ensures the zIndex configuration property on the managed overlay based instance
30440         * is set to the computed zIndex value from the DOM (with "auto" translating to 0).
30441         *
30442         * @method _syncZIndex
30443         * @param {Overlay} overlay The overlay instance being managed
30444         * @protected
30445         */
30446        _syncZIndex : function(overlay) {
30447            var zIndex = Dom.getStyle(overlay.element, "zIndex");
30448            if (!isNaN(zIndex)) {
30449                overlay.cfg.setProperty("zIndex", parseInt(zIndex, 10));
30450            } else {
30451                overlay.cfg.setProperty("zIndex", 0);
30452            }
30453        },
30454
30455        /**
30456        * Registers an Overlay or an array of Overlays with the manager. Upon 
30457        * registration, the Overlay receives functions for focus and blur, 
30458        * along with CustomEvents for each.
30459        *
30460        * @method register
30461        * @param {Overlay} overlay  An Overlay to register with the manager.
30462        * @param {Overlay[]} overlay  An array of Overlays to register with 
30463        * the manager.
30464        * @return {boolean} true if any Overlays are registered.
30465        */
30466        register: function (overlay) {
30467
30468            var registered = false,
30469                i,
30470                n;
30471
30472            if (overlay instanceof Overlay) {
30473
30474                overlay.cfg.addProperty("manager", { value: this } );
30475
30476                this._bindFocus(overlay);
30477                this._bindBlur(overlay);
30478                this._bindDestroy(overlay);
30479                this._syncZIndex(overlay);
30480
30481                this.overlays.push(overlay);
30482                this.bringToTop(overlay);
30483
30484                registered = true;
30485
30486            } else if (overlay instanceof Array) {
30487
30488                for (i = 0, n = overlay.length; i < n; i++) {
30489                    registered = this.register(overlay[i]) || registered;
30490                }
30491
30492            }
30493
30494            return registered;
30495        },
30496
30497        /**
30498        * Places the specified Overlay instance on top of all other 
30499        * Overlay instances.
30500        * @method bringToTop
30501        * @param {YAHOO.widget.Overlay} p_oOverlay Object representing an 
30502        * Overlay instance.
30503        * @param {String} p_oOverlay String representing the id of an 
30504        * Overlay instance.
30505        */        
30506        bringToTop: function (p_oOverlay) {
30507
30508            var oOverlay = this.find(p_oOverlay),
30509                nTopZIndex,
30510                oTopOverlay,
30511                aOverlays;
30512
30513            if (oOverlay) {
30514
30515                aOverlays = this.overlays;
30516                aOverlays.sort(this.compareZIndexDesc);
30517
30518                oTopOverlay = aOverlays[0];
30519
30520                if (oTopOverlay) {
30521                    nTopZIndex = Dom.getStyle(oTopOverlay.element, "zIndex");
30522
30523                    if (!isNaN(nTopZIndex)) {
30524
30525                        var bRequiresBump = false;
30526
30527                        if (oTopOverlay !== oOverlay) {
30528                            bRequiresBump = true;
30529                        } else if (aOverlays.length > 1) {
30530                            var nNextZIndex = Dom.getStyle(aOverlays[1].element, "zIndex");
30531                            // Don't rely on DOM order to stack if 2 overlays are at the same zindex.
30532                            if (!isNaN(nNextZIndex) && (nTopZIndex == nNextZIndex)) {
30533                                bRequiresBump = true;
30534                            }
30535                        }
30536
30537                        if (bRequiresBump) {
30538                            oOverlay.cfg.setProperty("zindex", (parseInt(nTopZIndex, 10) + 2));
30539                        }
30540                    }
30541                    aOverlays.sort(this.compareZIndexDesc);
30542                }
30543            }
30544        },
30545
30546        /**
30547        * Attempts to locate an Overlay by instance or ID.
30548        * @method find
30549        * @param {Overlay} overlay  An Overlay to locate within the manager
30550        * @param {String} overlay  An Overlay id to locate within the manager
30551        * @return {Overlay} The requested Overlay, if found, or null if it 
30552        * cannot be located.
30553        */
30554        find: function (overlay) {
30555
30556            var isInstance = overlay instanceof Overlay,
30557                overlays = this.overlays,
30558                n = overlays.length,
30559                found = null,
30560                o,
30561                i;
30562
30563            if (isInstance || typeof overlay == "string") {
30564                for (i = n-1; i >= 0; i--) {
30565                    o = overlays[i];
30566                    if ((isInstance && (o === overlay)) || (o.id == overlay)) {
30567                        found = o;
30568                        break;
30569                    }
30570                }
30571            }
30572
30573            return found;
30574        },
30575
30576        /**
30577        * Used for sorting the manager's Overlays by z-index.
30578        * @method compareZIndexDesc
30579        * @private
30580        * @return {Number} 0, 1, or -1, depending on where the Overlay should 
30581        * fall in the stacking order.
30582        */
30583        compareZIndexDesc: function (o1, o2) {
30584
30585            var zIndex1 = (o1.cfg) ? o1.cfg.getProperty("zIndex") : null, // Sort invalid (destroyed)
30586                zIndex2 = (o2.cfg) ? o2.cfg.getProperty("zIndex") : null; // objects at bottom.
30587
30588            if (zIndex1 === null && zIndex2 === null) {
30589                return 0;
30590            } else if (zIndex1 === null){
30591                return 1;
30592            } else if (zIndex2 === null) {
30593                return -1;
30594            } else if (zIndex1 > zIndex2) {
30595                return -1;
30596            } else if (zIndex1 < zIndex2) {
30597                return 1;
30598            } else {
30599                return 0;
30600            }
30601        },
30602
30603        /**
30604        * Shows all Overlays in the manager.
30605        * @method showAll
30606        */
30607        showAll: function () {
30608            var overlays = this.overlays,
30609                n = overlays.length,
30610                i;
30611
30612            for (i = n - 1; i >= 0; i--) {
30613                overlays[i].show();
30614            }
30615        },
30616
30617        /**
30618        * Hides all Overlays in the manager.
30619        * @method hideAll
30620        */
30621        hideAll: function () {
30622            var overlays = this.overlays,
30623                n = overlays.length,
30624                i;
30625
30626            for (i = n - 1; i >= 0; i--) {
30627                overlays[i].hide();
30628            }
30629        },
30630
30631        /**
30632        * Returns a string representation of the object.
30633        * @method toString
30634        * @return {String} The string representation of the OverlayManager
30635        */
30636        toString: function () {
30637            return "OverlayManager";
30638        }
30639    };
30640}());
30641(function () {
30642
30643    /**
30644    * Tooltip is an implementation of Overlay that behaves like an OS tooltip, 
30645    * displaying when the user mouses over a particular element, and 
30646    * disappearing on mouse out.
30647    * @namespace YAHOO.widget
30648    * @class Tooltip
30649    * @extends YAHOO.widget.Overlay
30650    * @constructor
30651    * @param {String} el The element ID representing the Tooltip <em>OR</em>
30652    * @param {HTMLElement} el The element representing the Tooltip
30653    * @param {Object} userConfig The configuration object literal containing 
30654    * the configuration that should be set for this Overlay. See configuration 
30655    * documentation for more details.
30656    */
30657    YAHOO.widget.Tooltip = function (el, userConfig) {
30658        YAHOO.widget.Tooltip.superclass.constructor.call(this, el, userConfig);
30659    };
30660
30661    var Lang = YAHOO.lang,
30662        Event = YAHOO.util.Event,
30663        CustomEvent = YAHOO.util.CustomEvent,
30664        Dom = YAHOO.util.Dom,
30665        Tooltip = YAHOO.widget.Tooltip,
30666        UA = YAHOO.env.ua,
30667        bIEQuirks = (UA.ie && (UA.ie <= 6 || document.compatMode == "BackCompat")),
30668
30669        m_oShadowTemplate,
30670
30671        /**
30672        * Constant representing the Tooltip's configuration properties
30673        * @property DEFAULT_CONFIG
30674        * @private
30675        * @final
30676        * @type Object
30677        */
30678        DEFAULT_CONFIG = {
30679
30680            "PREVENT_OVERLAP": { 
30681                key: "preventoverlap", 
30682                value: true, 
30683                validator: Lang.isBoolean, 
30684                supercedes: ["x", "y", "xy"] 
30685            },
30686
30687            "SHOW_DELAY": { 
30688                key: "showdelay", 
30689                value: 200, 
30690                validator: Lang.isNumber 
30691            }, 
30692
30693            "AUTO_DISMISS_DELAY": { 
30694                key: "autodismissdelay", 
30695                value: 5000, 
30696                validator: Lang.isNumber 
30697            }, 
30698
30699            "HIDE_DELAY": { 
30700                key: "hidedelay", 
30701                value: 250, 
30702                validator: Lang.isNumber 
30703            }, 
30704
30705            "TEXT": { 
30706                key: "text", 
30707                suppressEvent: true 
30708            }, 
30709
30710            "CONTAINER": { 
30711                key: "container"
30712            },
30713
30714            "DISABLED": {
30715                key: "disabled",
30716                value: false,
30717                suppressEvent: true
30718            },
30719
30720            "XY_OFFSET": {
30721                key: "xyoffset",
30722                value: [0, 25],
30723                suppressEvent: true
30724            }
30725        },
30726
30727        /**
30728        * Constant representing the name of the Tooltip's events
30729        * @property EVENT_TYPES
30730        * @private
30731        * @final
30732        * @type Object
30733        */
30734        EVENT_TYPES = {
30735            "CONTEXT_MOUSE_OVER": "contextMouseOver",
30736            "CONTEXT_MOUSE_OUT": "contextMouseOut",
30737            "CONTEXT_TRIGGER": "contextTrigger"
30738        };
30739
30740    /**
30741    * Constant representing the Tooltip CSS class
30742    * @property YAHOO.widget.Tooltip.CSS_TOOLTIP
30743    * @static
30744    * @final
30745    * @type String
30746    */
30747    Tooltip.CSS_TOOLTIP = "yui-tt";
30748
30749    function restoreOriginalWidth(sOriginalWidth, sForcedWidth) {
30750
30751        var oConfig = this.cfg,
30752            sCurrentWidth = oConfig.getProperty("width");
30753
30754        if (sCurrentWidth == sForcedWidth) {
30755            oConfig.setProperty("width", sOriginalWidth);
30756        }
30757    }
30758
30759    /* 
30760        changeContent event handler that sets a Tooltip instance's "width"
30761        configuration property to the value of its root HTML 
30762        elements's offsetWidth if a specific width has not been set.
30763    */
30764
30765    function setWidthToOffsetWidth(p_sType, p_aArgs) {
30766
30767        if ("_originalWidth" in this) {
30768            restoreOriginalWidth.call(this, this._originalWidth, this._forcedWidth);
30769        }
30770
30771        var oBody = document.body,
30772            oConfig = this.cfg,
30773            sOriginalWidth = oConfig.getProperty("width"),
30774            sNewWidth,
30775            oClone;
30776
30777        if ((!sOriginalWidth || sOriginalWidth == "auto") && 
30778            (oConfig.getProperty("container") != oBody || 
30779            oConfig.getProperty("x") >= Dom.getViewportWidth() || 
30780            oConfig.getProperty("y") >= Dom.getViewportHeight())) {
30781
30782            oClone = this.element.cloneNode(true);
30783            oClone.style.visibility = "hidden";
30784            oClone.style.top = "0px";
30785            oClone.style.left = "0px";
30786
30787            oBody.appendChild(oClone);
30788
30789            sNewWidth = (oClone.offsetWidth + "px");
30790
30791            oBody.removeChild(oClone);
30792            oClone = null;
30793
30794            oConfig.setProperty("width", sNewWidth);
30795            oConfig.refireEvent("xy");
30796
30797            this._originalWidth = sOriginalWidth || "";
30798            this._forcedWidth = sNewWidth;
30799        }
30800    }
30801
30802    // "onDOMReady" that renders the ToolTip
30803
30804    function onDOMReady(p_sType, p_aArgs, p_oObject) {
30805        this.render(p_oObject);
30806    }
30807
30808    //  "init" event handler that automatically renders the Tooltip
30809
30810    function onInit() {
30811        Event.onDOMReady(onDOMReady, this.cfg.getProperty("container"), this);
30812    }
30813
30814    YAHOO.extend(Tooltip, YAHOO.widget.Overlay, { 
30815
30816        /**
30817        * The Tooltip initialization method. This method is automatically 
30818        * called by the constructor. A Tooltip is automatically rendered by 
30819        * the init method, and it also is set to be invisible by default, 
30820        * and constrained to viewport by default as well.
30821        * @method init
30822        * @param {String} el The element ID representing the Tooltip <em>OR</em>
30823        * @param {HTMLElement} el The element representing the Tooltip
30824        * @param {Object} userConfig The configuration object literal 
30825        * containing the configuration that should be set for this Tooltip. 
30826        * See configuration documentation for more details.
30827        */
30828        init: function (el, userConfig) {
30829
30830            this.logger = new YAHOO.widget.LogWriter(this.toString());
30831
30832            Tooltip.superclass.init.call(this, el);
30833
30834            this.beforeInitEvent.fire(Tooltip);
30835
30836            Dom.addClass(this.element, Tooltip.CSS_TOOLTIP);
30837
30838            if (userConfig) {
30839                this.cfg.applyConfig(userConfig, true);
30840            }
30841
30842            this.cfg.queueProperty("visible", false);
30843            this.cfg.queueProperty("constraintoviewport", true);
30844
30845            this.setBody("");
30846
30847            this.subscribe("changeContent", setWidthToOffsetWidth);
30848            this.subscribe("init", onInit);
30849            this.subscribe("render", this.onRender);
30850
30851            this.initEvent.fire(Tooltip);
30852        },
30853
30854        /**
30855        * Initializes the custom events for Tooltip
30856        * @method initEvents
30857        */
30858        initEvents: function () {
30859
30860            Tooltip.superclass.initEvents.call(this);
30861            var SIGNATURE = CustomEvent.LIST;
30862
30863            /**
30864            * CustomEvent fired when user mouses over a context element. Returning false from
30865            * a subscriber to this event will prevent the tooltip from being displayed for
30866            * the current context element.
30867            * 
30868            * @event contextMouseOverEvent
30869            * @param {HTMLElement} context The context element which the user just moused over
30870            * @param {DOMEvent} e The DOM event object, associated with the mouse over
30871            */
30872            this.contextMouseOverEvent = this.createEvent(EVENT_TYPES.CONTEXT_MOUSE_OVER);
30873            this.contextMouseOverEvent.signature = SIGNATURE;
30874
30875            /**
30876            * CustomEvent fired when the user mouses out of a context element.
30877            * 
30878            * @event contextMouseOutEvent
30879            * @param {HTMLElement} context The context element which the user just moused out of
30880            * @param {DOMEvent} e The DOM event object, associated with the mouse out
30881            */
30882            this.contextMouseOutEvent = this.createEvent(EVENT_TYPES.CONTEXT_MOUSE_OUT);
30883            this.contextMouseOutEvent.signature = SIGNATURE;
30884
30885            /**
30886            * CustomEvent fired just before the tooltip is displayed for the current context.
30887            * <p>
30888            *  You can subscribe to this event if you need to set up the text for the 
30889            *  tooltip based on the context element for which it is about to be displayed.
30890            * </p>
30891            * <p>This event differs from the beforeShow event in following respects:</p>
30892            * <ol>
30893            *   <li>
30894            *    When moving from one context element to another, if the tooltip is not
30895            *    hidden (the <code>hidedelay</code> is not reached), the beforeShow and Show events will not
30896            *    be fired when the tooltip is displayed for the new context since it is already visible.
30897            *    However the contextTrigger event is always fired before displaying the tooltip for
30898            *    a new context.
30899            *   </li>
30900            *   <li>
30901            *    The trigger event provides access to the context element, allowing you to 
30902            *    set the text of the tooltip based on context element for which the tooltip is
30903            *    triggered.
30904            *   </li>
30905            * </ol>
30906            * <p>
30907            *  It is not possible to prevent the tooltip from being displayed
30908            *  using this event. You can use the contextMouseOverEvent if you need to prevent
30909            *  the tooltip from being displayed.
30910            * </p>
30911            * @event contextTriggerEvent
30912            * @param {HTMLElement} context The context element for which the tooltip is triggered
30913            */
30914            this.contextTriggerEvent = this.createEvent(EVENT_TYPES.CONTEXT_TRIGGER);
30915            this.contextTriggerEvent.signature = SIGNATURE;
30916        },
30917
30918        /**
30919        * Initializes the class's configurable properties which can be 
30920        * changed using the Overlay's Config object (cfg).
30921        * @method initDefaultConfig
30922        */
30923        initDefaultConfig: function () {
30924
30925            Tooltip.superclass.initDefaultConfig.call(this);
30926
30927            /**
30928            * Specifies whether the Tooltip should be kept from overlapping 
30929            * its context element.
30930            * @config preventoverlap
30931            * @type Boolean
30932            * @default true
30933            */
30934            this.cfg.addProperty(DEFAULT_CONFIG.PREVENT_OVERLAP.key, {
30935                value: DEFAULT_CONFIG.PREVENT_OVERLAP.value, 
30936                validator: DEFAULT_CONFIG.PREVENT_OVERLAP.validator, 
30937                supercedes: DEFAULT_CONFIG.PREVENT_OVERLAP.supercedes
30938            });
30939
30940            /**
30941            * The number of milliseconds to wait before showing a Tooltip 
30942            * on mouseover.
30943            * @config showdelay
30944            * @type Number
30945            * @default 200
30946            */
30947            this.cfg.addProperty(DEFAULT_CONFIG.SHOW_DELAY.key, {
30948                handler: this.configShowDelay,
30949                value: 200, 
30950                validator: DEFAULT_CONFIG.SHOW_DELAY.validator
30951            });
30952
30953            /**
30954            * The number of milliseconds to wait before automatically 
30955            * dismissing a Tooltip after the mouse has been resting on the 
30956            * context element.
30957            * @config autodismissdelay
30958            * @type Number
30959            * @default 5000
30960            */
30961            this.cfg.addProperty(DEFAULT_CONFIG.AUTO_DISMISS_DELAY.key, {
30962                handler: this.configAutoDismissDelay,
30963                value: DEFAULT_CONFIG.AUTO_DISMISS_DELAY.value,
30964                validator: DEFAULT_CONFIG.AUTO_DISMISS_DELAY.validator
30965            });
30966
30967            /**
30968            * The number of milliseconds to wait before hiding a Tooltip 
30969            * after mouseout.
30970            * @config hidedelay
30971            * @type Number
30972            * @default 250
30973            */
30974            this.cfg.addProperty(DEFAULT_CONFIG.HIDE_DELAY.key, {
30975                handler: this.configHideDelay,
30976                value: DEFAULT_CONFIG.HIDE_DELAY.value, 
30977                validator: DEFAULT_CONFIG.HIDE_DELAY.validator
30978            });
30979
30980            /**
30981            * Specifies the Tooltip's text. The text is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source. 
30982            * @config text
30983            * @type HTML
30984            * @default null
30985            */
30986            this.cfg.addProperty(DEFAULT_CONFIG.TEXT.key, {
30987                handler: this.configText,
30988                suppressEvent: DEFAULT_CONFIG.TEXT.suppressEvent
30989            });
30990
30991            /**
30992            * Specifies the container element that the Tooltip's markup 
30993            * should be rendered into.
30994            * @config container
30995            * @type HTMLElement/String
30996            * @default document.body
30997            */
30998            this.cfg.addProperty(DEFAULT_CONFIG.CONTAINER.key, {
30999                handler: this.configContainer,
31000                value: document.body
31001            });
31002
31003            /**
31004            * Specifies whether or not the tooltip is disabled. Disabled tooltips
31005            * will not be displayed. If the tooltip is driven by the title attribute
31006            * of the context element, the title attribute will still be removed for 
31007            * disabled tooltips, to prevent default tooltip behavior.
31008            * 
31009            * @config disabled
31010            * @type Boolean
31011            * @default false
31012            */
31013            this.cfg.addProperty(DEFAULT_CONFIG.DISABLED.key, {
31014                handler: this.configContainer,
31015                value: DEFAULT_CONFIG.DISABLED.value,
31016                supressEvent: DEFAULT_CONFIG.DISABLED.suppressEvent
31017            });
31018
31019            /**
31020            * Specifies the XY offset from the mouse position, where the tooltip should be displayed, specified
31021            * as a 2 element array (e.g. [10, 20]); 
31022            *
31023            * @config xyoffset
31024            * @type Array
31025            * @default [0, 25]
31026            */
31027            this.cfg.addProperty(DEFAULT_CONFIG.XY_OFFSET.key, {
31028                value: DEFAULT_CONFIG.XY_OFFSET.value.concat(),
31029                supressEvent: DEFAULT_CONFIG.XY_OFFSET.suppressEvent 
31030            });
31031
31032            /**
31033            * Specifies the element or elements that the Tooltip should be 
31034            * anchored to on mouseover.
31035            * @config context
31036            * @type HTMLElement[]/String[]
31037            * @default null
31038            */ 
31039
31040            /**
31041            * String representing the width of the Tooltip.  <em>Please note:
31042            * </em> As of version 2.3 if either no value or a value of "auto" 
31043            * is specified, and the Toolip's "container" configuration property
31044            * is set to something other than <code>document.body</code> or 
31045            * its "context" element resides outside the immediately visible 
31046            * portion of the document, the width of the Tooltip will be 
31047            * calculated based on the offsetWidth of its root HTML and set just 
31048            * before it is made visible.  The original value will be 
31049            * restored when the Tooltip is hidden. This ensures the Tooltip is 
31050            * rendered at a usable width.  For more information see 
31051            * YUILibrary bug #1685496 and YUILibrary 
31052            * bug #1735423.
31053            * @config width
31054            * @type String
31055            * @default null
31056            */
31057        
31058        },
31059        
31060        // BEGIN BUILT-IN PROPERTY EVENT HANDLERS //
31061        
31062        /**
31063        * The default event handler fired when the "text" property is changed.
31064        * @method configText
31065        * @param {String} type The CustomEvent type (usually the property name)
31066        * @param {Object[]} args The CustomEvent arguments. For configuration 
31067        * handlers, args[0] will equal the newly applied value for the property.
31068        * @param {Object} obj The scope object. For configuration handlers, 
31069        * this will usually equal the owner.
31070        */
31071        configText: function (type, args, obj) {
31072            var text = args[0];
31073            if (text) {
31074                this.setBody(text);
31075            }
31076        },
31077        
31078        /**
31079        * The default event handler fired when the "container" property 
31080        * is changed.
31081        * @method configContainer
31082        * @param {String} type The CustomEvent type (usually the property name)
31083        * @param {Object[]} args The CustomEvent arguments. For 
31084        * configuration handlers, args[0] will equal the newly applied value 
31085        * for the property.
31086        * @param {Object} obj The scope object. For configuration handlers,
31087        * this will usually equal the owner.
31088        */
31089        configContainer: function (type, args, obj) {
31090            var container = args[0];
31091
31092            if (typeof container == 'string') {
31093                this.cfg.setProperty("container", document.getElementById(container), true);
31094            }
31095        },
31096        
31097        /**
31098        * @method _removeEventListeners
31099        * @description Removes all of the DOM event handlers from the HTML
31100        *  element(s) that trigger the display of the tooltip.
31101        * @protected
31102        */
31103        _removeEventListeners: function () {
31104        
31105            var aElements = this._context,
31106                nElements,
31107                oElement,
31108                i;
31109
31110            if (aElements) {
31111                nElements = aElements.length;
31112                if (nElements > 0) {
31113                    i = nElements - 1;
31114                    do {
31115                        oElement = aElements[i];
31116                        Event.removeListener(oElement, "mouseover", this.onContextMouseOver);
31117                        Event.removeListener(oElement, "mousemove", this.onContextMouseMove);
31118                        Event.removeListener(oElement, "mouseout", this.onContextMouseOut);
31119                    }
31120                    while (i--);
31121                }
31122            }
31123        },
31124        
31125        /**
31126        * The default event handler fired when the "context" property 
31127        * is changed.
31128        * @method configContext
31129        * @param {String} type The CustomEvent type (usually the property name)
31130        * @param {Object[]} args The CustomEvent arguments. For configuration 
31131        * handlers, args[0] will equal the newly applied value for the property.
31132        * @param {Object} obj The scope object. For configuration handlers,
31133        * this will usually equal the owner.
31134        */
31135        configContext: function (type, args, obj) {
31136
31137            var context = args[0],
31138                aElements,
31139                nElements,
31140                oElement,
31141                i;
31142
31143            if (context) {
31144
31145                // Normalize parameter into an array
31146                if (! (context instanceof Array)) {
31147                    if (typeof context == "string") {
31148                        this.cfg.setProperty("context", [document.getElementById(context)], true);
31149                    } else { // Assuming this is an element
31150                        this.cfg.setProperty("context", [context], true);
31151                    }
31152                    context = this.cfg.getProperty("context");
31153                }
31154
31155                // Remove any existing mouseover/mouseout listeners
31156                this._removeEventListeners();
31157
31158                // Add mouseover/mouseout listeners to context elements
31159                this._context = context;
31160
31161                aElements = this._context;
31162
31163                if (aElements) {
31164                    nElements = aElements.length;
31165                    if (nElements > 0) {
31166                        i = nElements - 1;
31167                        do {
31168                            oElement = aElements[i];
31169                            Event.on(oElement, "mouseover", this.onContextMouseOver, this);
31170                            Event.on(oElement, "mousemove", this.onContextMouseMove, this);
31171                            Event.on(oElement, "mouseout", this.onContextMouseOut, this);
31172                        }
31173                        while (i--);
31174                    }
31175                }
31176            }
31177        },
31178
31179        // END BUILT-IN PROPERTY EVENT HANDLERS //
31180
31181        // BEGIN BUILT-IN DOM EVENT HANDLERS //
31182
31183        /**
31184        * The default event handler fired when the user moves the mouse while 
31185        * over the context element.
31186        * @method onContextMouseMove
31187        * @param {DOMEvent} e The current DOM event
31188        * @param {Object} obj The object argument
31189        */
31190        onContextMouseMove: function (e, obj) {
31191            obj.pageX = Event.getPageX(e);
31192            obj.pageY = Event.getPageY(e);
31193        },
31194
31195        /**
31196        * The default event handler fired when the user mouses over the 
31197        * context element.
31198        * @method onContextMouseOver
31199        * @param {DOMEvent} e The current DOM event
31200        * @param {Object} obj The object argument
31201        */
31202        onContextMouseOver: function (e, obj) {
31203            var context = this;
31204
31205            if (context.title) {
31206                obj._tempTitle = context.title;
31207                context.title = "";
31208            }
31209
31210            // Fire first, to honor disabled set in the listner
31211            if (obj.fireEvent("contextMouseOver", context, e) !== false && !
31211obj.cfg.getProperty("disabled")) {
31212
31213                // Stop the tooltip from being hidden (set on last mouseout)
31214                if (obj.hideProcId) {
31215                    clearTimeout(obj.hideProcId);
31216                    obj.logger.log("Clearing hide timer: " + obj.hideProcId, "time");
31217                    obj.hideProcId = null;
31218                }
31219
31220                Event.on(context, "mousemove", obj.onContextMouseMove, obj);
31221
31222                /**
31223                * The unique process ID associated with the thread responsible 
31224                * for showing the Tooltip.
31225                * @type int
31226                */
31227                obj.showProcId = obj.doShow(e, context);
31228                obj.logger.log("Setting show tooltip timeout: " + obj.showProcId, "time");
31229            }
31230        },
31231
31232        /**
31233        * The default event handler fired when the user mouses out of 
31234        * the context element.
31235        * @method onContextMouseOut
31236        * @param {DOMEvent} e The current DOM event
31237        * @param {Object} obj The object argument
31238        */
31239        onContextMouseOut: function (e, obj) {
31240            var el = this;
31241
31242            if (obj._tempTitle) {
31243                el.title = obj._tempTitle;
31244                obj._tempTitle = null;
31245            }
31246
31247            if (obj.showProcId) {
31248                clearTimeout(obj.showProcId);
31249                obj.logger.log("Clearing show timer: " + obj.showProcId, "time");
31250                obj.showProcId = null;
31251            }
31252
31253            if (obj.hideProcId) {
31254                clearTimeout(obj.hideProcId);
31255                obj.logger.log("Clearing hide timer: " + obj.hideProcId, "time");
31256                obj.hideProcId = null;
31257            }
31258
31259            obj.fireEvent("contextMouseOut", el, e);
31260
31261            obj.hideProcId = setTimeout(function () {
31262                obj.hide();
31263            }, obj.cfg.getProperty("hidedelay"));
31264        },
31265
31266        // END BUILT-IN DOM EVENT HANDLERS //
31267
31268        /**
31269        * Processes the showing of the Tooltip by setting the timeout delay 
31270        * and offset of the Tooltip.
31271        * @method doShow
31272        * @param {DOMEvent} e The current DOM event
31273        * @param {HTMLElement} context The current context element
31274        * @return {Number} The process ID of the timeout function associated 
31275        * with doShow
31276        */
31277        doShow: function (e, context) {
31278
31279            var offset = this.cfg.getProperty("xyoffset"),
31280                xOffset = offset[0],
31281                yOffset = offset[1],
31282                me = this;
31283
31284            if (UA.opera && context.tagName && 
31285                context.tagName.toUpperCase() == "A") {
31286                yOffset += 12;
31287            }
31288
31289            return setTimeout(function () {
31290
31291                var txt = me.cfg.getProperty("text");
31292
31293                // title does not over-ride text
31294                if (me._tempTitle && (txt === "" || YAHOO.lang.isUndefined(txt) || YAHOO.lang.isNull(txt))) {
31295                    me.setBody(me._tempTitle);
31296                } else {
31297                    me.cfg.refireEvent("text");
31298                }
31299
31300                me.logger.log("Show tooltip", "time");
31301                me.moveTo(me.pageX + xOffset, me.pageY + yOffset);
31302
31303                if (me.cfg.getProperty("preventoverlap")) {
31304                    me.preventOverlap(me.pageX, me.pageY);
31305                }
31306
31307                Event.removeListener(context, "mousemove", me.onContextMouseMove);
31308
31309                me.contextTriggerEvent.fire(context);
31310
31311                me.show();
31312
31313                me.hideProcId = me.doHide();
31314                me.logger.log("Hide tooltip time active: " + me.hideProcId, "time");
31315
31316            }, this.cfg.getProperty("showdelay"));
31317        },
31318
31319        /**
31320        * Sets the timeout for the auto-dismiss delay, which by default is 5 
31321        * seconds, meaning that a tooltip will automatically dismiss itself 
31322        * after 5 seconds of being displayed.
31323        * @method doHide
31324        */
31325        doHide: function () {
31326
31327            var me = this;
31328
31329            me.logger.log("Setting hide tooltip timeout", "time");
31330
31331            return setTimeout(function () {
31332
31333                me.logger.log("Hide tooltip", "time");
31334                me.hide();
31335
31336            }, this.cfg.getProperty("autodismissdelay"));
31337
31338        },
31339
31340        /**
31341        * Fired when the Tooltip is moved, this event handler is used to 
31342        * prevent the Tooltip from overlapping with its context element.
31343        * @method preventOverlay
31344        * @param {Number} pageX The x coordinate position of the mouse pointer
31345        * @param {Number} pageY The y coordinate position of the mouse pointer
31346        */
31347        preventOverlap: function (pageX, pageY) {
31348        
31349            var height = this.element.offsetHeight,
31350                mousePoint = new YAHOO.util.Point(pageX, pageY),
31351                elementRegion = Dom.getRegion(this.element);
31352        
31353            elementRegion.top -= 5;
31354            elementRegion.left -= 5;
31355            elementRegion.right += 5;
31356            elementRegion.bottom += 5;
31357        
31358            this.logger.log("context " + elementRegion, "ttip");
31359            this.logger.log("mouse " + mousePoint, "ttip");
31360        
31361            if (elementRegion.contains(mousePoint)) {
31362                this.logger.log("OVERLAP", "warn");
31363                this.cfg.setProperty("y", (pageY - height - 5));
31364            }
31365        },
31366
31367
31368        /**
31369        * @method onRender
31370        * @description "render" event handler for the Tooltip.
31371        * @param {String} p_sType String representing the name of the event  
31372        * that was fired.
31373        * @param {Array} p_aArgs Array of arguments sent when the event 
31374        * was fired.
31375        */
31376        onRender: function (p_sType, p_aArgs) {
31377    
31378            function sizeShadow() {
31379    
31380                var oElement = this.element,
31381                    oShadow = this.underlay;
31382            
31383                if (oShadow) {
31384                    oShadow.style.width = (oElement.offsetWidth + 6) + "px";
31385                    oShadow.style.height = (oElement.offsetHeight + 1) + "px"; 
31386                }
31387            
31388            }
31389
31390            function addShadowVisibleClass() {
31391                Dom.addClass(this.underlay, "yui-tt-shadow-visible");
31392
31393                if (UA.ie) {
31394                    this.forceUnderlayRedraw();
31395                }
31396            }
31397
31398            function removeShadowVisibleClass() {
31399                Dom.removeClass(this.underlay, "yui-tt-shadow-visible");
31400            }
31401
31402            function createShadow() {
31403    
31404                var oShadow = this.underlay,
31405                    oElement,
31406                    Module,
31407                    nIE,
31408                    me;
31409    
31410                if (!oShadow) {
31411    
31412                    oElement = this.element;
31413                    Module = YAHOO.widget.Module;
31414                    nIE = UA.ie;
31415                    me = this;
31416
31417                    if (!m_oShadowTemplate) {
31418                        m_oShadowTemplate = document.createElement("div");
31419                        m_oShadowTemplate.className = "yui-tt-shadow";
31420                    }
31421
31422                    oShadow = m_oShadowTemplate.cloneNode(false);
31423
31424                    oElement.appendChild(oShadow);
31425
31426                    this.underlay = oShadow;
31427
31428                    // Backward compatibility, even though it's probably 
31429                    // intended to be "private", it isn't marked as such in the api docs
31430                    this._shadow = this.underlay;
31431
31432                    addShadowVisibleClass.call(this);
31433
31434                    this.subscribe("beforeShow", addShadowVisibleClass);
31435                    this.subscribe("hide", removeShadowVisibleClass);
31436
31437                    if (bIEQuirks) {
31438                        window.setTimeout(function () { 
31439                            sizeShadow.call(me); 
31440                        }, 0);
31441    
31442                        this.cfg.subscribeToConfigEvent("width", sizeShadow);
31443                        this.cfg.subscribeToConfigEvent("height", sizeShadow);
31444                        this.subscribe("changeContent", sizeShadow);
31445
31446                        Module.textResizeEvent.subscribe(sizeShadow, this, true);
31447                        this.subscribe("destroy", function () {
31448                            Module.textResizeEvent.unsubscribe(sizeShadow, this);
31449                        });
31450                    }
31451                }
31452            }
31453
31454            function onBeforeShow() {
31455                createShadow.call(this);
31456                this.unsubscribe("beforeShow", onBeforeShow);
31457            }
31458
31459            if (this.cfg.getProperty("visible")) {
31460                createShadow.call(this);
31461            } else {
31462                this.subscribe("beforeShow", onBeforeShow);
31463            }
31464        
31465        },
31466
31467        /**
31468         * Forces the underlay element to be repainted, through the application/removal
31469         * of a yui-force-redraw class to the underlay element.
31470         * 
31471         * @method forceUnderlayRedraw
31472         */
31473        forceUnderlayRedraw : function() {
31474            var tt = this;
31475            Dom.addClass(tt.underlay, "yui-force-redraw");
31476            setTimeout(function() {Dom.removeClass(tt.underlay, "yui-force-redraw");}, 0);
31477        },
31478
31479        /**
31480        * Removes the Tooltip element from the DOM and sets all child 
31481        * elements to null.
31482        * @method destroy
31483        */
31484        destroy: function () {
31485        
31486            // Remove any existing mouseover/mouseout listeners
31487            this._removeEventListeners();
31488
31489            Tooltip.superclass.destroy.call(this);  
31490        
31491        },
31492        
31493        /**
31494        * Returns a string representation of the object.
31495        * @method toString
31496        * @return {String} The string representation of the Tooltip
31497        */
31498        toString: function () {
31499            return "Tooltip " + this.id;
31500        }
31501    
31502    });
31503
31504}());
31505(function () {
31506
31507    /**
31508    * Panel is an implementation of Overlay that behaves like an OS window, 
31509    * with a draggable header and an optional close icon at the top right.
31510    * @namespace YAHOO.widget
31511    * @class Panel
31512    * @extends YAHOO.widget.Overlay
31513    * @constructor
31514    * @param {String} el The element ID representing the Panel <em>OR</em>
31515    * @param {HTMLElement} el The element representing the Panel
31516    * @param {Object} userConfig The configuration object literal containing 
31517    * the configuration that should be set for this Panel. See configuration 
31518    * documentation for more details.
31519    */
31520    YAHOO.widget.Panel = function (el, userConfig) {
31521        YAHOO.widget.Panel.superclass.constructor.call(this, el, userConfig);
31522    };
31523
31524    var _currentModal = null;
31525
31526    var Lang = YAHOO.lang,
31527        Util = YAHOO.util,
31528        Dom = Util.Dom,
31529        Event = Util.Event,
31530        CustomEvent = Util.CustomEvent,
31531        KeyListener = YAHOO.util.KeyListener,
31532        Config = Util.Config,
31533        Overlay = YAHOO.widget.Overlay,
31534        Panel = YAHOO.widget.Panel,
31535        UA = YAHOO.env.ua,
31536
31537        bIEQuirks = (UA.ie && (UA.ie <= 6 || document.compatMode == "BackCompat")),
31538
31539        m_oMaskTemplate,
31540        m_oUnderlayTemplate,
31541        m_oCloseIconTemplate,
31542
31543        /**
31544        * Constant representing the name of the Panel's events
31545        * @property EVENT_TYPES
31546        * @private
31547        * @final
31548        * @type Object
31549        */
31550        EVENT_TYPES = {
31551            "BEFORE_SHOW_MASK" : "beforeShowMask",
31552            "BEFORE_HIDE_MASK" : "beforeHideMask",
31553            "SHOW_MASK": "showMask",
31554            "HIDE_MASK": "hideMask",
31555            "DRAG": "drag"
31556        },
31557
31558        /**
31559        * Constant representing the Panel's configuration properties
31560        * @property DEFAULT_CONFIG
31561        * @private
31562        * @final
31563        * @type Object
31564        */
31565        DEFAULT_CONFIG = {
31566
31567            "CLOSE": { 
31568                key: "close", 
31569                value: true, 
31570                validator: Lang.isBoolean, 
31571                supercedes: ["visible"] 
31572            },
31573
31574            "DRAGGABLE": {
31575                key: "draggable", 
31576                value: (Util.DD ? true : false), 
31577                validator: Lang.isBoolean, 
31578                supercedes: ["visible"]  
31579            },
31580
31581            "DRAG_ONLY" : {
31582                key: "dragonly",
31583                value: false,
31584                validator: Lang.isBoolean,
31585                supercedes: ["draggable"]
31586            },
31587
31588            "UNDERLAY": { 
31589                key: "underlay", 
31590                value: "shadow", 
31591                supercedes: ["visible"] 
31592            },
31593
31594            "MODAL": { 
31595                key: "modal", 
31596                value: false, 
31597                validator: Lang.isBoolean, 
31598                supercedes: ["visible", "zindex"]
31599            },
31600
31601            "KEY_LISTENERS": {
31602                key: "keylisteners",
31603                suppressEvent: true,
31604                supercedes: ["visible"]
31605            },
31606
31607            "STRINGS" : {
31608                key: "strings",
31609                supercedes: ["close"],
31610                validator: Lang.isObject,
31611                value: {
31612                    close: "Close"
31613                }
31614            }
31615        };
31616
31617    /**
31618    * Constant representing the default CSS class used for a Panel
31619    * @property YAHOO.widget.Panel.CSS_PANEL
31620    * @static
31621    * @final
31622    * @type String
31623    */
31624    Panel.CSS_PANEL = "yui-panel";
31625    
31626    /**
31627    * Constant representing the default CSS class used for a Panel's 
31628    * wrapping container
31629    * @property YAHOO.widget.Panel.CSS_PANEL_CONTAINER
31630    * @static
31631    * @final
31632    * @type String
31633    */
31634    Panel.CSS_PANEL_CONTAINER = "yui-panel-container";
31635
31636    /**
31637     * Constant representing the default set of focusable elements 
31638     * on the pagewhich Modal Panels will prevent access to, when
31639     * the modal mask is displayed
31640     * 
31641     * @property YAHOO.widget.Panel.FOCUSABLE
31642     * @static
31643     * @type Array
31644     */
31645    Panel.FOCUSABLE = [
31646        "a",
31647        "button",
31648        "select",
31649        "textarea",
31650        "input",
31651        "iframe"
31652    ];
31653
31654    // Private CustomEvent listeners
31655
31656    /* 
31657        "beforeRender" event handler that creates an empty header for a Panel 
31658        instance if its "draggable" configuration property is set to "true" 
31659        and no header has been created.
31660    */
31661
31662    function createHeader(p_sType, p_aArgs) {
31663        if (!this.header && this.cfg.getProperty("draggable")) {
31664            this.setHeader("&#160;");
31665        }
31666    }
31667
31668    /* 
31669        "hide" event handler that sets a Panel instance's "width"
31670        configuration property back to its original value before 
31671        "setWidthToOffsetWidth" was called.
31672    */
31673
31674    function restoreOriginalWidth(p_sType, p_aArgs, p_oObject) {
31675
31676        var sOriginalWidth = p_oObject[0],
31677            sNewWidth = p_oObject[1],
31678            oConfig = this.cfg,
31679            sCurrentWidth = oConfig.getProperty("width");
31680
31681        if (sCurrentWidth == sNewWidth) {
31682            oConfig.setProperty("width", sOriginalWidth);
31683        }
31684
31685        this.unsubscribe("hide", restoreOriginalWidth, p_oObject);
31686    }
31687
31688    /* 
31689        "beforeShow" event handler that sets a Panel instance's "width"
31690        configuration property to the value of its root HTML 
31691        elements's offsetWidth
31692    */
31693
31694    function setWidthToOffsetWidth(p_sType, p_aArgs) {
31695
31696        var oConfig,
31697            sOriginalWidth,
31698            sNewWidth;
31699
31700        if (bIEQuirks) {
31701
31702            oConfig = this.cfg;
31703            sOriginalWidth = oConfig.getProperty("width");
31704            
31705            if (!sOriginalWidth || sOriginalWidth == "auto") {
31706    
31707                sNewWidth = (this.element.offsetWidth + "px");
31708    
31709                oConfig.setProperty("width", sNewWidth);
31710
31711                this.subscribe("hide", restoreOriginalWidth, 
31712                    [(sOriginalWidth || ""), sNewWidth]);
31713            
31714            }
31715        }
31716    }
31717
31718    YAHOO.extend(Panel, Overlay, {
31719
31720        /**
31721        * The Overlay initialization method, which is executed for Overlay and 
31722        * all of its subclasses. This method is automatically called by the 
31723        * constructor, and  sets up all DOM references for pre-existing markup, 
31724        * and creates required markup if it is not already present.
31725        * @method init
31726        * @param {String} el The element ID representing the Overlay <em>OR</em>
31727        * @param {HTMLElement} el The element representing the Overlay
31728        * @param {Object} userConfig The configuration object literal 
31729        * containing the configuration that should be set for this Overlay. 
31730        * See configuration documentation for more details.
31731        */
31732        init: function (el, userConfig) {
31733            /*
31734                 Note that we don't pass the user config in here yet because 
31735                 we only want it executed once, at the lowest subclass level
31736            */
31737
31738            Panel.superclass.init.call(this, el/*, userConfig*/);
31739
31740            this.beforeInitEvent.fire(Panel);
31741
31742            Dom.addClass(this.element, Panel.CSS_PANEL);
31743
31744            this.buildWrapper();
31745
31746            if (userConfig) {
31747                this.cfg.applyConfig(userConfig, true);
31748            }
31749
31750            this.subscribe("showMask", this._addFocusHandlers);
31751            this.subscribe("hideMask", this._removeFocusHandlers);
31752            this.subscribe("beforeRender", createHeader);
31753
31754            this.subscribe("render", function() {
31755                this.setFirstLastFocusable();
31756                this.subscribe("changeContent", this.setFirstLastFocusable);
31757            });
31758
31759            this.subscribe("show", this._focusOnShow);
31760
31761            this.initEvent.fire(Panel);
31762        },
31763
31764        /**
31765         * @method _onElementFocus
31766         * @private
31767         *
31768         * "focus" event handler for a focuable element. Used to automatically
31769         * blur the element when it receives focus to ensure that a Panel
31770         * instance's modality is not compromised.
31771         *
31772         * @param {Event} e The DOM event object
31773         */
31774        _onElementFocus : function(e){
31775
31776            if(_currentModal === this) {
31777
31778                var target = Event.getTarget(e),
31779                    doc = document.documentElement,
31780                    insideDoc = (target !== doc && target !== window);
31781
31782                // mask and documentElement checks added for IE, which focu
31782ses on the mask when it's clicked on, and focuses on 
31783                // the documentElement, when the document scrollbars are clicked on
31784                if (insideDoc && target !== this.element && target !== this.mask && !Dom.isAncestor(this.element, target)) {
31785                    try {
31786                        this._focusFirstModal();
31787                    } catch(err){
31788                        // Just in case we fail to focus
31789                        try {
31790                            if (insideDoc && target !== document.body) {
31791                                target.blur();
31792                            }
31793                        } catch(err2) { }
31794                    }
31795                }
31796            }
31797        },
31798
31799        /**
31800         * Focuses on the first element if present, otherwise falls back to the focus mechanisms used for 
31801         * modality. This method does not try/catch focus failures. The caller is responsible for catching exceptions,
31802         * and taking remedial measures.
31803         * 
31804         * @method _focusFirstModal
31805         */
31806        _focusFirstModal : function() {
31807            var el = this.firstElement;
31808            if (el) {
31809                el.focus();
31810            } else {
31811                if (this._modalFocus) {
31812                    this._modalFocus.focus();
31813                } else {
31814                    this.innerElement.focus();
31815                }
31816            }
31817        },
31818
31819        /** 
31820         *  @method _addFocusHandlers
31821         *  @protected
31822         *  
31823         *  "showMask" event handler that adds a "focus" event handler to all
31824         *  focusable elements in the document to enforce a Panel instance's 
31825         *  modality from being compromised.
31826         *
31827         *  @param p_sType {String} Custom event type
31828         *  @param p_aArgs {Array} Custom event arguments
31829         */
31830        _addFocusHandlers: function(p_sType, p_aArgs) {
31831            if (!this.firstElement) {
31832                if (UA.webkit || UA.opera) {
31833                    if (!this._modalFocus) {
31834                        this._createHiddenFocusElement();
31835                    }
31836                } else {
31837                    this.innerElement.tabIndex = 0;
31838                }
31839            }
31840            this._setTabLoop(this.firstElement, this.lastElement);
31841            Event.onFocus(document.documentElement, this._onElementFocus, this, true);
31842            _currentModal = this;
31843        },
31844
31845        /**
31846         * Creates a hidden focusable element, used to focus on,
31847         * to enforce modality for browsers in which focus cannot
31848         * be applied to the container box.
31849         * 
31850         * @method _createHiddenFocusElement
31851         * @private
31852         */
31853        _createHiddenFocusElement : function() {
31854            var e = document.createElement("button");
31855            e.style.height = "1px";
31856            e.style.width = "1px";
31857            e.style.position = "absolute";
31858            e.style.left = "-10000em";
31859            e.style.opacity = 0;
31860            e.tabIndex = -1;
31861            this.innerElement.appendChild(e);
31862            this._modalFocus = e;
31863        },
31864
31865        /**
31866         *  @method _removeFocusHandlers
31867         *  @protected
31868         *
31869         *  "hideMask" event handler that removes all "focus" event handlers added 
31870         *  by the "addFocusEventHandlers" method.
31871         *
31872         *  @param p_sType {String} Event type
31873         *  @param p_aArgs {Array} Event Arguments
31874         */
31875        _removeFocusHandlers: function(p_sType, p_aArgs) {
31876            Event.removeFocusListener(document.documentElement, this._onElementFocus, this);
31877
31878            if (_currentModal == this) {
31879                _currentModal = null;
31880            }
31881        },
31882
31883        /**
31884         * Focus handler for the show event
31885         *
31886         * @method _focusOnShow
31887         * @param {String} type Event Type
31888         * @param {Array} args Event arguments
31889         * @param {Object} obj Additional data 
31890         */
31891        _focusOnShow : function(type, args, obj) {
31892
31893            if (args && args[1]) {
31894                Event.stopEvent(args[1]);
31895            }
31896
31897            if (!this.focusFirst(type, args, obj)) {
31898                if (this.cfg.getProperty("modal")) {
31899                    this._focusFirstModal();
31900                }
31901            }
31902        },
31903
31904        /**
31905         * Sets focus to the first element in the Panel.
31906         *
31907         * @method focusFirst
31908         * @return {Boolean} true, if successfully focused, false otherwise 
31909         */
31910        focusFirst: function (type, args, obj) {
31911            var el = this.firstElement, focused = false;
31912
31913            if (args && args[1]) {
31914                Event.stopEvent(args[1]);
31915            }
31916
31917            if (el) {
31918                try {
31919                    el.focus();
31920                    focused = true;
31921                } catch(err) {
31922                    // Ignore
31923                }
31924            }
31925
31926            return focused;
31927        },
31928
31929        /**
31930         * Sets focus to the last element in the Panel.
31931         *
31932         * @method focusLast
31933         * @return {Boolean} true, if successfully focused, false otherwise
31934         */
31935        focusLast: function (type, args, obj) {
31936            var el = this.lastElement, focused = false;
31937
31938            if (args && args[1]) {
31939                Event.stopEvent(args[1]);
31940            }
31941
31942            if (el) {
31943                try {
31944                    el.focus();
31945                    focused = true;
31946                } catch(err) {
31947                    // Ignore
31948                }
31949            }
31950
31951            return focused;
31952        },
31953
31954        /**
31955         * Protected internal method for setTabLoop, which can be used by 
31956         * subclasses to jump in and modify the arguments passed in if required.
31957         *
31958         * @method _setTabLoop
31959         * @param {HTMLElement} firstElement
31960         * @param {HTMLElement} lastElement
31961         * @protected
31962         *
31963         */
31964        _setTabLoop : function(firstElement, lastElement) {
31965            this.setTabLoop(firstElement, lastElement);
31966        },
31967
31968        /**
31969         * Sets up a tab, shift-tab loop between the first and last elements
31970         * provided. NOTE: Sets up the preventBackTab and preventTabOut KeyListener
31971         * instance properties, which are reset everytime this method is invoked.
31972         *
31973         * @method setTabLoop
31974         * @param {HTMLElement} firstElement
31975         * @param {HTMLElement} lastElement
31976         *
31977         */
31978        setTabLoop : function(firstElement, lastElement) {
31979
31980            var backTab = this.preventBackTab, tab = this.preventTabOut,
31981                showEvent = this.showEvent, hideEvent = this.hideEvent;
31982
31983            if (backTab) {
31984                backTab.disable();
31985                showEvent.unsubscribe(backTab.enable, backTab);
31986                hideEvent.unsubscribe(backTab.disable, backTab);
31987                backTab = this.preventBackTab = null;
31988            }
31989
31990            if (tab) {
31991                tab.disable();
31992                showEvent.unsubscribe(tab.enable, tab);
31993                hideEvent.unsubscribe(tab.disable,tab);
31994                tab = this.preventTabOut = null;
31995            }
31996
31997            if (firstElement) {
31998                this.preventBackTab = new KeyListener(firstElement, 
31999                    {shift:true, keys:9},
32000                    {fn:this.focusLast, scope:this, correctScope:true}
32001                );
32002                backTab = this.preventBackTab;
32003
32004                showEvent.subscribe(backTab.enable, backTab, true);
32005                hideEvent.subscribe(backTab.disable,backTab, true);
32006            }
32007
32008            if (lastElement) {
32009                this.preventTabOut = new KeyListener(lastElement, 
32010                    {shift:false, keys:9}, 
32011                    {fn:this.focusFirst, scope:this, correctScope:true}
32012                );
32013                tab = this.preventTabOut;
32014
32015                showEvent.subscribe(tab.enable, tab, true);
32016                hideEvent.subscribe(tab.disable,tab, true);
32017            }
32018        },
32019
32020        /**
32021         * Returns an array of the currently focusable items which reside within
32022         * Panel. The set of focusable elements the method looks for are defined
32023         * in the Panel.FOCUSABLE static property
32024         *
32025         * @method getFocusableElements
32026         * @param {HTMLElement} root element to start from.
32027         */
32028        getFocusableElements : function(root) {
32029
32030            root = root || this.innerElement;
32031
32032            var focusable = {}, panel = this;
32033            for (var i = 0; i < Panel.FOCUSABLE.length; i++) {
32034                focusable[Panel.FOCUSABLE[i]] = true;
32035            }
32036
32037            // Not looking by Tag, since we want elements in DOM order
32038            
32039            return Dom.getElementsBy(function(el) { return panel._testIfFocusable(el, focusable); }, null, root);
32040        },
32041
32042        /**
32043         * This is the test method used by getFocusableElements, to determine which elements to 
32044         * include in the focusable elements list. Users may override this to customize behavior.
32045         *
32046         * @method _testIfFocusable
32047         * @param {Object} el The element being tested
32048         * @param {Object} focusable The hash of known focusable elements, created by an array-to-map operation on Panel.FOCUSABLE
32049         * @protected
32050         */
32051        _testIfFocusable: function(el, focusable) {
32052            if (el.focus && el.type !== "hidden" && !el.disabled && focusable[el.tagName.toLowerCase()]) {
32053                return true;
32054            }
32055            return false;
32056        },
32057
32058        /**
32059         * Sets the firstElement and lastElement instance properties
32060         * to the first and last focusable elements in the Panel.
32061         *
32062         * @method setFirstLastFocusable
32063         */
32064        setFirstLastFocusable : function() {
32065
32066            this.firstElement = null;
32067            this.lastElement = null;
32068
32069            var elements = this.getFocusableElements();
32070            this.focusableElements = elements;
32071
32072            if (elements.length > 0) {
32073                this.firstElement = elements[0];
32074                this.lastElement = elements[elements.length - 1];
32075            }
32076
32077            if (this.cfg.getProperty("modal")) {
32078                this._setTabLoop(this.firstElement, this.lastElement);
32079            }
32080        },
32081
32082        /**
32083         * Initializes the custom events for Module which are fired 
32084         * automatically at appropriate times by the Module class.
32085         */
32086        initEvents: function () {
32087            Panel.superclass.initEvents.call(this);
32088
32089            var SIGNATURE = CustomEvent.LIST;
32090
32091            /**
32092            * CustomEvent fired after the modality mask is shown
32093            * @event showMaskEvent
32094            */
32095            this.showMaskEvent = this.createEvent(EVENT_TYPES.SHOW_MASK);
32096            this.showMaskEvent.signature = SIGNATURE;
32097
32098            /**
32099            * CustomEvent fired before the modality mask is shown. Subscribers can return false to prevent the
32100            * mask from being shown
32101            * @event beforeShowMaskEvent
32102            */
32103            this.beforeShowMaskEvent = this.createEvent(EVENT_TYPES.BEFORE_SHOW_MASK);
32104            this.beforeShowMaskEvent.signature = SIGNATURE;
32105
32106            /**
32107            * CustomEvent fired after the modality mask is hidden
32108            * @event hideMaskEvent
32109            */
32110            this.hideMaskEvent = this.createEvent(EVENT_TYPES.HIDE_MASK);
32111            this.hideMaskEvent.signature = SIGNATURE;
32112
32113            /**
32114            * CustomEvent fired before the modality mask is hidden. Subscribers can return false to prevent the
32115            * mask from being hidden
32116            * @event beforeHideMaskEvent
32117            */
32118            this.beforeHideMaskEvent = this.createEvent(EVENT_TYPES.BEFORE_HIDE_MASK);
32119            this.beforeHideMaskEvent.signature = SIGNATURE;
32120
32121            /**
32122            * CustomEvent when the Panel is dragged
32123            * @event dragEvent
32124            */
32125            this.dragEvent = this.createEvent(EVENT_TYPES.DRAG);
32126            this.dragEvent.signature = SIGNATURE;
32127        },
32128
32129        /**
32130         * Initializes the class's configurable properties which can be changed 
32131         * using the Panel's Config object (cfg).
32132         * @method initDefaultConfig
32133         */
32134        initDefaultConfig: function () {
32135            Panel.superclass.initDefaultConfig.call(this);
32136
32137            // Add panel config properties //
32138
32139            /**
32140            * True if the Panel should display a "close" button
32141            * @config close
32142            * @type Boolean
32143            * @default true
32144            */
32145            this.cfg.addProperty(DEFAULT_CONFIG.CLOSE.key, { 
32146                handler: this.configClose, 
32147                value: DEFAULT_CONFIG.CLOSE.value, 
32148                validator: DEFAULT_CONFIG.CLOSE.validator, 
32149                supercedes: DEFAULT_CONFIG.CLOSE.supercedes 
32150            });
32151
32152            /**
32153            * Boolean specifying if the Panel should be draggable.  The default 
32154            * value is "true" if the Drag and Drop utility is included, 
32155            * otherwise it is "false." <strong>PLEASE NOTE:</strong> There is a 
32156            * known issue in IE 6 (Strict Mode and Quirks Mode) and IE 7 
32157            * (Quirks Mode) where Panels that either don't have a value set for 
32158            * their "width" configuration property, or their "width" 
32159            * configuration property is set to "auto" will only be draggable by
32160            * placing the mouse on the text of the Panel's header element.
32161            * To fix this bug, draggable Panels missing a value for their 
32162            * "width" configuration property, or whose "width" configuration 
32163            * property is set to "auto" will have it set to the value of 
32164            * their root HTML element's offsetWidth before they are made 
32165            * visible.  The calculated width is then removed when the Panel is   
32166            * hidden. <em>This fix is only applied to draggable Panels in IE 6 
32167            * (Strict Mode and Quirks Mode) and IE 7 (Quirks Mode)</em>. For 
32168            * more information on this issue see:
32169            * YUILibrary bugs #1726972 and #1589210.
32170            * @config draggable
32171            * @type Boolean
32172            * @default true
32173            */
32174            this.cfg.addProperty(DEFAULT_CONFIG.DRAGGABLE.key, {
32175                handler: this.configDraggable,
32176                value: (Util.DD) ? true : false,
32177                validator: DEFAULT_CONFIG.DRAGGABLE.validator,
32178                supercedes: DEFAULT_CONFIG.DRAGGABLE.supercedes
32179            });
32180
32181            /**
32182            * Boolean specifying if the draggable Panel should be drag only, not interacting with drop 
32183            * targets on the page.
32184            * <p>
32185            * When set to true, draggable Panels will not check to see if they are over drop targets,
32186            * or fire the DragDrop events required to support drop target interaction (onDragEnter, 
32187            * onDragOver, onDragOut, onDragDrop etc.).
32188            * If the Panel is not designed to be dropped on any target elements on the page, then this 
32189            * flag can be set to true to improve performance.
32190            * </p>
32191            * <p>
32192            * When set to false, all drop target related events will be fired.
32193            * </p>
32194            * <p>
32195            * The property is set to false by default to maintain backwards compatibility but should be 
32196            * set to true if drop target interaction is not required for the Panel, to improve performance.</p>
32197            * 
32198            * @config dragOnly
32199            * @type Boolean
32200            * @default false
32201            */
32202            this.cfg.addProperty(DEFAULT_CONFIG.DRAG_ONLY.key, { 
32203                value: DEFAULT_CONFIG.DRAG_ONLY.value, 
32204                validator: DEFAULT_CONFIG.DRAG_ONLY.validator, 
32205                supercedes: DEFAULT_CONFIG.DRAG_ONLY.supercedes 
32206            });
32207
32208            /**
32209            * Sets the type of underlay to display for the Panel. Valid values 
32210            * are "shadow," "matte," and "none".  <strong>PLEASE NOTE:</strong> 
32211            * The creation of the underlay element is deferred until the Panel 
32212            * is initially made visible.  For Gecko-based browsers on Mac
32213            * OS X the underlay elment is always created as it is used as a 
32214            * shim to prevent Aqua scrollbars below a Panel instance from poking 
32215            * through it (See YUILibrary bug #1723530).
32216            * @config underlay
32217            * @type String
32218            * @default shadow
32219            */
32220            this.cfg.addProperty(DEFAULT_CONFIG.UNDERLAY.key, { 
32221                handler: this.configUnderlay, 
32222                value: DEFAULT_CONFIG.UNDERLAY.value, 
32223                supercedes: DEFAULT_CONFIG.UNDERLAY.supercedes 
32224            });
32225        
32226            /**
32227            * True if the Panel should be displayed in a modal fashion, 
32228            * automatically creating a transparent mask over the document that
32229            * will not be removed until the Panel is dismissed.
32230            * @config modal
32231            * @type Boolean
32232            * @default false
32233            */
32234            this.cfg.addProperty(DEFAULT_CONFIG.MODAL.key, { 
32235                handler: this.configModal, 
32236                value: DEFAULT_CONFIG.MODAL.value,
32237                validator: DEFAULT_CONFIG.MODAL.validator, 
32238                supercedes: DEFAULT_CONFIG.MODAL.supercedes 
32239            });
32240
32241            /**
32242            * A KeyListener (or array of KeyListeners) that will be enabled 
32243            * when the Panel is shown, and disabled when the Panel is hidden.
32244            * @config keylisteners
32245            * @type YAHOO.util.KeyListener[]
32246            * @default null
32247            */
32248            this.cfg.addProperty(DEFAULT_CONFIG.KEY_LISTENERS.key, { 
32249                handler: this.configKeyListeners, 
32250                suppressEvent: DEFAULT_CONFIG.KEY_LISTENERS.suppressEvent, 
32251                supercedes: DEFAULT_CONFIG.KEY_LISTENERS.supercedes 
32252            });
32253
32254            /**
32255            * UI Strings used by the Panel. The strings are inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
32256            * 
32257            * @config strings
32258            * @type Object
32259            * @default An object literal with the properties shown below:
32260            *     <dl>
32261            *         <dt>close</dt><dd><em>HTML</em> : The markup to use as the label for the close icon. Defaults to "Close".</dd>
32262            *     </dl>
32263            */
32264            this.cfg.addProperty(DEFAULT_CONFIG.STRINGS.key, { 
32265                value:DEFAULT_CONFIG.STRINGS.value,
32266                handler:this.configStrings,
32267                validator:DEFAULT_CONFIG.STRINGS.validator,
32268                supercedes:DEFAULT_CONFIG.STRINGS.supercedes
32269            });
32270        },
32271
32272        // BEGIN BUILT-IN PROPERTY EVENT HANDLERS //
32273        
32274        /**
32275        * The default event handler fired when the "close" property is changed.
32276        * The method controls the appending or hiding of the close icon at the 
32277        * top right of the Panel.
32278        * @method configClose
32279        * @param {String} type The CustomEvent type (usually the property name)
32280        * @param {Object[]} args The CustomEvent arguments. For configuration 
32281        * handlers, args[0] will equal the newly applied value for the property.
32282        * @param {Object} obj The scope object. For configuration handlers, 
32283        * this will usually equal the owner.
32284        */
32285        configClose: function (type, args, obj) {
32286
32287            var val = args[0],
32288                oClose = this.close,
32289                strings = this.cfg.getProperty("strings"),
32290                fc;
32291
32292            if (val) {
32293                if (!oClose) {
32294
32295                    if (!m_oCloseIconTemplate) {
32296                        m_oCloseIconTemplate = document.createElement("a");
32297                        m_oCloseIconTemplate.className = "container-close";
32298                        m_oCloseIconTemplate.href = "#";
32299                    }
32300
32301                    oClose = m_oCloseIconTemplate.cloneNode(true);
32302
32303                    fc = this.innerElement.firstChild;
32304
32305                    if (fc) {
32306                        this.innerElement.insertBefore(oClose, fc);
32307                    } else {
32308                        this.innerElement.appendChild(oClose);
32309                    }
32310
32311                    oClose.innerHTML = (strings && strings.close) ? strings.close : "&#160;";
32312
32313                    Event.on(oClose, "click", this._doClose, this, true);
32314
32315                    this.close = oClose;
32316
32317                } else {
32318                    oClose.style.display = "block";
32319                }
32320
32321            } else {
32322                if (oClose) {
32323                    oClose.style.display = "none";
32324                }
32325            }
32326
32327        },
32328
32329        /**
32330         * Event handler for the close icon
32331         * 
32332         * @method _doClose
32333         * @protected
32334         * 
32335         * @param {DOMEvent} e
32336         */
32337        _doClose : function (e) {
32338            Event.preventDefault(e);
32339            this.hide();
32340        },
32341
32342        /**
32343        * The default event handler fired when the "draggable" property 
32344        * is changed.
32345        * @method configDraggable
32346        * @param {String} type The CustomEvent type (usually the property name)
32347        * @param {Object[]} args The CustomEvent arguments. For configuration 
32348        * handlers, args[0] will equal the newly applied value for the property.
32349        * @param {Object} obj The scope object. For configuration handlers, 
32350        * this will usually equal the owner.
32351        */
32352        configDraggable: function (type, args, obj) {
32353            var val = args[0];
32354
32355            if (val) {
32356                if (!Util.DD) {
32357                    YAHOO.log("DD dependency not met.", "error");
32358                    this.cfg.setProperty("draggable", false);
32359                    return;
32360                }
32361
32362                if (this.header) {
32363                    Dom.setStyle(this.header, "cursor", "move");
32364                    this.registerDragDrop();
32365                }
32366
32367                this.subscribe("beforeShow", setWidthToOffsetWidth);
32368
32369            } else {
32370
32371                if (this.dd) {
32372                    this.dd.unreg();
32373                }
32374
32375                if (this.header) {
32376                    Dom.setStyle(this.header,"cursor","auto");
32377                }
32378
32379                this.unsubscribe("beforeShow", setWidthToOffsetWidth);
32380            }
32381        },
32382      
32383        /**
32384        * The default event handler fired when the "underlay" property 
32385        * is changed.
32386        * @method configUnderlay
32387        * @param {String} type The CustomEvent type (usually the property name)
32388        * @param {Object[]} args The CustomEvent arguments. For configuration 
32389        * handlers, args[0] will equal the newly applied value for the property.
32390        * @param {Object} obj The scope object. For configuration handlers, 
32391        * this will usually equal the owner.
32392        */
32393        configUnderlay: function (type, args, obj) {
32394
32395            var bMacGecko = (this.platform == "mac" && UA.gecko),
32396                sUnderlay = args[0].toLowerCase(),
32397                oUnderlay = this.underlay,
32398                oElement = this.element;
32399
32400            function createUnderlay() {
32401                var bNew = false;
32402                if (!oUnderlay) { // create if not already in DOM
32403
32404                    if (!m_oUnderlayTemplate) {
32405                        m_oUnderlayTemplate = document.createElement("div");
32406                        m_oUnderlayTemplate.className = "underlay";
32407                    }
32408
32409                    oUnderlay = m_oUnderlayTemplate.cloneNode(false);
32410                    this.element.appendChild(oUnderlay);
32411
32412                    this.underlay = oUnderlay;
32413
32414                    if (bIEQuirks) {
32415                        this.sizeUnderlay();
32416                        this.cfg.subscribeToConfigEvent("width", this.sizeUnderlay);
32417                        this.cfg.subscribeToConfigEvent("height", this.sizeUnderlay);
32418
32419                        this.changeContentEvent.subscribe(this.sizeUnderlay);
32420                        YAHOO.widget.Module.textResizeEvent.subscribe(this.sizeUnderlay, this, true);
32421                    }
32422
32423                    if (UA.webkit && UA.webkit < 420) {
32424                        this.changeContentEvent.subscribe(this.forceUnderlayRedraw);
32425                    }
32426
32427                    bNew = true;
32428                }
32429            }
32430
32431            function onBeforeShow() {
32432                var bNew = createUnderlay.call(this);
32433                if (!bNew && bIEQuirks) {
32434                    this.sizeUnderlay();
32435                }
32436                this._underlayDeferred = false;
32437                this.beforeShowEvent.unsubscribe(onBeforeShow);
32438            }
32439
32440            function destroyUnderlay() {
32441                if (this._underlayDeferred) {
32442                    this.beforeShowEvent.unsubscribe(onBeforeShow);
32443                    this._underlayDeferred = false;
32444                }
32445
32446                if (oUnderlay) {
32447                    this.cfg.unsubscribeFromConfigEvent("width", this.sizeUnderlay);
32448                    this.cfg.unsubscribeFromConfigEvent("height",this.sizeUnderlay);
32449                    this.changeContentEvent.unsubscribe(this.sizeUnderlay);
32450                    this.changeContentEvent.unsubscribe(this.forceUnderlayRedraw);
32451                    YAHOO.widget.Module.textResizeEvent.unsubscribe(this.sizeUnderlay, this, true);
32452
32453                    this.element.removeChild(oUnderlay);
32454
32455                    this.underlay = null;
32456                }
32457            }
32458
32459            switch (sUnderlay) {
32460                case "shadow":
32461                    Dom.removeClass(oElement, "matte");
32462                    Dom.addClass(oElement, "shadow");
32463                    break;
32464                case "matte":
32465                    if (!bMacGecko) {
32466                        destroyUnderlay.call(this);
32467                    }
32468                    Dom.removeClass(oElement, "shadow");
32469                    Dom.addClass(oElement, "matte");
32470                    break;
32471                default:
32472                    if (!bMacGecko) {
32473                        destroyUnderlay.call(this);
32474                    }
32475                    Dom.removeClass(oElement, "shadow");
32476                    Dom.removeClass(oElement, "matte");
32477                    break;
32478            }
32479
32480            if ((sUnderlay == "shadow") || (bMacGecko && !oUnderlay)) {
32481                if (this.cfg.getProperty("visible")) {
32482                    var bNew = createUnderlay.call(this);
32483                    if (!bNew && bIEQuirks) {
32484                        this.sizeUnderlay();
32485                    }
32486                } else {
32487                    if (!this._underlayDeferred) {
32488                        this.beforeShowEvent.subscribe(onBeforeShow);
32489                        this._underlayDeferred = true;
32490                    }
32491                }
32492            }
32493        },
32494        
32495        /**
32496        * The default event handler fired when the "modal" property is 
32497        * changed. This handler subscribes or unsubscribes to the show and hide
32498        * events to handle the display or hide of the modality mask.
32499        * @method configModal
32500        * @param {String} type The CustomEvent type (usually the property name)
32501        * @param {Object[]} args The CustomEvent arguments. For configuration 
32502        * handlers, args[0] will equal the newly applied value for the property.
32503        * @param {Object} obj The scope object. For configuration handlers, 
32504        * this will usually equal the owner.
32505        */
32506        configModal: function (type, args, obj) {
32507
32508            var modal = args[0];
32509            if (modal) {
32510                if (!this._hasModalityEventListeners) {
32511
32512                    this.subscribe("beforeShow", this.buildMask);
32513                    this.subscribe("beforeShow", this.bringToTop);
32514                    this.subscribe("beforeShow", this.showMask);
32515                    this.subscribe("hide", this.hideMask);
32516
32517                    Overlay.windowResizeEvent.subscribe(this.sizeMask, 
32518                        this, true);
32519
32520                    this._hasModalityEventListeners = true;
32521                }
32522            } else {
32523                if (this._hasModalityEventListeners) {
32524
32525                    if (this.cfg.getProperty("visible")) {
32526                        this.hideMask();
32527                        this.removeMask();
32528                    }
32529
32530                    this.unsubscribe("beforeShow", this.buildMask);
32531                    this.unsubscribe("beforeShow", this.bringToTop);
32532                    this.unsubscribe("beforeShow", this.showMask);
32533                    this.unsubscribe("hide", this.hideMask);
32534
32535                    Overlay.windowResizeEvent.unsubscribe(this.sizeMask, this);
32536
32537                    this._hasModalityEventListeners = false;
32538                }
32539            }
32540        },
32541
32542        /**
32543        * Removes the modality mask.
32544        * @method removeMask
32545        */
32546        removeMask: function () {
32547
32548            var oMask = this.mask,
32549                oParentNode;
32550
32551            if (oMask) {
32552                /*
32553                    Hide the mask before destroying it to ensure that DOM
32554                    event handlers on focusable elements get removed.
32555                */
32556                this.hideMask();
32557
32558                oParentNode = oMask.parentNode;
32559                if (oParentNode) {
32560                    oParentNode.removeChild(oMask);
32561                }
32562
32563                this.mask = null;
32564            }
32565        },
32566        
32567        /**
32568        * The default event handler fired when the "keylisteners" property 
32569        * is changed.
32570        * @method configKeyListeners
32571        * @param {String} type The CustomEvent type (usually the property name)
32572        * @param {Object[]} args The CustomEvent arguments. For configuration
32573        * handlers, args[0] will equal the newly applied value for the property.
32574        * @param {Object} obj The scope object. For configuration handlers, 
32575        * this will usually equal the owner.
32576        */
32577        configKeyListeners: function (type, args, obj) {
32578
32579            var listeners = args[0],
32580                listener,
32581                nListeners,
32582                i;
32583        
32584            if (listeners) {
32585
32586                if (listeners instanceof Array) {
32587
32588                    nListeners = listeners.length;
32589
32590                    for (i = 0; i < nListeners; i++) {
32591
32592                        listener = listeners[i];
32593        
32594                        if (!Config.alreadySubscribed(this.showEvent, 
32595                            listener.enable, listener)) {
32596
32597                            this.showEvent.subscribe(listener.enable, 
32598                                listener, true);
32599
32600                        }
32601
32602                        if (!Config.alreadySubscribed(this.hideEvent, 
32603                            listener.disable, listener)) {
32604
32605                            this.hideEvent.subscribe(listener.disable, 
32606                                listener, true);
32607
32608                            this.destroyEvent.subscribe(listener.disable, 
32609                                listener, true);
32610                        }
32611                    }
32612
32613                } else {
32614
32615                    if (!Config.alreadySubscribed(this.showEvent, 
32616                        listeners.enable, listeners)) {
32617
32618                        this.showEvent.subscribe(listeners.enable, 
32619                            listeners, true);
32620                    }
32621
32622                    if (!Config.alreadySubscribed(this.hideEvent, 
32623                        listeners.disable, listeners)) {
32624
32625                        this.hideEvent.subscribe(listeners.disable, 
32626                            listeners, true);
32627
32628                        this.destroyEvent.subscribe(listeners.disable, 
32629                            listeners, true);
32630
32631                    }
32632
32633                }
32634
32635            }
32636
32637        },
32638
32639        /**
32640        * The default handler for the "strings" property
32641        * @method configStrings
32642        */
32643        configStrings : function(type, args, obj) {
32644            var val = Lang.merge(DEFAULT_CONFIG.STRINGS.value, args[0]);
32645            this.cfg.setProperty(DEFAULT_CONFIG.STRINGS.key, val, true);
32646        },
32647
32648        /**
32649        * The default event handler fired when the "height" property is changed.
32650        * @method configHeight
32651        * @param {String} type The CustomEvent type (usually the property name)
32652        * @param {Object[]} args The CustomEvent arguments. For configuration 
32653        * handlers, args[0] will equal the newly applied value for the property.
32654        * @param {Object} obj The scope object. For configuration handlers, 
32655        * this will usually equal the owner.
32656        */
32657        configHeight: function (type, args, obj) {
32658            var height = args[0],
32659                el = this.innerElement;
32660
32661            Dom.setStyle(el, "height", height);
32662            this.cfg.refireEvent("iframe");
32663        },
32664
32665        /**
32666         * The default custom event handler executed when the Panel's height is changed, 
32667         * if the autofillheight property has been set.
32668         *
32669         * @method _autoFillOnHeightChange
32670         * @protected
32671         * @param {String} type The event type
32672         * @param {Array} args The array of arguments passed to event subscribers
32673         * @param {HTMLElement} el The header, body or footer element which is to be resized to fill
32674         * out the containers height
32675         */
32676        _autoFillOnHeightChange : function(type, args, el) {
32677            Panel.superclass._autoFillOnHeightChange.apply(this, arguments);
32678            if (bIEQuirks) {
32679                var panel = this;
32680                setTimeout(function() {
32681                    panel.sizeUnderlay();
32682                },0);
32683            }
32684        },
32685
32686        /**
32687        * The default event handler fired when the "width" property is changed.
32688        * @method configWidth
32689        * @param {String} type The CustomEvent type (usually the property name)
32690        * @param {Object[]} args The CustomEvent arguments. For configuration 
32691        * handlers, args[0] will equal the newly applied value for the property.
32692        * @param {Object} obj The scope object. For configuration handlers, 
32693        * this will usually equal the owner.
32694        */
32695        configWidth: function (type, args, obj) {
32696    
32697            var width = args[0],
32698                el = this.innerElement;
32699    
32700            Dom.setStyle(el, "width", width);
32701            this.cfg.refireEvent("iframe");
32702    
32703        },
32704        
32705        /**
32706        * The default event handler fired when the "zIndex" property is changed.
32707        * @method configzIndex
32708        * @param {String} type The CustomEvent type (usually the property name)
32709        * @param {Object[]} args The CustomEvent arguments. For configuration 
32710        * handlers, args[0] will equal the newly applied value for the property.
32711        * @param {Object} obj The scope object. For configuration handlers, 
32712        * this will usually equal the owner.
32713        */
32714        configzIndex: function (type, args, obj) {
32715            Panel.superclass.configzIndex.call(this, type, args, obj);
32716
32717            if (this.mask || this.cfg.getProperty("modal") === true) {
32718                var panelZ = Dom.getStyle(this.element, "zIndex");
32719                if (!panelZ || isNaN(panelZ)) {
32720                    panelZ = 0;
32721                }
32722
32723                if (panelZ === 0) {
32724                    // Recursive call to configzindex (which should be stopped
32725                    // from going further because panelZ should no longer === 0)
32726                    this.cfg.setProperty("zIndex", 1);
32727                } else {
32728                    this.stackMask();
32729                }
32730            }
32731        },
32732
32733        // END BUILT-IN PROPERTY EVENT HANDLERS //
32734        /**
32735        * Builds the wrapping container around the Panel that is used for 
32736        * positioning the shadow and matte underlays. The container element is 
32737        * assigned to a  local instance variable called container, and the 
32738        * element is reinserted inside of it.
32739        * @method buildWrapper
32740        */
32741        buildWrapper: function () {
32742
32743            var elementParent = this.element.parentNode,
32744                originalElement = this.element,
32745                wrapper = document.createElement("div");
32746
32747            wrapper.className = Panel.CSS_PANEL_CONTAINER;
32748            wrapper.id = originalElement.id + "_c";
32749
32750            if (elementParent) {
32751                elementParent.insertBefore(wrapper, originalElement);
32752            }
32753
32754            wrapper.appendChild(originalElement);
32755
32756            this.element = wrapper;
32757            this.innerElement = originalElement;
32758
32759            Dom.setStyle(this.innerElement, "visibility", "inherit");
32760        },
32761
32762        /**
32763        * Adjusts the size of the shadow based on the size of the element.
32764        * @method sizeUnderlay
32765        */
32766        sizeUnderlay: function () {
32767            var oUnderlay = this.underlay,
32768                oElement;
32769
32770            if (oUnderlay) {
32771                oElement = this.element;
32772                oUnderlay.style.width = oElement.offsetWidth + "px";
32773                oUnderlay.style.height = oElement.offsetHeight + "px";
32774            }
32775        },
32776
32777        /**
32778        * Registers the Panel's header for drag & drop capability.
32779        * @method registerDragDrop
32780        */
32781        registerDragDrop: function () {
32782
32783            var me = this;
32784
32785            if (this.header) {
32786
32787                if (!Util.DD) {
32788                    YAHOO.log("DD dependency not met.", "error");
32789                    return;
32790                }
32791
32792                var bDragOnly = (this.cfg.getProperty("dragonly") === true);
32793
32794                /**
32795                 * The YAHOO.util.DD instance, used to implement the draggable header for the panel if draggable is enabled
32796                 *
32797                 * @property dd
32798                 * @type YAHOO.util.DD
32799                 */
32800                this.dd = new Util.DD(this.element.id, this.id, {dragOnly: bDragOnly});
32801
32802                if (!this.header.id) {
32803                    this.header.id = this.id + "_h";
32804                }
32805
32806                this.dd.startDrag = function () {
32807
32808                    var offsetHeight,
32809                        offsetWidth,
32810                        viewPortWidth,
32811                        viewPortHeight,
32812                        scrollX,
32813                        scrollY;
32814
32815                    if (YAHOO.env.ua.ie == 6) {
32816                        Dom.addClass(me.element,"drag");
32817                    }
32818
32819                    if (me.cfg.getProperty("constraintoviewport")) {
32820
32821                        var nViewportOffset = Overlay.VIEWPORT_OFFSET;
32822
32823                        offsetHeight = me.element.offsetHeight;
32824                        offsetWidth = me.element.offsetWidth;
32825
32826                        viewPortWidth = Dom.getViewportWidth();
32827                        viewPortHeight = Dom.getViewportHeight();
32828
32829                        scrollX = Dom.getDocumentScrollLeft();
32830                        scrollY = Dom.getDocumentScrollTop();
32831
32832                        if (offsetHeight + nViewportOffset < viewPortHeight) {
32833                            this.minY = scrollY + nViewportOffset;
32834                            this.maxY = scrollY + viewPortHeight - offsetHeight - nViewportOffset;
32835                        } else {
32836                            this.minY = scrollY + nViewportOffset;
32837                            this.maxY = scrollY + nViewportOffset;
32838                        }
32839
32840                        if (offsetWidth + nViewportOffset < viewPortWidth) {
32841                            this.minX = scrollX + nViewportOffset;
32842                            this.maxX = scrollX + viewPortWidth - offsetWidth - nViewportOffset;
32843                        } else {
32844                            this.minX = scrollX + nViewportOffset;
32845                            this.maxX = scrollX + nViewportOffset;
32846                        }
32847
32848                        this.constrainX = true;
32849                        this.constrainY = true;
32850                    } else {
32851                        this.constrainX = false;
32852                        this.constrainY = false;
32853                    }
32854
32855                    me.dragEvent.fire("startDrag", arguments);
32856                };
32857
32858                this.dd.onDrag = function () {
32859                    me.syncPosition();
32860                    me.cfg.refireEvent("iframe");
32861                    if (this.platform == "mac" && YAHOO.env.ua.gecko) {
32862                        this.showMacGeckoScrollbars();
32863                    }
32864
32865                    me.dragEvent.fire("onDrag", arguments);
32866                };
32867
32868                this.dd.endDrag = function () {
32869
32870                    if (YAHOO.env.ua.ie == 6) {
32871                        Dom.removeClass(me.element,"drag");
32872                    }
32873
32874                    me.dragEvent.fire("endDrag", arguments);
32875                    me.moveEvent.fire(me.cfg.getProperty("xy"));
32876
32877                };
32878
32879                this.dd.setHandleElId(this.header.id);
32880                this.dd.addInvalidHandleType("INPUT");
32881                this.dd.addInvalidHandleType("SELECT");
32882                this.dd.addInvalidHandleType("TEXTAREA");
32883            }
32884        },
32885        
32886        /**
32887        * Builds the mask that is laid over the document when the Panel is 
32888        * configured to be modal.
32889        * @method buildMask
32890        */
32891        buildMask: function () {
32892            var oMask = this.mask;
32893            if (!oMask) {
32894                if (!m_oMaskTemplate) {
32895                    m_oMaskTemplate = document.createElement("div");
32896                    m_oMaskTemplate.className = "mask";
32897                    m_oMaskTemplate.innerHTML = "&#160;";
32898                }
32899                oMask = m_oMaskTemplate.cloneNode(true);
32900                oMask.id = this.id + "_mask";
32901
32902                document.body.insertBefore(oMask, document.body.firstChild);
32903
32904                this.mask = oMask;
32905
32906                if (YAHOO.env.ua.gecko && this.platform == "mac") {
32907                    Dom.addClass(this.mask, "block-scrollbars");
32908                }
32909
32910                // Stack mask based on the element zindex
32911                this.stackMask();
32912            }
32913        },
32914
32915        /**
32916        * Hides the modality mask.
32917        * @method hideMask
32918        */
32919        hideMask: function () {
32920            if (this.cfg.getProperty("modal") && this.mask && this.beforeHideMaskEvent.fire()) {
32921                this.mask.style.display = "none";
32922                Dom.removeClass(document.body, "masked");
32923                this.hideMaskEvent.fire();
32924            }
32925        },
32926
32927        /**
32928        * Shows the modality mask.
32929        * @method showMask
32930        */
32931        showMask: function () {
32932            if (this.cfg.getProperty("modal") && this.mask && this.beforeShowMaskEvent.fire()) {
32933                Dom.addClass(document.body, "masked");
32934                this.sizeMask();
32935                this.mask.style.display = "block";
32936                this.showMaskEvent.fire();
32937            }
32938        },
32939
32940        /**
32941        * Sets the size of the modality mask to cover the entire scrollable 
32942        * area of the document
32943        * @method sizeMask
32944        */
32945        sizeMask: function () {
32946            if (this.mask) {
32947
32948                // Shrink mask first, so it doesn't affect the document size.
32949                var mask = this.mask,
32950                    viewWidth = Dom.getViewportWidth(),
32951                    viewHeight = Dom.getViewportHeight();
32952
32953                if (mask.offsetHeight > viewHeight) {
32954                    mask.style.height = viewHeight + "px";
32955                }
32956
32957                if (mask.offsetWidth > viewWidth) {
32958                    mask.style.width = viewWidth + "px";
32959                }
32960
32961                // Then size it to the document
32962                mask.style.height = Dom.getDocumentHeight() + "px";
32963                mask.style.width = Dom.getDocumentWidth() + "px";
32964            }
32965        },
32966
32967        /**
32968         * Sets the zindex of the mask, if it exists, based on the zindex of 
32969         * the Panel element. The zindex of the mask is set to be one less 
32970         * than the Panel element's zindex.
32971         * 
32972         * <p>NOTE: This method will not bump up the zindex of the Panel
32973         * to ensure that the mask has a non-negative zindex. If you require the
32974         * mask zindex to be 0 or higher, the zindex of the Panel 
32975         * should be set to a value higher than 0, before this method is called.
32976         * </p>
32977         * @method stackMask
32978         */
32979        stackMask: function() {
32980            if (this.mask) {
32981                var panelZ = Dom.getStyle(this.element, "zIndex");
32982                if (!YAHOO.lang.isUndefined(panelZ) && !isNaN(panelZ)) {
32983                    Dom.setStyle(this.mask, "zIndex", panelZ - 1);
32984                }
32985            }
32986        },
32987
32988        /**
32989        * Renders the Panel by inserting the elements that are not already in 
32990        * the main Panel into their correct places. Optionally appends the 
32991        * Panel to the specified node prior to the render's execution. NOTE: 
32992        * For Panels without existing markup, the appendToNode argument is 
32993        * REQUIRED. If this argument is ommitted and the current element is 
32994        * not present in the document, the function will return false, 
32995        * indicating that the render was a failure.
32996        * @method render
32997        * @param {String} appendToNode The element id to which the Module 
32998        * should be appended to prior to rendering <em>OR</em>
32999        * @param {HTMLElement} appendToNode The element to which the Module 
33000        * should be appended to prior to rendering
33001        * @return {boolean} Success or failure of the render
33002        */
33003        render: function (appendToNode) {
33004            return Panel.superclass.render.call(this, appendToNode, this.innerElement);
33005        },
33006
33007        /**
33008         * Renders the currently set header into it's proper position under the 
33009         * module element. If the module element is not provided, "this.innerElement" 
33010         * is used.
33011         *
33012         * @method _renderHeader
33013         * @protected
33014         * @param {HTMLElement} moduleElement Optional. A reference to the module element
33015         */
33016        _renderHeader: function(moduleElement){
33017            moduleElement = moduleElement || this.innerElement;
33018			Panel.superclass._renderHeader.call(this, moduleElement);
33019        },
33020
33021        /**
33022         * Renders the currently set body into it's proper position under the 
33023         * module element. If the module element is not provided, "this.innerElement" 
33024         * is used.
33025         * 
33026         * @method _renderBody
33027         * @protected
33028         * @param {HTMLElement} moduleElement Optional. A reference to the module element.
33029         */
33030        _renderBody: function(moduleElement){
33031            moduleElement = moduleElement || this.innerElement;
33032            Panel.superclass._renderBody.call(this, moduleElement);
33033        },
33034
33035        /**
33036         * Renders the currently set footer into it's proper position under the 
33037         * module element. If the module element is not provided, "this.innerElement" 
33038         * is used.
33039         *
33040         * @method _renderFooter
33041         * @protected
33042         * @param {HTMLElement} moduleElement Optional. A reference to the module element
33043         */
33044        _renderFooter: function(moduleElement){
33045            moduleElement = moduleElement || this.innerElement;
33046            Panel.superclass._renderFooter.call(this, moduleElement);
33047        },
33048
33049        /**
33050        * Removes the Panel element from the DOM and sets all child elements
33051        * to null.
33052        * @method destroy
33053        * @param {boolean} shallowPurge If true, only the parent element's DOM event listeners are purged. If false, or not provided, all children are also purged of DOM event listeners. 
33054        * NOTE: The flag is a "shallowPurge" flag, as opposed to what may be a more intuitive "purgeChildren" flag to maintain backwards compatibility with behavior prior to 2.9.0.
33055        */
33056        destroy: function (shallowPurge) {
33057            Overlay.windowResizeEvent.unsubscribe(this.sizeMask, this);
33058            this.removeMask();
33059            if (this.close) {
33060                Event.purgeElement(this.close);
33061            }
33062            Panel.superclass.destroy.call(this, shallowPurge);  
33063        },
33064
33065        /**
33066         * Forces the underlay element to be repainted through the application/removal 
33067         * of a yui-force-redraw class to the underlay element.
33068         *
33069         * @method forceUnderlayRedraw
33070         */
33071        forceUnderlayRedraw : function () {
33072            var u = this.underlay;
33073            Dom.addClass(u, "yui-force-redraw");
33074            setTimeout(function(){Dom.removeClass(u, "yui-force-redraw");}, 0);
33075        },
33076
33077        /**
33078        * Returns a String representation of the object.
33079        * @method toString
33080        * @return {String} The string representation of the Panel.
33081        */
33082        toString: function () {
33083            return "Panel " + this.id;
33084        }
33085    
33086    });
33087
33088}());
33089(function () {
33090
33091    /**
33092    * <p>
33093    * Dialog is an implementation of Panel that can be used to submit form 
33094    * data.
33095    * </p>
33096    * <p>
33097    * Built-in functionality for buttons with event handlers is included. 
33098    * If the optional YUI Button dependancy is included on the page, the buttons
33099    * created will be instances of YAHOO.widget.Button, otherwise regular HTML buttons
33100    * will be created.
33101    * </p>
33102    * <p>
33103    * Forms can be processed in 3 ways -- via an asynchronous Connection utility call, 
33104    * a simple form POST or GET, or manually. The YUI Connection utility should be
33105    * included if you're using the default "async" postmethod, but is not required if
33106    * you're using any of the other postmethod values.
33107    * </p>
33108    * @namespace YAHOO.widget
33109    * @class Dialog
33110    * @extends YAHOO.widget.Panel
33111    * @constructor
33112    * @param {String} el The element ID representing the Dialog <em>OR</em>
33113    * @param {HTMLElement} el The element representing the Dialog
33114    * @param {Object} userConfig The configuration object literal containing 
33115    * the configuration that should be set for this Dialog. See configuration 
33116    * documentation for more details.
33117    */
33118    YAHOO.widget.Dialog = function (el, userConfig) {
33119        YAHOO.widget.Dialog.superclass.constructor.call(this, el, userConfig);
33120    };
33121
33122    var Event = YAHOO.util.Event,
33123        CustomEvent = YAHOO.util.CustomEvent,
33124        Dom = YAHOO.util.Dom,
33125        Dialog = YAHOO.widget.Dialog,
33126        Lang = YAHOO.lang,
33127
33128        /**
33129         * Constant representing the name of the Dialog's events
33130         * @property EVENT_TYPES
33131         * @private
33132         * @final
33133         * @type Object
33134         */
33135        EVENT_TYPES = {
33136            "BEFORE_SUBMIT": "beforeSubmit",
33137            "SUBMIT": "submit",
33138            "MANUAL_SUBMIT": "manualSubmit",
33139            "ASYNC_SUBMIT": "asyncSubmit",
33140            "FORM_SUBMIT": "formSubmit",
33141            "CANCEL": "cancel"
33142        },
33143
33144        /**
33145        * Constant representing the Dialog's configuration properties
33146        * @property DEFAULT_CONFIG
33147        * @private
33148        * @final
33149        * @type Object
33150        */
33151        DEFAULT_CONFIG = {
33152
33153            "POST_METHOD": { 
33154                key: "postmethod", 
33155                value: "async"
33156            },
33157
33158            "POST_DATA" : {
33159                key: "postdata",
33160                value: null
33161            },
33162
33163            "BUTTONS": {
33164                key: "buttons",
33165                value: "none",
33166                supercedes: ["visible"]
33167            },
33168
33169            "HIDEAFTERSUBMIT" : {
33170                key: "hideaftersubmit",
33171                value: true
33172            }
33173
33174        };
33175
33176    /**
33177    * Constant representing the default CSS class used for a Dialog
33178    * @property YAHOO.widget.Dialog.CSS_DIALOG
33179    * @static
33180    * @final
33181    * @type String
33182    */
33183    Dialog.CSS_DIALOG = "yui-dialog";
33184
33185    function removeButtonEventHandlers() {
33186
33187        var aButtons = this._aButtons,
33188            nButtons,
33189            oButton,
33190            i;
33191
33192        if (Lang.isArray(aButtons)) {
33193            nButtons = aButtons.length;
33194
33195            if (nButtons > 0) {
33196                i = nButtons - 1;
33197                do {
33198                    oButton = aButtons[i];
33199
33200                    if (YAHOO.widget.Button && oButton instanceof YAHOO.widget.Button) {
33201                        oButton.destroy();
33202                    }
33203                    else if (oButton.tagName.toUpperCase() == "BUTTON") {
33204                        Event.purgeElement(oButton);
33205                        Event.purgeElement(oButton, false);
33206                    }
33207                }
33208                while (i--);
33209            }
33210        }
33211    }
33212
33213    YAHOO.extend(Dialog, YAHOO.widget.Panel, { 
33214
33215        /**
33216        * @property form
33217        * @description Object reference to the Dialog's 
33218        * <code>&#60;form&#62;</code> element.
33219        * @default null 
33220        * @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
33221        * level-one-html.html#ID-40002357">HTMLFormElement</a>
33222        */
33223        form: null,
33224    
33225        /**
33226        * Initializes the class's configurable properties which can be changed 
33227        * using the Dialog's Config object (cfg).
33228        * @method initDefaultConfig
33229        */
33230        initDefaultConfig: function () {
33231            Dialog.superclass.initDefaultConfig.call(this);
33232
33233            /**
33234            * The internally maintained callback object for use with the 
33235            * Connection utility. The format of the callback object is 
33236            * similar to Connection Manager's callback object and is 
33237            * simply passed through to Connection Manager when the async 
33238            * request is made.
33239            * @property callback
33240            * @type Object
33241            */
33242            this.callback = {
33243
33244                /**
33245                * The function to execute upon success of the 
33246                * Connection submission (when the form does not
33247                * contain a file input element).
33248                * 
33249                * @property callback.success
33250                * @type Function
33251                */
33252                success: null,
33253
33254                /**
33255                * The function to execute upon failure of the 
33256                * Connection submission
33257                * @property callback.failure
33258                * @type Function
33259                */
33260                failure: null,
33261
33262                /**
33263                *<p>
33264                * The function to execute upon success of the 
33265                * Connection submission, when the form contains
33266                * a file input element.
33267                * </p>
33268                * <p>
33269                * <em>NOTE:</em> Connection manager will not
33270                * invoke the success or failure handlers for the file
33271                * upload use case. This will be the only callback
33272                * handler invoked.
33273                * </p>
33274                * <p>
33275                * For more information, see the <a href="http://developer.yahoo.com/yui/connection/#file">
33276                * Connection Manager documenation on file uploads</a>.
33277                * </p>
33278                * @property callback.upload
33279                * @type Function
33280                */
33281
33282                /**
33283                * The arbitrary argument or arguments to pass to the Connection 
33284                * callback functions
33285                * @property callback.argument
33286                * @type Object
33287                */
33288                argument: null
33289
33290            };
33291
33292            // Add form dialog config properties //
33293            /**
33294            * The method to use for posting the Dialog's form. Possible values 
33295            * are "async", "form", and "manual".
33296            * @config postmethod
33297            * @type String
33298            * @default async
33299            */
33300            this.cfg.addProperty(DEFAULT_CONFIG.POST_METHOD.key, {
33301                handler: this.configPostMethod, 
33302                value: DEFAULT_CONFIG.POST_METHOD.value, 
33303                validator: function (val) {
33304                    if (val != "form" && val != "async" && val != "none" && 
33305                        val != "manual") {
33306                        return false;
33307                    } else {
33308                        return true;
33309                    }
33310                }
33311            });
33312
33313            /**
33314            * Any additional post data which needs to be sent when using the 
33315            * <a href="#config_postmethod">async</a> postmethod for dialog POST submissions.
33316            * The format for the post data string is defined by Connection Manager's 
33317            * <a href="YAHOO.util.Connect.html#method_asyncRequest">asyncRequest</a> 
33318            * method.
33319            * @config postdata
33320            * @type String
33321            * @default null
33322            */
33323            this.cfg.addProperty(DEFAULT_CONFIG.POST_DATA.key, {
33324                value: DEFAULT_CONFIG.POST_DATA.value
33325            });
33326
33327            /**
33328            * This property is used to configure whether or not the 
33329            * dialog should be automatically hidden after submit.
33330            * 
33331            * @config hideaftersubmit
33332            * @type Boolean
33333            * @default true
33334            */
33335            this.cfg.addProperty(DEFAULT_CONFIG.HIDEAFTERSUBMIT.key, {
33336                value: DEFAULT_CONFIG.HIDEAFTERSUBMIT.value
33337            });
33338
33339            /**
33340            * Array of object literals, each containing a set of properties 
33341            * defining a button to be appended into the Dialog's footer.
33342            *
33343            * <p>Each button object in the buttons array can have three properties:</p>
33344            * <dl>
33345            *    <dt>text:</dt>
33346            *    <dd>
33347            *       The text that will display on the face of the button. The text can 
33348            *       include HTML, as long as it is compliant with HTML Button specifications. The text is added to the DOM as HTML,
33349            *       and should be escaped by the implementor if coming from an external source. 
33350            *    </dd>
33351            *    <dt>handler:</dt>
33352            *    <dd>Can be either:
33353            *    <ol>
33354            *       <li>A reference to a function that should fire when the 
33355            *       button is clicked.  (In this case scope of this function is 
33356            *       always its Dialog instance.)</li>
33357            *
33358            *       <li>An object literal representing the code to be 
33359            *       executed when the button is clicked.
33360            *       
33361            *       <p>Format:</p>
33362            *
33363            *       <p>
33364            *       <code>{
33365            *       <br>
33366            *       <strong>fn:</strong> Function, &#47;&#47;
33367            *       The handler to call when  the event fires.
33368            *       <br>
33369            *       <strong>obj:</strong> Object, &#47;&#47; 
33370            *       An  object to pass back to the handler.
33371            *       <br>
33372            *       <strong>scope:</strong> Object &#47;&#47; 
33373            *       The object to use for the scope of the handler.
33374            *       <br>
33375            *       }</code>
33376            *       </p>
33377            *       </li>
33378            *     </ol>
33379            *     </dd>
33380            *     <dt>isDefault:</dt>
33381            *     <dd>
33382            *        An optional boolean value that specifies that a button 
33383            *        should be highlighted and focused by default.
33384            *     </dd>
33385            * </dl>
33386            *
33387            * <em>NOTE:</em>If the YUI Button Widget is included on the page, 
33388            * the buttons created will be instances of YAHOO.widget.Button. 
33389            * Otherwise, HTML Buttons (<code>&#60;BUTTON&#62;</code>) will be 
33390            * created.
33391            *
33392            * @config buttons
33393            * @type {Array|String}
33394            * @default "none"
33395            */
33396            this.cfg.addProperty(DEFAULT_CONFIG.BUTTONS.key, {
33397                handler: this.configButtons,
33398                value: DEFAULT_CONFIG.BUTTONS.value,
33399                supercedes : DEFAULT_CONFIG.BUTTONS.supercedes
33400            }); 
33401
33402        },
33403
33404        /**
33405        * Initializes the custom events for Dialog which are fired 
33406        * automatically at appropriate times by the Dialog class.
33407        * @method initEvents
33408        */
33409        initEvents: function () {
33410            Dialog.superclass.initEvents.call(this);
33411
33412            var SIGNATURE = CustomEvent.LIST;
33413
33414            /**
33415            * CustomEvent fired prior to submission
33416            * @event beforeSubmitEvent
33417            */ 
33418            this.beforeSubmitEvent = 
33419                this.createEvent(EVENT_TYPES.BEFORE_SUBMIT);
33420            this.beforeSubmitEvent.signature = SIGNATURE;
33421            
33422            /**
33423            * CustomEvent fired after submission
33424            * @event submitEvent
33425            */
33426            this.submitEvent = this.createEvent(EVENT_TYPES.SUBMIT);
33427            this.submitEvent.signature = SIGNATURE;
33428        
33429            /**
33430            * CustomEvent fired for manual submission, before the generic submit event is fired
33431            * @event manualSubmitEvent
33432            */
33433            this.manualSubmitEvent = 
33434                this.createEvent(EVENT_TYPES.MANUAL_SUBMIT);
33435            this.manualSubmitEvent.signature = SIGNATURE;
33436
33437            /**
33438            * CustomEvent fired after asynchronous submission, before the generic submit event is fired
33439            *
33440            * @event asyncSubmitEvent
33441            * @param {Object} conn The connection object, returned by YAHOO.util.Connect.asyncRequest
33442            */
33443            this.asyncSubmitEvent = this.createEvent(EVENT_TYPES.ASYNC_SUBMIT);
33444            this.asyncSubmitEvent.signature = SIGNATURE;
33445
33446            /**
33447            * CustomEvent fired after form-based submission, before the generic submit event is fired
33448            * @event formSubmitEvent
33449            */
33450            this.formSubmitEvent = this.createEvent(EVENT_TYPES.FORM_SUBMIT);
33451            this.formSubmitEvent.signature = SIGNATURE;
33452
33453            /**
33454            * CustomEvent fired after cancel
33455            * @event cancelEvent
33456            */
33457            this.cancelEvent = this.createEvent(EVENT_TYPES.CANCEL);
33458            this.cancelEvent.signature = SIGNATURE;
33459        
33460        },
33461        
33462        /**
33463        * The Dialog initialization method, which is executed for Dialog and 
33464        * all of its subclasses. This method is automatically called by the 
33465        * constructor, and  sets up all DOM references for pre-existing markup, 
33466        * and creates required markup if it is not already present.
33467        * 
33468        * @method init
33469        * @param {String} el The element ID representing the Dialog <em>OR</em>
33470        * @param {HTMLElement} el The element representing the Dialog
33471        * @param {Object} userConfig The configuration object literal 
33472        * containing the configuration that should be set for this Dialog. 
33473        * See configuration documentation for more details.
33474        */
33475        init: function (el, userConfig) {
33476
33477            /*
33478                 Note that we don't pass the user config in here yet because 
33479                 we only want it executed once, at the lowest subclass level
33480            */
33481
33482            Dialog.superclass.init.call(this, el/*, userConfig*/); 
33483
33484            this.beforeInitEvent.fire(Dialog);
33485
33486            Dom.addClass(this.element, Dialog.CSS_DIALOG);
33487
33488            this.cfg.setProperty("visible", false);
33489
33490            if (userConfig) {
33491                this.cfg.applyConfig(userConfig, true);
33492            }
33493
33494            //this.showEvent.subscribe(this.focusFirst, this, true);
33495            this.beforeHideEvent.subscribe(this.blurButtons, this, true);
33496
33497            this.subscribe("changeBody", this.registerForm);
33498
33499            this.initEvent.fire(Dialog);
33500        },
33501
33502        /**
33503        * Submits the Dialog's form depending on the value of the 
33504        * "postmethod" configuration property.  <strong>Please note:
33505        * </strong> As of version 2.3 this method will automatically handle 
33506        * asyncronous file uploads should the Dialog instance's form contain 
33507        * <code>&#60;input type="file"&#62;</code> elements.  If a Dialog 
33508        * instance will be handling asyncronous file uploads, its 
33509        * <code>callback</code> property will need to be setup with a 
33510        * <code>upload</code> handler rather than the standard 
33511        * <code>success</code> and, or <code>failure</code> handlers.  For more 
33512        * information, see the <a href="http://developer.yahoo.com/yui/
33513        * connection/#file">Connection Manager documenation on file uploads</a>.
33514        * @method doSubmit
33515        */
33516        doSubmit: function () {
33517
33518            var Connect = YAHOO.util.Connect,
33519                oForm = this.form,
33520                bUseFileUpload = false,
33521                bUseSecureFileUpload = false,
33522                aElements,
33523                nElements,
33524                i,
33525                formAttrs;
33526
33527            switch (this.cfg.getProperty("postmethod")) {
33528
33529                case "async":
33530                    aElements = oForm.elements;
33531                    nElements = aElements.length;
33532
33533                    if (nElements > 0) {
33534                        i = nElements - 1;
33535                        do {
33536                            if (aElements[i].type == "file") {
33537                                bUseFileUpload = true;
33538                                break;
33539                            }
33540                        }
33541                        while(i--);
33542                    }
33543
33544                    if (bUseFileUpload && YAHOO.env.ua.ie && this.isSecure) {
33545                        bUseSecureFileUpload = true;
33546                    }
33547
33548                    formAttrs = this._getFormAttributes(oForm);
33549
33550                    Connect.setForm(oForm, bUseFileUpload, bUseSecureFileUpload);
33551
33552                    var postData = this.cfg.getProperty("postdata");
33553                    var c = Connect.asyncRequest(formAttrs.method, formAttrs.action, this.callback, postData);
33554
33555                    this.asyncSubmitEvent.fire(c);
33556
33557                    break;
33558
33559                case "form":
33560                    oForm.submit();
33561                    this.formSubmitEvent.fire();
33562                    break;
33563
33564                case "none":
33565                case "manual":
33566                    this.manualSubmitEvent.fire();
33567                    break;
33568            }
33569        },
33570
33571        /**
33572         * Retrieves important attributes (currently method and action) from
33573         * the form element, accounting for any elements which may have the same name 
33574         * as the attributes. Defaults to "POST" and "" for method and action respectively
33575         * if the attribute cannot be retrieved.
33576         *
33577         * @method _getFormAttributes
33578         * @protected
33579         * @param {HTMLFormElement} oForm The HTML Form element from which to retrieve the attributes
33580         * @return {Object} Object literal, with method and action String properties.
33581         */
33582        _getFormAttributes : function(oForm){
33583            var attrs = {
33584                method : null,
33585                action : null
33586            };
33587
33588            if (oForm) {
33589                if (oForm.getAttributeNode) {
33590                    var action = oForm.getAttributeNode("action");
33591                    var method = oForm.getAttributeNode("method");
33592
33593                    if (action) {
33594                        attrs.action = action.value;
33595                    }
33596
33597                    if (method) {
33598                        attrs.method = method.value;
33599                    }
33600
33601                } else {
33602                    attrs.action = oForm.getAttribute("action");
33603                    attrs.method = oForm.getAttribute("method");
33604                }
33605            }
33606
33607            attrs.method = (Lang.isString(attrs.method) ? attrs.method : "POST").toUpperCase();
33608            attrs.action = Lang.isString(attrs.action) ? attrs.action : "";
33609
33610            return attrs;
33611        },
33612
33613        /**
33614        * Prepares the Dialog's internal FORM object, creating one if one is
33615        * not currently present.
33616        * @method registerForm
33617        */
33618        registerForm: function() {
33619
33620            var form = this.element.getElementsByTagName("form")[0];
33621
33622            if (this.form) {
33623                if (this.form == form && Dom.isAncestor(this.element, this.form)) {
33624                    return;
33625                } else {
33626                    Event.purgeElement(this.form);
33627                    this.form = null;
33628                }
33629            }
33630
33631            if (!form) {
33632                form = document.createElement("form");
33633                form.name = "frm_" + this.id;
33634                this.body.appendChild(form);
33635            }
33636
33637            if (form) {
33638                this.form = form;
33639                Event.on(form, "submit", this._submitHandler, this, true);
33640            }
33641        },
33642
33643        /**
33644         * Internal handler for the form submit event
33645         *
33646         * @method _submitHandler
33647         * @protected
33648         * @param {DOMEvent} e The DOM Event object
33649         */
33650        _submitHandler : function(e) {
33651            Event.stopEvent(e);
33652            this.submit();
33653            this.form.blur();
33654        },
33655
33656        /**
33657         * Sets up a tab, shift-tab loop between the first and last elements
33658         * provided. NOTE: Sets up the preventBackTab and preventTabOut KeyListener
33659         * instance properties, which are reset everytime this method is invoked.
33660         *
33661         * @method setTabLoop
33662         * @param {HTMLElement} firstElement
33663         * @param {HTMLElement} lastElement
33664         *
33665         */
33666        setTabLoop : function(firstElement, lastElement) {
33667
33668            firstElement = firstElement || this.firstButton;
33669            lastElement = lastElement || this.lastButton;
33670
33671            Dialog.superclass.setTabLoop.call(this, firstElement, lastElement);
33672        },
33673
33674        /**
33675         * Protected internal method for setTabLoop, which can be used by 
33676         * subclasses to jump in and modify the arguments passed in if required.
33677         *
33678         * @method _setTabLoop
33679         * @param {HTMLElement} firstElement
33680         * @param {HTMLElement} lastElement
33681         * @protected
33682         */
33683        _setTabLoop : function(firstElement, lastElement) {
33684            firstElement = firstElement || this.firstButton;
33685            lastElement = this.lastButton || lastElement;
33686
33687            this.setTabLoop(firstElement, lastElement);
33688        },
33689
33690        /**
33691         * Configures instance properties, pointing to the 
33692         * first and last focusable elements in the Dialog's form.
33693         *
33694         * @method setFirstLastFocusable
33695         */
33696        setFirstLastFocusable : function() {
33697
33698            Dialog.superclass.setFirstLastFocusable.call(this);
33699
33700            var i, l, el, elements = this.focusableElements;
33701
33702            this.firstFormElement = null;
33703            this.lastFormElement = null;
33704
33705            if (this.form && elements && elements.length > 0) {
33706                l = elements.length;
33707
33708                for (i = 0; i < l; ++i) {
33709                    el = elements[i];
33710                    if (this.form === el.form) {
33711                        this.firstFormElement = el;
33712                        break;
33713                    }
33714                }
33715
33716                for (i = l-1; i >= 0; --i) {
33717                    el = elements[i];
33718                    if (this.form === el.form) {
33719                        this.lastFormElement = el;
33720                        break;
33721                    }
33722                }
33723            }
33724        },
33725
33726        // BEGIN BUILT-IN PROPERTY EVENT HANDLERS //
33727        /**
33728        * The default event handler fired when the "close" property is 
33729        * changed. The method controls the appending or hiding of the close
33730        * icon at the top right of the Dialog.
33731        * @method configClose
33732        * @param {String} type The CustomEvent type (usually the property name)
33733        * @param {Object[]} args The CustomEvent arguments. For 
33734        * configuration handlers, args[0] will equal the newly applied value 
33735        * for the property.
33736        * @param {Object} obj The scope object. For configuration handlers, 
33737        * this will usually equal the owner.
33738        */
33739        configClose: function (type, args, obj) {
33740            Dialog.superclass.configClose.apply(this, arguments);
33741        },
33742
33743        /**
33744         * Event handler for the close icon
33745         * 
33746         * @method _doClose
33747         * @protected
33748         * 
33749         * @param {DOMEvent} e
33750         */
33751         _doClose : function(e) {
33752            Event.preventDefault(e);
33753            this.cancel();
33754        },
33755
33756        /**
33757        * The default event handler for the "buttons" configuration property
33758        * @method configButtons
33759        * @param {String} type The CustomEvent type (usually the property name)
33760        * @param {Object[]} args The CustomEvent arguments. For configuration 
33761        * handlers, args[0] will equal the newly applied value for the property.
33762        * @param {Object} obj The scope object. For configuration handlers, 
33763        * this will usually equal the owner.
33764        */
33765        configButtons: function (type, args, obj) {
33766
33767            var Button = YAHOO.widget.Button,
33768                aButtons = args[0],
33769                oInnerElement = this.innerElement,
33770                oButton,
33771                oButtonEl,
33772                oYUIButton,
33773                nButtons,
33774                oSpan,
33775                oFooter,
33776                i;
33777
33778            removeButtonEventHandlers.call(this);
33779
33780            this._aButtons = null;
33781
33782            if (Lang.isArray(aButtons)) {
33783
33784                oSpan = document.createElement("span");
33785                oSpan.className = "button-group";
33786                nButtons = aButtons.length;
33787
33788                this._aButtons = [];
33789                this.defaultHtmlButton = null;
33790
33791                for (i = 0; i < nButtons; i++) {
33792                    oButton = aButtons[i];
33793
33794                    if (Button) {
33795                        oYUIButton = new Button({ label: oButton.text, type:oButton.t
33795ype });
33796                        oYUIButton.appendTo(oSpan);
33797
33798                        oButtonEl = oYUIButton.get("element");
33799
33800                        if (oButton.isDefault) {
33801                            oYUIButton.addClass("default");
33802                            this.defaultHtmlButton = oButtonEl;
33803                        }
33804
33805                        if (Lang.isFunction(oButton.handler)) {
33806
33807                            oYUIButton.set("onclick", { 
33808                                fn: oButton.handler, 
33809                                obj: this, 
33810                                scope: this 
33811                            });
33812
33813                        } else if (Lang.isObject(oButton.handler) && Lang.isFunction(oButton.handler.fn)) {
33814
33815                            oYUIButton.set("onclick", { 
33816                                fn: oButton.handler.fn, 
33817                                obj: ((!Lang.isUndefined(oButton.handler.obj)) ? oButton.handler.obj : this), 
33818                                scope: (oButton.handler.scope || this) 
33819                            });
33820
33821                        }
33822
33823                        this._aButtons[this._aButtons.length] = oYUIButton;
33824
33825                    } else {
33826
33827                        oButtonEl = document.createElement("button");
33828                        oButtonEl.setAttribute("type", "button");
33829
33830                        if (oButton.isDefault) {
33831                            oButtonEl.className = "default";
33832                            this.defaultHtmlButton = oButtonEl;
33833                        }
33834
33835                        oButtonEl.innerHTML = oButton.text;
33836
33837                        if (Lang.isFunction(oButton.handler)) {
33838                            Event.on(oButtonEl, "click", oButton.handler, this, true);
33839                        } else if (Lang.isObject(oButton.handler) && 
33840                            Lang.isFunction(oButton.handler.fn)) {
33841    
33842                            Event.on(oButtonEl, "click", 
33843                                oButton.handler.fn, 
33844                                ((!Lang.isUndefined(oButton.handler.obj)) ? oButton.handler.obj : this), 
33845                                (oButton.handler.scope || this));
33846                        }
33847
33848                        oSpan.appendChild(oButtonEl);
33849                        this._aButtons[this._aButtons.length] = oButtonEl;
33850                    }
33851
33852                    oButton.htmlButton = oButtonEl;
33853
33854                    if (i === 0) {
33855                        this.firstButton = oButtonEl;
33856                    }
33857
33858                    if (i == (nButtons - 1)) {
33859                        this.lastButton = oButtonEl;
33860                    }
33861                }
33862
33863                this.setFooter(oSpan);
33864
33865                oFooter = this.footer;
33866
33867                if (Dom.inDocument(this.element) && !Dom.isAncestor(oInnerElement, oFooter)) {
33868                    oInnerElement.appendChild(oFooter);
33869                }
33870
33871                this.buttonSpan = oSpan;
33872
33873            } else { // Do cleanup
33874                oSpan = this.buttonSpan;
33875                oFooter = this.footer;
33876                if (oSpan && oFooter) {
33877                    oFooter.removeChild(oSpan);
33878                    this.buttonSpan = null;
33879                    this.firstButton = null;
33880                    this.lastButton = null;
33881                    this.defaultHtmlButton = null;
33882                }
33883            }
33884
33885            this.changeContentEvent.fire();
33886        },
33887
33888        /**
33889        * @method getButtons
33890        * @description Returns an array containing each of the Dialog's 
33891        * buttons, by default an array of HTML <code>&#60;BUTTON&#62;</code> 
33892        * elements.  If the Dialog's buttons were created using the 
33893        * YAHOO.widget.Button class (via the inclusion of the optional Button 
33894        * dependency on the page), an array of YAHOO.widget.Button instances 
33895        * is returned.
33896        * @return {Array}
33897        */
33898        getButtons: function () {
33899            return this._aButtons || null;
33900        },
33901
33902        /**
33903         * <p>
33904         * Sets focus to the first focusable element in the Dialog's form if found, 
33905         * else, the default button if found, else the first button defined via the 
33906         * "buttons" configuration property.
33907         * </p>
33908         * <p>
33909         * This method is invoked when the Dialog is made visible.
33910         * </p>
33911         * @method focusFirst
33912         * @return {Boolean} true, if focused. false if not
33913         */
33914        focusFirst: function (type, args, obj) {
33915
33916            var el = this.firstFormElement, 
33917                focused = false;
33918
33919            if (args && args[1]) {
33920                Event.stopEvent(args[1]);
33921
33922                // When tabbing here, use firstElement instead of firstFormElement
33923                if (args[0] === 9 && this.firstElement) {
33924                    el = this.firstElement;
33925                }
33926            }
33927
33928            if (el) {
33929                try {
33930                    el.focus();
33931                    focused = true;
33932                } catch(oException) {
33933                    // Ignore
33934                }
33935            } else {
33936                if (this.defaultHtmlButton) {
33937                    focused = this.focusDefaultButton();
33938                } else {
33939                    focused = this.focusFirstButton();
33940                }
33941            }
33942            return focused;
33943        },
33944
33945        /**
33946        * Sets focus to the last element in the Dialog's form or the last 
33947        * button defined via the "buttons" configuration property.
33948        * @method focusLast
33949        * @return {Boolean} true, if focused. false if not
33950        */
33951        focusLast: function (type, args, obj) {
33952
33953            var aButtons = this.cfg.getProperty("buttons"),
33954                el = this.lastFormElement,
33955                focused = false;
33956
33957            if (args && args[1]) {
33958                Event.stopEvent(args[1]);
33959
33960                // When tabbing here, use lastElement instead of lastFormElement
33961                if (args[0] === 9 && this.lastElement) {
33962                    el = this.lastElement;
33963                }
33964            }
33965
33966            if (aButtons && Lang.isArray(aButtons)) {
33967                focused = this.focusLastButton();
33968            } else {
33969                if (el) {
33970                    try {
33971                        el.focus();
33972                        focused = true;
33973                    } catch(oException) {
33974                        // Ignore
33975                    }
33976                }
33977            }
33978
33979            return focused;
33980        },
33981
33982        /**
33983         * Helper method to normalize button references. It either returns the 
33984         * YUI Button instance for the given element if found,
33985         * or the passes back the HTMLElement reference if a corresponding YUI Button
33986         * reference is not found or YAHOO.widget.Button does not exist on the page.
33987         *
33988         * @method _getButton
33989         * @private
33990         * @param {HTMLElement} button
33991         * @return {YAHOO.widget.Button|HTMLElement}
33992         */
33993        _getButton : function(button) {
33994            var Button = YAHOO.widget.Button;
33995
33996            // If we have an HTML button and YUI Button is on the page, 
33997            // get the YUI Button reference if available.
33998            if (Button && button && button.nodeName && button.id) {
33999                button = Button.getButton(button.id) || button;
34000            }
34001
34002            return button;
34003        },
34004
34005        /**
34006        * Sets the focus to the button that is designated as the default via 
34007        * the "buttons" configuration property. By default, this method is 
34008        * called when the Dialog is made visible.
34009        * @method focusDefaultButton
34010        * @return {Boolean} true if focused, false if not
34011        */
34012        focusDefaultButton: function () {
34013            var button = this._getButton(this.defaultHtmlButton), 
34014                         focused = false;
34015            
34016            if (button) {
34017                /*
34018                    Place the call to the "focus" method inside a try/catch
34019                    block to prevent IE from throwing JavaScript errors if
34020                    the element is disabled or hidden.
34021                */
34022                try {
34023                    button.focus();
34024                    focused = true;
34025                } catch(oException) {
34026                }
34027            }
34028            return focused;
34029        },
34030
34031        /**
34032        * Blurs all the buttons defined via the "buttons" 
34033        * configuration property.
34034        * @method blurButtons
34035        */
34036        blurButtons: function () {
34037            
34038            var aButtons = this.cfg.getProperty("buttons"),
34039                nButtons,
34040                oButton,
34041                oElement,
34042                i;
34043
34044            if (aButtons && Lang.isArray(aButtons)) {
34045                nButtons = aButtons.length;
34046                if (nButtons > 0) {
34047                    i = (nButtons - 1);
34048                    do {
34049                        oButton = aButtons[i];
34050                        if (oButton) {
34051                            oElement = this._getButton(oButton.htmlButton);
34052                            if (oElement) {
34053                                /*
34054                                    Place the call to the "blur" method inside  
34055                                    a try/catch block to prevent IE from  
34056                                    throwing JavaScript errors if the element 
34057                                    is disabled or hidden.
34058                                */
34059                                try {
34060                                    oElement.blur();
34061                                } catch(oException) {
34062                                    // ignore
34063                                }
34064                            }
34065                        }
34066                    } while(i--);
34067                }
34068            }
34069        },
34070
34071        /**
34072        * Sets the focus to the first button created via the "buttons"
34073        * configuration property.
34074        * @method focusFirstButton
34075        * @return {Boolean} true, if focused. false if not
34076        */
34077        focusFirstButton: function () {
34078
34079            var aButtons = this.cfg.getProperty("buttons"),
34080                oButton,
34081                oElement,
34082                focused = false;
34083
34084            if (aButtons && Lang.isArray(aButtons)) {
34085                oButton = aButtons[0];
34086                if (oButton) {
34087                    oElement = this._getButton(oButton.htmlButton);
34088                    if (oElement) {
34089                        /*
34090                            Place the call to the "focus" method inside a 
34091                            try/catch block to prevent IE from throwing 
34092                            JavaScript errors if the element is disabled 
34093                            or hidden.
34094                        */
34095                        try {
34096                            oElement.focus();
34097                            focused = true;
34098                        } catch(oException) {
34099                            // ignore
34100                        }
34101                    }
34102                }
34103            }
34104
34105            return focused;
34106        },
34107
34108        /**
34109        * Sets the focus to the last button created via the "buttons" 
34110        * configuration property.
34111        * @method focusLastButton
34112        * @return {Boolean} true, if focused. false if not
34113        */
34114        focusLastButton: function () {
34115
34116            var aButtons = this.cfg.getProperty("buttons"),
34117                nButtons,
34118                oButton,
34119                oElement, 
34120                focused = false;
34121
34122            if (aButtons && Lang.isArray(aButtons)) {
34123                nButtons = aButtons.length;
34124                if (nButtons > 0) {
34125                    oButton = aButtons[(nButtons - 1)];
34126
34127                    if (oButton) {
34128                        oElement = this._getButton(oButton.htmlButton);
34129                        if (oElement) {
34130                            /*
34131                                Place the call to the "focus" method inside a 
34132                                try/catch block to prevent IE from throwing 
34133                                JavaScript errors if the element is disabled
34134                                or hidden.
34135                            */
34136        
34137                            try {
34138                                oElement.focus();
34139                                focused = true;
34140                            } catch(oException) {
34141                                // Ignore
34142                            }
34143                        }
34144                    }
34145                }
34146            }
34147
34148            return focused;
34149        },
34150
34151        /**
34152        * The default event handler for the "postmethod" configuration property
34153        * @method configPostMethod
34154        * @param {String} type The CustomEvent type (usually the property name)
34155        * @param {Object[]} args The CustomEvent arguments. For 
34156        * configuration handlers, args[0] will equal the newly applied value 
34157        * for the property.
34158        * @param {Object} obj The scope object. For configuration handlers, 
34159        * this will usually equal the owner.
34160        */
34161        configPostMethod: function (type, args, obj) {
34162            this.registerForm();
34163        },
34164
34165        // END BUILT-IN PROPERTY EVENT HANDLERS //
34166        
34167        /**
34168        * Built-in function hook for writing a validation function that will 
34169        * be checked for a "true" value prior to a submit. This function, as 
34170        * implemented by default, always returns true, so it should be 
34171        * overridden if validation is necessary.
34172        * @method validate
34173        */
34174        validate: function () {
34175            return true;
34176        },
34177
34178        /**
34179        * Executes a submit of the Dialog if validation 
34180        * is successful. By default the Dialog is hidden
34181        * after submission, but you can set the "hideaftersubmit"
34182        * configuration property to false, to prevent the Dialog
34183        * from being hidden.
34184        * 
34185        * @method submit
34186        */
34187        submit: function () {
34188            if (this.validate()) {
34189                if (this.beforeSubmitEvent.fire()) {
34190                    this.doSubmit();
34191                    this.submitEvent.fire();
34192    
34193                    if (this.cfg.getProperty("hideaftersubmit")) {
34194                        this.hide();
34195                    }
34196    
34197                    return true;
34198                } else {
34199                    return false;
34200                }
34201            } else {
34202                return false;
34203            }
34204        },
34205
34206        /**
34207        * Executes the cancel of the Dialog followed by a hide.
34208        * @method cancel
34209        */
34210        cancel: function () {
34211            this.cancelEvent.fire();
34212            this.hide();
34213        },
34214        
34215        /**
34216        * Returns a JSON-compatible data structure representing the data 
34217        * currently contained in the form.
34218        * @method getData
34219        * @return {Object} A JSON object reprsenting the data of the 
34220        * current form.
34221        */
34222        getData: function () {
34223
34224            var oForm = this.form,
34225                aElements,
34226                nTotalElements,
34227                oData,
34228                sName,
34229                oElement,
34230                nElements,
34231                sType,
34232                sTagName,
34233                aOptions,
34234                nOptions,
34235                aValues,
34236                oOption,
34237                oRadio,
34238                oCheckbox,
34239                valueAttr,
34240                i,
34241                n;    
34242    
34243            function isFormElement(p_oElement) {
34244                var sTag = p_oElement.tagName.toUpperCase();
34245                return ((sTag == "INPUT" || sTag == "TEXTAREA" || 
34246                        sTag == "SELECT") && p_oElement.name == sName);
34247            }
34248
34249            if (oForm) {
34250
34251                aElements = oForm.elements;
34252                nTotalElements = aElements.length;
34253                oData = {};
34254
34255                for (i = 0; i < nTotalElements; i++) {
34256                    sName = aElements[i].name;
34257
34258                    /*
34259                        Using "Dom.getElementsBy" to safeguard user from JS 
34260                        errors that result from giving a form field (or set of 
34261                        fields) the same name as a native method of a form 
34262                        (like "submit") or a DOM collection (such as the "item"
34263                        method). Originally tried accessing fields via the 
34264                        "namedItem" method of the "element" collection, but 
34265                        discovered that it won't return a collection of fields 
34266                        in Gecko.
34267                    */
34268
34269                    oElement = Dom.getElementsBy(isFormElement, "*", oForm);
34270                    nElements = oElement.length;
34271
34272                    if (nElements > 0) {
34273                        if (nElements == 1) {
34274                            oElement = oElement[0];
34275
34276                            sType = oElement.type;
34277                            sTagName = oElement.tagName.toUpperCase();
34278
34279                            switch (sTagName) {
34280                                case "INPUT":
34281                                    if (sType == "checkbox") {
34282                                        oData[sName] = oElement.checked;
34283                                    } else if (sType != "radio") {
34284                                        oData[sName] = oElement.value;
34285                                    }
34286                                    break;
34287
34288                                case "TEXTAREA":
34289                                    oData[sName] = oElement.value;
34290                                    break;
34291    
34292                                case "SELECT":
34293                                    aOptions = oElement.options;
34294                                    nOptions = aOptions.length;
34295                                    aValues = [];
34296    
34297                                    for (n = 0; n < nOptions; n++) {
34298                                        oOption = aOptions[n];
34299                                        if (oOption.selected) {
34300                                            valueAttr = oOption.attributes.value;
34301                                            aValues[aValues.length] = (valueAttr && valueAttr.specified) ? oOption.value : oOption.text;
34302                                        }
34303                                    }
34304                                    oData[sName] = aValues;
34305                                    break;
34306                            }
34307        
34308                        } else {
34309                            sType = oElement[0].type;
34310                            switch (sType) {
34311                                case "radio":
34312                                    for (n = 0; n < nElements; n++) {
34313                                        oRadio = oElement[n];
34314                                        if (oRadio.checked) {
34315                                            oData[sName] = oRadio.value;
34316                                            break;
34317                                        }
34318                                    }
34319                                    break;
34320        
34321                                case "checkbox":
34322                                    aValues = [];
34323                                    for (n = 0; n < nElements; n++) {
34324                                        oCheckbox = oElement[n];
34325                                        if (oCheckbox.checked) {
34326                                            aValues[aValues.length] =  oCheckbox.value;
34327                                        }
34328                                    }
34329                                    oData[sName] = aValues;
34330                                    break;
34331                            }
34332                        }
34333                    }
34334                }
34335            }
34336
34337            return oData;
34338        },
34339
34340        /**
34341        * Removes the Panel element from the DOM and sets all child elements 
34342        * to null.
34343        * @method destroy
34344        * @param {boolean} shallowPurge If true, only the parent element's DOM event listeners are purged. If false, or not provided, all children are also purged of DOM event listeners. 
34345        * NOTE: The flag is a "shallowPurge" flag, as opposed to what may be a more intuitive "purgeChildren" flag to maintain backwards compatibility with behavior prior to 2.9.0.
34346        */
34347        destroy: function (shallowPurge) {
34348            removeButtonEventHandlers.call(this);
34349
34350            this._aButtons = null;
34351
34352            var aForms = this.element.getElementsByTagName("form"),
34353                oForm;
34354
34355            if (aForms.length > 0) {
34356                oForm = aForms[0];
34357
34358                if (oForm) {
34359                    Event.purgeElement(oForm);
34360                    if (oForm.parentNode) {
34361                        oForm.parentNode.removeChild(oForm);
34362                    }
34363                    this.form = null;
34364                }
34365            }
34366            Dialog.superclass.destroy.call(this, shallowPurge);
34367        },
34368
34369        /**
34370        * Returns a string representation of the object.
34371        * @method toString
34372        * @return {String} The string representation of the Dialog
34373        */
34374        toString: function () {
34375            return "Dialog " + this.id;
34376        }
34377    
34378    });
34379
34380}());
34381(function () {
34382
34383    /**
34384    * SimpleDialog is a simple implementation of Dialog that can be used to 
34385    * submit a single value. Forms can be processed in 3 ways -- via an 
34386    * asynchronous Connection utility call, a simple form POST or GET, 
34387    * or manually.
34388    * @namespace YAHOO.widget
34389    * @class SimpleDialog
34390    * @extends YAHOO.widget.Dialog
34391    * @constructor
34392    * @param {String} el The element ID representing the SimpleDialog 
34393    * <em>OR</em>
34394    * @param {HTMLElement} el The element representing the SimpleDialog
34395    * @param {Object} userConfig The configuration object literal containing 
34396    * the configuration that should be set for this SimpleDialog. See 
34397    * configuration documentation for more details.
34398    */
34399    YAHOO.widget.SimpleDialog = function (el, userConfig) {
34400    
34401        YAHOO.widget.SimpleDialog.superclass.constructor.call(this, 
34402            el, userConfig);
34403    
34404    };
34405
34406    var Dom = YAHOO.util.Dom,
34407        SimpleDialog = YAHOO.widget.SimpleDialog,
34408    
34409        /**
34410        * Constant representing the SimpleDialog's configuration properties
34411        * @property DEFAULT_CONFIG
34412        * @private
34413        * @final
34414        * @type Object
34415        */
34416        DEFAULT_CONFIG = {
34417        
34418            "ICON": { 
34419                key: "icon", 
34420                value: "none", 
34421                suppressEvent: true  
34422            },
34423        
34424            "TEXT": { 
34425                key: "text", 
34426                value: "", 
34427                suppressEvent: true, 
34428                supercedes: ["icon"] 
34429            }
34430        
34431        };
34432
34433    /**
34434    * Constant for the standard network icon for a blocking action
34435    * @property YAHOO.widget.SimpleDialog.ICON_BLOCK
34436    * @static
34437    * @final
34438    * @type String
34439    */
34440    SimpleDialog.ICON_BLOCK = "blckicon";
34441    
34442    /**
34443    * Constant for the standard network icon for alarm
34444    * @property YAHOO.widget.SimpleDialog.ICON_ALARM
34445    * @static
34446    * @final
34447    * @type String
34448    */
34449    SimpleDialog.ICON_ALARM = "alrticon";
34450    
34451    /**
34452    * Constant for the standard network icon for help
34453    * @property YAHOO.widget.SimpleDialog.ICON_HELP
34454    * @static
34455    * @final
34456    * @type String
34457    */
34458    SimpleDialog.ICON_HELP  = "hlpicon";
34459    
34460    /**
34461    * Constant for the standard network icon for info
34462    * @property YAHOO.widget.SimpleDialog.ICON_INFO
34463    * @static
34464    * @final
34465    * @type String
34466    */
34467    SimpleDialog.ICON_INFO  = "infoicon";
34468    
34469    /**
34470    * Constant for the standard network icon for warn
34471    * @property YAHOO.widget.SimpleDialog.ICON_WARN
34472    * @static
34473    * @final
34474    * @type String
34475    */
34476    SimpleDialog.ICON_WARN  = "warnicon";
34477    
34478    /**
34479    * Constant for the standard network icon for a tip
34480    * @property YAHOO.widget.SimpleDialog.ICON_TIP
34481    * @static
34482    * @final
34483    * @type String
34484    */
34485    SimpleDialog.ICON_TIP   = "tipicon";
34486
34487    /**
34488    * Constant representing the name of the CSS class applied to the element 
34489    * created by the "icon" configuration property.
34490    * @property YAHOO.widget.SimpleDialog.ICON_CSS_CLASSNAME
34491    * @static
34492    * @final
34493    * @type String
34494    */
34495    SimpleDialog.ICON_CSS_CLASSNAME = "yui-icon";
34496    
34497    /**
34498    * Constant representing the default CSS class used for a SimpleDialog
34499    * @property YAHOO.widget.SimpleDialog.CSS_SIMPLEDIALOG
34500    * @static
34501    * @final
34502    * @type String
34503    */
34504    SimpleDialog.CSS_SIMPLEDIALOG = "yui-simple-dialog";
34505
34506    
34507    YAHOO.extend(SimpleDialog, YAHOO.widget.Dialog, {
34508    
34509        /**
34510        * Initializes the class's configurable properties which can be changed 
34511        * using the SimpleDialog's Config object (cfg).
34512        * @method initDefaultConfig
34513        */
34514        initDefaultConfig: function () {
34515        
34516            SimpleDialog.superclass.initDefaultConfig.call(this);
34517        
34518            // Add dialog config properties //
34519        
34520            /**
34521            * Sets the informational icon for the SimpleDialog
34522            * @config icon
34523            * @type String
34524            * @default "none"
34525            */
34526            this.cfg.addProperty(DEFAULT_CONFIG.ICON.key, {
34527                handler: this.configIcon,
34528                value: DEFAULT_CONFIG.ICON.value,
34529                suppressEvent: DEFAULT_CONFIG.ICON.suppressEvent
34530            });
34531        
34532            /**
34533            * Sets the text for the SimpleDialog. The text is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
34534            * @config text
34535            * @type HTML
34536            * @default ""
34537            */
34538            this.cfg.addProperty(DEFAULT_CONFIG.TEXT.key, { 
34539                handler: this.configText, 
34540                value: DEFAULT_CONFIG.TEXT.value, 
34541                suppressEvent: DEFAULT_CONFIG.TEXT.suppressEvent, 
34542                supercedes: DEFAULT_CONFIG.TEXT.supercedes 
34543            });
34544        
34545        },
34546        
34547        
34548        /**
34549        * The SimpleDialog initialization method, which is executed for 
34550        * SimpleDialog and all of its subclasses. This method is automatically 
34551        * called by the constructor, and  sets up all DOM references for 
34552        * pre-existing markup, and creates required markup if it is not 
34553        * already present.
34554        * @method init
34555        * @param {String} el The element ID representing the SimpleDialog 
34556        * <em>OR</em>
34557        * @param {HTMLElement} el The element representing the SimpleDialog
34558        * @param {Object} userConfig The configuration object literal 
34559        * containing the configuration that should be set for this 
34560        * SimpleDialog. See configuration documentation for more details.
34561        */
34562        init: function (el, userConfig) {
34563
34564            /*
34565                Note that we don't pass the user config in here yet because we 
34566                only want it executed once, at the lowest subclass level
34567            */
34568
34569            SimpleDialog.superclass.init.call(this, el/*, userConfig*/);
34570        
34571            this.beforeInitEvent.fire(SimpleDialog);
34572        
34573            Dom.addClass(this.element, SimpleDialog.CSS_SIMPLEDIALOG);
34574        
34575            this.cfg.queueProperty("postmethod", "manual");
34576        
34577            if (userConfig) {
34578                this.cfg.applyConfig(userConfig, true);
34579            }
34580        
34581            this.beforeRenderEvent.subscribe(function () {
34582                if (! this.body) {
34583                    this.setBody("");
34584                }
34585            }, this, true);
34586        
34587            this.initEvent.fire(SimpleDialog);
34588        
34589        },
34590        
34591        /**
34592        * Prepares the SimpleDialog's internal FORM object, creating one if one 
34593        * is not currently present, and adding the value hidden field.
34594        * @method registerForm
34595        */
34596        registerForm: function () {
34597            SimpleDialog.superclass.registerForm.call(this);
34598
34599            var doc = this.form.ownerDocument,
34600                input = doc.createElement("input");
34601
34602            input.type = "hidden";
34603            input.name = this.id;
34604            input.value = "";
34605
34606            this.form.appendChild(input);
34607        },
34608
34609        // BEGIN BUILT-IN PROPERTY EVENT HANDLERS //
34610        
34611        /**
34612        * Fired when the "icon" property is set.
34613        * @method configIcon
34614        * @param {String} type The CustomEvent type (usually the property name)
34615        * @param {Object[]} args The CustomEvent arguments. For configuration 
34616        * handlers, args[0] will equal the newly applied value for the property.
34617        * @param {Object} obj The scope object. For configuration handlers, 
34618        * this will usually equal the owner.
34619        */
34620        configIcon: function (type,args,obj) {
34621        
34622            var sIcon = args[0],
34623                oBody = this.body,
34624                sCSSClass = SimpleDialog.ICON_CSS_CLASSNAME,
34625				aElements,
34626                oIcon,
34627                oIconParent;
34628        
34629            if (sIcon && sIcon != "none") {
34630
34631                aElements = Dom.getElementsByClassName(sCSSClass, "*" , oBody);
34632
34633				if (aElements.length === 1) {
34634
34635					oIcon = aElements[0];
34636                    oIconParent = oIcon.parentNode;
34637
34638                    if (oIconParent) {
34639
34640                        oIconParent.removeChild(oIcon);
34641
34642                        oIcon = null;
34643
34644                    }
34645
34646				}
34647
34648
34649                if (sIcon.indexOf(".") == -1) {
34650
34651                    oIcon = document.createElement("span");
34652                    oIcon.className = (sCSSClass + " " + sIcon);
34653                    oIcon.innerHTML = "&#160;";
34654
34655                } else {
34656
34657                    oIcon = document.createElement("img");
34658                    oIcon.src = (this.imageRoot + sIcon);
34659                    oIcon.className = sCSSClass;
34660
34661                }
34662                
34663
34664                if (oIcon) {
34665                
34666                    oBody.insertBefore(oIcon, oBody.firstChild);
34667                
34668                }
34669
34670            }
34671
34672        },
34673
34674        /**
34675        * Fired when the "text" property is set.
34676        * @method configText
34677        * @param {String} type The CustomEvent type (usually the property name)
34678        * @param {Object[]} args The CustomEvent arguments. For configuration 
34679        * handlers, args[0] will equal the newly applied value for the property.
34680        * @param {Object} obj The scope object. For configuration handlers, 
34681        * this will usually equal the owner.
34682        */
34683        configText: function (type,args,obj) {
34684            var text = args[0];
34685            if (text) {
34686                this.setBody(text);
34687                this.cfg.refireEvent("icon");
34688            }
34689        },
34690        
34691        // END BUILT-IN PROPERTY EVENT HANDLERS //
34692        
34693        /**
34694        * Returns a string representation of the object.
34695        * @method toString
34696        * @return {String} The string representation of the SimpleDialog
34697        */
34698        toString: function () {
34699            return "SimpleDialog " + this.id;
34700        }
34701
34702        /**
34703        * <p>
34704        * Sets the SimpleDialog's body content to the HTML specified. 
34705        * If no body is present, one will be automatically created. 
34706        * An empty string can be passed to the method to clear the contents of the body.
34707        * </p>
34708        * <p><strong>NOTE:</strong> SimpleDialog provides the <a href="#config_text">text</a>
34709        * and <a href="#config_icon">icon</a> configuration properties to set the contents
34710        * of it's body element in accordance with the UI design for a SimpleDialog (an 
34711        * icon and message text). Calling setBody on the SimpleDialog will not enforce this 
34712        * UI design constraint and will replace the entire contents of the SimpleDialog body. 
34713        * It should only be used if you wish the replace the default icon/text body structure 
34714        * of a SimpleDialog with your own custom markup.</p>
34715        * 
34716        * @method setBody
34717        * @param {HTML} bodyContent The HTML used to set the body. 
34718        * As a convenience, non HTMLElement objects can also be passed into 
34719        * the method, and will be treated as strings, with the body innerHTML
34720        * set to their default toString implementations.
34721        * 
34722        * <p>NOTE: Markup passed into this method is added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.</p>
34723        * 
34724        * <em>OR</em>
34725        * @param {HTMLElement} bodyContent The HTMLElement to add as the first and only child of the body element.
34726        * <em>OR</em>
34727        * @param {DocumentFragment} bodyContent The document fragment 
34728        * containing elements which are to be added to the body
34729        */
34730    });
34731
34732}());
34733(function () {
34734
34735    /**
34736    * ContainerEffect encapsulates animation transitions that are executed when 
34737    * an Overlay is shown or hidden.
34738    * @namespace YAHOO.widget
34739    * @class ContainerEffect
34740    * @constructor
34741    * @param {YAHOO.widget.Overlay} overlay The Overlay that the animation 
34742    * should be associated with
34743    * @param {Object} attrIn The object literal representing the animation 
34744    * arguments to be used for the animate-in transition. The arguments for 
34745    * this literal are: attributes(object, see YAHOO.util.Anim for description), 
34746    * duration(Number), and method(i.e. Easing.easeIn).
34747    * @param {Object} attrOut The object literal representing the animation 
34748    * arguments to be used for the animate-out transition. The arguments for  
34749    * this literal are: attributes(object, see YAHOO.util.Anim for description), 
34750    * duration(Number), and method(i.e. Easing.easeIn).
34751    * @param {HTMLElement} targetElement Optional. The target element that  
34752    * should be animated during the transition. Defaults to overlay.element.
34753    * @param {class} Optional. The animation class to instantiate. Defaults to 
34754    * YAHOO.util.Anim. Other options include YAHOO.util.Motion.
34755    */
34756    YAHOO.widget.ContainerEffect = function (overlay, attrIn, attrOut, targetElement, animClass) {
34757
34758        if (!animClass) {
34759            animClass = YAHOO.util.Anim;
34760        }
34761
34762        /**
34763        * The overlay to animate
34764        * @property overlay
34765        * @type YAHOO.widget.Overlay
34766        */
34767        this.overlay = overlay;
34768    
34769        /**
34770        * The animation attributes to use when transitioning into view
34771        * @property attrIn
34772        * @type Object
34773        */
34774        this.attrIn = attrIn;
34775    
34776        /**
34777        * The animation attributes to use when transitioning out of view
34778        * @property attrOut
34779        * @type Object
34780        */
34781        this.attrOut = attrOut;
34782    
34783        /**
34784        * The target element to be animated
34785        * @property targetElement
34786        * @type HTMLElement
34787        */
34788        this.targetElement = targetElement || overlay.element;
34789    
34790        /**
34791        * The animation class to use for animating the overlay
34792        * @property animClass
34793        * @type class
34794        */
34795        this.animClass = animClass;
34796    };
34797
34798    var Dom = YAHOO.util.Dom,
34799        CustomEvent = YAHOO.util.CustomEvent,
34800        ContainerEffect = YAHOO.widget.ContainerEffect;
34801
34802    /**
34803    * A pre-configured ContainerEffect instance that can be used for fading 
34804    * an overlay in and out.
34805    * @method FADE
34806    * @static
34807    * @param {YAHOO.widget.Overlay} overlay The Overlay object to animate
34808    * @param {Number} dur The duration of the animation
34809    * @return {YAHOO.widget.ContainerEffect} The configured ContainerEffect object
34810    */
34811    ContainerEffect.FADE = function (overlay, dur) {
34812
34813        var Easing = YAHOO.util.Easing,
34814            fin = {
34815                attributes: {opacity:{from:0, to:1}},
34816                duration: dur,
34817                method: Easing.easeIn
34818            },
34819            fout = {
34820                attributes: {opacity:{to:0}},
34821                duration: dur,
34822                method: Easing.easeOut
34823            },
34824            fade = new ContainerEffect(overlay, fin, fout, overlay.element);
34825
34826        fade.handleUnderlayStart = function() {
34827            var underlay = this.overlay.underlay;
34828            if (underlay && YAHOO.env.ua.ie) {
34829                var hasFilters = (underlay.filters && underlay.filters.length > 0);
34830                if(hasFilters) {
34831                    Dom.addClass(overlay.element, "yui-effect-fade");
34832                }
34833            }
34834        };
34835
34836        fade.handleUnderlayComplete = function() {
34837            var underlay = this.overlay.underlay;
34838            if (underlay && YAHOO.env.ua.ie) {
34839                Dom.removeClass(overlay.element, "yui-effect-fade");
34840            }
34841        };
34842
34843        fade.handleStartAnimateIn = function (type, args, obj) {
34844            obj.overlay._fadingIn = true;
34845
34846            Dom.addClass(obj.overlay.element, "hide-select");
34847
34848            if (!obj.overlay.underlay) {
34849                obj.overlay.cfg.refireEvent("underlay");
34850            }
34851
34852            obj.handleUnderlayStart();
34853
34854            obj.overlay._setDomVisibility(true);
34855            Dom.setStyle(obj.overlay.element, "opacity", 0);
34856        };
34857
34858        fade.handleCompleteAnimateIn = function (type,args,obj) {
34859            obj.overlay._fadingIn = false;
34860            
34861            Dom.removeClass(obj.overlay.element, "hide-select");
34862
34863            if (obj.overlay.element.style.filter) {
34864                obj.overlay.element.style.filter = null;
34865            }
34866
34867            obj.handleUnderlayComplete();
34868
34869            obj.overlay.cfg.refireEvent("iframe");
34870            obj.animateInCompleteEvent.fire();
34871        };
34872
34873        fade.handleStartAnimateOut = function (type, args, obj) {
34874            obj.overlay._fadingOut = true;
34875            Dom.addClass(obj.overlay.element, "hide-select");
34876            obj.handleUnderlayStart();
34877        };
34878
34879        fade.handleCompleteAnimateOut =  function (type, args, obj) {
34880            obj.overlay._fadingOut = false;
34881            Dom.removeClass(obj.overlay.element, "hide-select");
34882
34883            if (obj.overlay.element.style.filter) {
34884                obj.overlay.element.style.filter = null;
34885            }
34886            obj.overlay._setDomVisibility(false);
34887            Dom.setStyle(obj.overlay.element, "opacity", 1);
34888
34889            obj.handleUnderlayComplete();
34890
34891            obj.overlay.cfg.refireEvent("iframe");
34892            obj.animateOutCompleteEvent.fire();
34893        };
34894
34895        fade.init();
34896        return fade;
34897    };
34898    
34899    
34900    /**
34901    * A pre-configured ContainerEffect instance that can be used for sliding an 
34902    * overlay in and out.
34903    * @method SLIDE
34904    * @static
34905    * @param {YAHOO.widget.Overlay} overlay The Overlay object to animate
34906    * @param {Number} dur The duration of the animation
34907    * @return {YAHOO.widget.ContainerEffect} The configured ContainerEffect object
34908    */
34909    ContainerEffect.SLIDE = function (overlay, dur) {
34910        var Easing = YAHOO.util.Easing,
34911
34912            x = overlay.cfg.getProperty("x") || Dom.getX(overlay.element),
34913            y = overlay.cfg.getProperty("y") || Dom.getY(overlay.element),
34914            clientWidth = Dom.getClientWidth(),
34915            offsetWidth = overlay.element.offsetWidth,
34916
34917            sin =  { 
34918                attributes: { points: { to: [x, y] } },
34919                duration: dur,
34920                method: Easing.easeIn 
34921            },
34922
34923            sout = {
34924                attributes: { points: { to: [(clientWidth + 25), y] } },
34925                duration: dur,
34926                method: Easing.easeOut 
34927            },
34928
34929            slide = new ContainerEffect(overlay, sin, sout, overlay.element, YAHOO.util.Motion);
34930
34931        slide.handleStartAnimateIn = function (type,args,obj) {
34932            obj.overlay.element.style.left = ((-25) - offsetWidth) + "px";
34933            obj.overlay.element.style.top  = y + "px";
34934        };
34935
34936        slide.handleTweenAnimateIn = function (type, args, obj) {
34937        
34938            var pos = Dom.getXY(obj.overlay.element),
34939                currentX = pos[0],
34940                currentY = pos[1];
34941        
34942            if (Dom.getStyle(obj.overlay.element, "visibility") == 
34943                "hidden" && currentX < x) {
34944
34945                obj.overlay._setDomVisibility(true);
34946
34947            }
34948        
34949            obj.overlay.cfg.setProperty("xy", [currentX, currentY], true);
34950            obj.overlay.cfg.refireEvent("iframe");
34951        };
34952        
34953        slide.handleCompleteAnimateIn = function (type, args, obj) {
34954            obj.overlay.cfg.setProperty("xy", [x, y], true);
34955            obj.startX = x;
34956            obj.startY = y;
34957            obj.overlay.cfg.refireEvent("iframe");
34958            obj.animateInCompleteEvent.fire();
34959        };
34960
34961        slide.handleStartAnimateOut = function (type, args, obj) {
34962    
34963            var vw = Dom.getViewportWidth(),
34964                pos = Dom.getXY(obj.overlay.element),
34965                yso = pos[1];
34966    
34967            obj.animOut.attributes.points.to = [(vw + 25), yso];
34968        };
34969        
34970        slide.handleTweenAnimateOut = function (type, args, obj) {
34971    
34972            var pos = Dom.getXY(obj.overlay.element),
34973                xto = pos[0],
34974                yto = pos[1];
34975        
34976            obj.overlay.cfg.setProperty("xy", [xto, yto], true);
34977            obj.overlay.cfg.refireEvent("iframe");
34978        };
34979        
34980        slide.handleCompleteAnimateOut = function (type, args, obj) {
34981            obj.overlay._setDomVisibility(false);
34982
34983            obj.overlay.cfg.setProperty("xy", [x, y]);
34984            obj.animateOutCompleteEvent.fire();
34985        };
34986
34987        slide.init();
34988        return slide;
34989    };
34990
34991    ContainerEffect.prototype = {
34992
34993        /**
34994        * Initializes the animation classes and events.
34995        * @method init
34996        */
34997        init: function () {
34998
34999            this.beforeAnimateInEvent = this.createEvent("beforeAnimateIn");
35000            this.beforeAnimateInEvent.signature = CustomEvent.LIST;
35001            
35002            this.beforeAnimateOutEvent = this.createEvent("beforeAnimateOut");
35003            this.beforeAnimateOutEvent.signature = CustomEvent.LIST;
35004        
35005            this.animateInCompleteEvent = this.createEvent("animateInComplete");
35006            this.animateInCompleteEvent.signature = CustomEvent.LIST;
35007        
35008            this.animateOutCompleteEvent = this.createEvent("animateOutComplete");
35009            this.animateOutCompleteEvent.signature = CustomEvent.LIST;
35010
35011            this.animIn = new this.animClass(
35012                this.targetElement, 
35013                this.attrIn.attributes, 
35014                this.attrIn.duration, 
35015                this.attrIn.method);
35016
35017            this.animIn.onStart.subscribe(this.handleStartAnimateIn, this);
35018            this.animIn.onTween.subscribe(this.handleTweenAnimateIn, this);
35019            this.animIn.onComplete.subscribe(this.handleCompleteAnimateIn,this);
35020        
35021            this.animOut = new this.animClass(
35022                this.targetElement, 
35023                this.attrOut.attributes, 
35024                this.attrOut.duration, 
35025                this.attrOut.method);
35026
35027            this.animOut.onStart.subscribe(this.handleStartAnimateOut, this);
35028            this.animOut.onTween.subscribe(this.handleTweenAnimateOut, this);
35029            this.animOut.onComplete.subscribe(this.handleCompleteAnimateOut, this);
35030
35031        },
35032
35033        /**
35034        * Triggers the in-animation.
35035        * @method animateIn
35036        */
35037        animateIn: function () {
35038            this._stopAnims(this.lastFrameOnStop);
35039            this.beforeAnimateInEvent.fire();
35040            this.animIn.animate();
35041        },
35042
35043        /**
35044        * Triggers the out-animation.
35045        * @method animateOut
35046        */
35047        animateOut: function () {
35048            this._stopAnims(this.lastFrameOnStop);
35049            this.beforeAnimateOutEvent.fire();
35050            this.animOut.animate();
35051        },
35052        
35053        /**
35054         * Flag to define whether Anim should jump to the last frame,
35055         * when animateIn or animateOut is stopped.
35056         *
35057         * @property lastFrameOnStop
35058         * @default true
35059         * @type boolean
35060         */
35061        lastFrameOnStop : true,
35062
35063        /**
35064         * Stops both animIn and animOut instances, if in progress.
35065         *
35066         * @method _stopAnims
35067         * @param {boolean} finish If true, animation will jump to final frame.
35068         * @protected
35069         */
35070        _stopAnims : function(finish) {
35071            if (this.animOut && this.animOut.isAnimated()) {
35072                this.animOut.stop(finish);
35073            }
35074
35075            if (this.animIn && this.animIn.isAnimated()) {
35076                this.animIn.stop(finish);
35077            }
35078        },
35079
35080        /**
35081        * The default onStart handler for the in-animation.
35082        * @method handleStartAnimateIn
35083        * @param {String} type The CustomEvent type
35084        * @param {Object[]} args The CustomEvent arguments
35085        * @param {Object} obj The scope object
35086        */
35087        handleStartAnimateIn: function (type, args, obj) { },
35088
35089        /**
35090        * The default onTween handler for the in-animation.
35091        * @method handleTweenAnimateIn
35092        * @param {String} type The CustomEvent type
35093        * @param {Object[]} args The CustomEvent arguments
35094        * @param {Object} obj The scope object
35095        */
35096        handleTweenAnimateIn: function (type, args, obj) { },
35097
35098        /**
35099        * The default onComplete handler for the in-animation.
35100        * @method handleCompleteAnimateIn
35101        * @param {String} type The CustomEvent type
35102        * @param {Object[]} args The CustomEvent arguments
35103        * @param {Object} obj The scope object
35104        */
35105        handleCompleteAnimateIn: function (type, args, obj) { },
35106
35107        /**
35108        * The default onStart handler for the out-animation.
35109        * @method handleStartAnimateOut
35110        * @param {String} type The CustomEvent type
35111        * @param {Object[]} args The CustomEvent arguments
35112        * @param {Object} obj The scope object
35113        */
35114        handleStartAnimateOut: function (type, args, obj) { },
35115
35116        /**
35117        * The default onTween handler for the out-animation.
35118        * @method handleTweenAnimateOut
35119        * @param {String} type The CustomEvent type
35120        * @param {Object[]} args The CustomEvent arguments
35121        * @param {Object} obj The scope object
35122        */
35123        handleTweenAnimateOut: function (type, args, obj) { },
35124
35125        /**
35126        * The default onComplete handler for the out-animation.
35127        * @method handleCompleteAnimateOut
35128        * @param {String} type The CustomEvent type
35129        * @param {Object[]} args The CustomEvent arguments
35130        * @param {Object} obj The scope object
35131        */
35132        handleCompleteAnimateOut: function (type, args, obj) { },
35133        
35134        /**
35135        * Returns a string representation of the object.
35136        * @method toString
35137        * @return {String} The string representation of the ContainerEffect
35138        */
35139        toString: function () {
35140            var output = "ContainerEffect";
35141            if (this.overlay) {
35142                output += " [" + this.overlay.toString() + "]";
35143            }
35144            return output;
35145        }
35146    };
35147
35148    YAHOO.lang.augmentProto(ContainerEffect, YAHOO.util.EventProvider);
35149
35150})();
35151YAHOO.register("container", YAHOO.widget.Module, {version: "2.9.0", build: "2800"});
35152/*
35153Copyright (c) 2011, Yahoo! Inc. All rights reserved.
35154Code licensed under the BSD License:
35155http://developer.yahoo.com/yui/license.html
35156version: 2.9.0
35157*/
35158
35159
35160/**
35161* @module menu
35162* @description <p>The Menu family of components features a collection of 
35163* controls that make it easy to add menus to your website or web application.  
35164* With the Menu Controls you can create website fly-out menus, customized 
35165* context menus, or application-style menu bars with just a small amount of 
35166* scripting.</p><p>The Menu family of controls features:</p>
35167* <ul>
35168*    <li>Keyboard and mouse navigation.</li>
35169*    <li>A rich event model that provides access to all of a menu's 
35170*    interesting moments.</li>
35171*    <li>Support for 
35172*    <a href="http://en.wikipedia.org/wiki/Progressive_Enhancement">Progressive
35173*    Enhancement</a>; Menus can be created from simple, 
35174*    semantic markup on the page or purely through JavaScript.</li>
35175* </ul>
35176* @title Menu
35177* @namespace YAHOO.widget
35178* @requires Event, Dom, Container
35179*/
35180(function () {
35181
35182    var UA = YAHOO.env.ua,
35183        Dom = YAHOO.util.Dom,
35184        Event = YAHOO.util.Event,
35185        Lang = YAHOO.lang,
35186
35187        _DIV = "DIV",
35188        _HD = "hd",
35189        _BD = "bd",
35190        _FT = "ft",
35191        _LI = "LI",
35192        _DISABLED = "disabled",
35193        _MOUSEOVER = "mouseover",
35194        _MOUSEOUT = "mouseout",
35195        _MOUSEDOWN = "mousedown",
35196        _MOUSEUP = "mouseup",
35197        _CLICK = "click",
35198        _TOUCHSTART = "touchstart",
35199        _TOUCHEND = "touchend",
35200        _KEYDOWN = "keydown",
35201        _KEYUP = "keyup",
35202        _KEYPRESS = "keypress",
35203        _CLICK_TO_HIDE = "clicktohide",
35204        _POSITION = "position", 
35205        _DYNAMIC = "dynamic",
35206        _SHOW_DELAY = "showdelay",
35207        _SELECTED = "selected",
35208        _VISIBLE = "visible",
35209        _UL = "UL",
35210        _MENUMANAGER = "MenuManager";
35211
35212
35213    /**
35214    * Singleton that manages a collection of all menus and menu items.  Listens 
35215    * for DOM events at the document level and dispatches the events to the 
35216    * corresponding menu or menu item.
35217    *
35218    * @namespace YAHOO.widget
35219    * @class MenuManager
35220    * @static
35221    */
35222    YAHOO.widget.MenuManager = function () {
35223    
35224        // Private member variables
35225    
35226    
35227        // Flag indicating if the DOM event handlers have been attached
35228    
35229        var m_bInitializedEventHandlers = false,
35230    
35231    
35232        // Collection of menus
35233
35234        m_oMenus = {},
35235
35236
35237        // Collection of visible menus
35238    
35239        m_oVisibleMenus = {},
35240    
35241    
35242        //  Collection of menu items 
35243
35244        m_oItems = {},
35245
35246
35247        // Map of DOM event types to their equivalent CustomEvent types
35248        
35249        m_oEventTypes = {
35250            "click": "clickEvent",
35251            "mousedown": "mouseDownEvent",
35252            "mouseup": "mouseUpEvent",
35253            "mouseover": "mouseOverEvent",
35254            "mouseout": "mouseOutEvent",
35255            "touchstart": "mouseOverEvent",
35256            "touchend": "mouseUpEvent",
35257            "keydown": "keyDownEvent",
35258            "keyup": "keyUpEvent",
35259            "keypress": "keyPressEvent",
35260            "focus": "focusEvent",
35261            "focusin": "focusEvent",
35262            "blur": "blurEvent",
35263            "focusout": "blurEvent"
35264        },
35265    
35266    
35267        m_oFocusedMenuItem = null;
35268    
35269    
35270    
35271        // Private methods
35272    
35273    
35274        /**
35275        * @method getMenuRootElement
35276        * @description Finds the root DIV node of a menu or the root LI node of 
35277        * a menu item.
35278        * @private
35279        * @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
35280        * level-one-html.html#ID-58190037">HTMLElement</a>} p_oElement Object 
35281        * specifying an HTML element.
35282        */
35283        function getMenuRootElement(p_oElement) {
35284        
35285            var oParentNode,
35286                returnVal;
35287    
35288            if (p_oElement && p_oElement.tagName) {
35289            
35290                switch (p_oElement.tagName.toUpperCase()) {
35291                        
35292                case _DIV:
35293    
35294                    oParentNode = p_oElement.parentNode;
35295    
35296                    // Check if the DIV is the inner "body" node of a menu
35297
35298                    if ((
35299                            Dom.hasClass(p_oElement, _HD) ||
35300                            Dom.hasClass(p_oElement, _BD) ||
35301                            Dom.hasClass(p_oElement, _FT)
35302                        ) && 
35303                        oParentNode && 
35304                        oParentNode.tagName && 
35305                        oParentNode.tagName.toUpperCase() == _DIV) {
35306                    
35307                        returnVal = oParentNode;
35308                    
35309                    }
35310                    else {
35311                    
35312                        returnVal = p_oElement;
35313                    
35314                    }
35315                
35316                    break;
35317
35318                case _LI:
35319    
35320                    returnVal = p_oElement;
35321                    
35322                    break;
35323
35324                default:
35325    
35326                    oParentNode = p_oElement.parentNode;
35327    
35328                    if (oParentNode) {
35329                    
35330                        returnVal = getMenuRootElement(oParentNode);
35331                    
35332                    }
35333                
35334                    break;
35335                
35336                }
35337    
35338            }
35339            
35340            return returnVal;
35341            
35342        }
35343    
35344    
35345    
35346        // Private event handlers
35347    
35348    
35349        /**
35350        * @method onDOMEvent
35351        * @description Generic, global event handler for all of a menu's 
35352        * DOM-based events.  This listens for events against the document 
35353        * object.  If the target of a given event is a member of a menu or 
35354        * menu item's DOM, the instance's corresponding Custom Event is fired.
35355        * @private
35356        * @param {Event} p_oEvent Object representing the DOM event object  
35357        * passed back by the event utility (YAHOO.util.Event).
35358        */
35359        function onDOMEvent(p_oEvent) {
35360    
35361            // Get the target node of the DOM event
35362        
35363            var oTarget = Event.getTarget(p_oEvent),
35364                
35365            // See if the target of the event was a menu, or a menu item
35366    
35367            oElement = getMenuRootElement(oTarget),
35368            bFireEvent = true,
35369            sEventType = p_oEvent.type,
35370            sCustomEventType,
35371            sTagName,
35372            sId,
35373            oMenuItem,
35374            oMenu; 
35375    
35376    
35377            if (oElement) {
35378    
35379                sTagName = oElement.tagName.toUpperCase();
35380        
35381                if (sTagName == _LI) {
35382            
35383                    sId = oElement.id;
35384            
35385                    if (sId && m_oItems[sId]) {
35386            
35387                        oMenuItem = m_oItems[sId];
35388                        oMenu = oMenuItem.parent;
35389            
35390                    }
35391                
35392                }
35393                else if (sTagName == _DIV) {
35394                
35395                    if (oElement.id) {
35396                    
35397                        oMenu = m_oMenus[oElement.id];
35398                    
35399                    }
35400                
35401                }
35402    
35403            }
35404    
35405    
35406            if (oMenu) {
35407    
35408                sCustomEventType = m_oEventTypes[sEventType];
35409
35410                /*
35411                    There is an inconsistency between Firefox for Mac OS X and 
35412                    Firefox Windows & Linux regarding the triggering of the 
35413                    display of the browser's context menu and the subsequent 
35414                    firing of the "click" event. In Firefox for Windows & Linux, 
35415                    when the user triggers the display of the browser's context 
35416                    menu the "click" event also fires for the document object, 
35417                    even though the "click" event did not fire for the element 
35418                    that was the original target of the "contextmenu" event. 
35419                    This is unique to Firefox on Windows & Linux.  For all 
35420                    other A-Grade browsers, including Firefox for Mac OS X, the 
35421                    "click" event doesn't fire for the document object. 
35422
35423                    This bug in Firefox for Windows affects Menu, as Menu 
35424                    instances listen for events at the document level and 
35425                    dispatches Custom Events of the same name.  Therefore users
35426                    of Menu will get an unwanted firing of the "click" 
35427                    custom event.  The following line fixes this bug.
35428                */
35429                
35430
35431
35432                if (sEventType == "click" && 
35433                    (UA.gecko && oMenu.platform != "mac") && 
35434                    p_oEvent.button > 0) {
35435
35436                    bFireEvent = false;
35437
35438                }
35439    
35440                // Fire the Custom Event that corresponds the current DOM event    
35441        
35442                if (bFireEvent && oMenuItem && !oMenuItem.cfg.getProperty(_DISABLED)) {
35443                    oMenuItem[sCustomEventType].fire(p_oEvent);                   
35444                }
35445        
35446                if (bFireEvent) {
35447                    oMenu[sCustomEventType].fire(p_oEvent, oMenuItem);
35448                }
35449            
35450            }
35451            else if (sEventType == _MOUSEDOWN) {
35452    
35453                /*
35454                    If the target of the event wasn't a menu, hide all 
35455                    dynamically positioned menus
35456                */
35457                
35458                for (var i in m_oVisibleMenus) {
35459        
35460                    if (Lang.hasOwnProperty(m_oVisibleMenus, i)) {
35461        
35462                        oMenu = m_oVisibleMenus[i];
35463
35464                        if (oMenu.cfg.getProperty(_CLICK_TO_HIDE) && 
35465                            !(oMenu instanceof YAHOO.widget.MenuBar) && 
35466                            oMenu.cfg.getProperty(_POSITION) == _DYNAMIC) {
35467
35468                            oMenu.hide();
35469
35470                            //	In IE when the user mouses down on a focusable 
35471                            //	element that element will be focused and become 
35472                            //	the "activeElement".
35473                            //	(http://msdn.microsoft.com/en-us/library/ms533065(VS.85).aspx)
35474                            //	However, there is a bug in IE where if there is 
35475                            //	a positioned element with a focused descendant 
35476                            //	that is hidden in response to the mousedown 
35477                            //	event, the target of the mousedown event will 
35478                            //	appear to have focus, but will not be set as 
35479                            //	the activeElement.  This will result in the 
35480                            //	element not firing key events, even though it
35481                            //	appears to have focus.  The following call to 
35482                            //	"setActive" fixes this bug.
35483
35484                            if (UA.ie && oTarget.focus && (UA.ie < 9)) {
35485                                oTarget.setActive();
35486                            }
35487        
35488                        }
35489                        else {
35490                            
35491                            if (oMenu.cfg.getProperty(_SHOW_DELAY) > 0) {
35492                            
35493                                oMenu._cancelShowDelay();
35494                            
35495                            }
35496
35497
35498                            if (oMenu.activeItem) {
35499                        
35500                                oMenu.activeItem.blur();
35501                                oMenu.activeItem.cfg.setProperty(_SELECTED, false);
35502                        
35503                                oMenu.activeItem = null;            
35504                        
35505                            }
35506        
35507                        }
35508        
35509                    }
35510        
35511                } 
35512    
35513            }
35514            else if (sEventType == _TOUCHEND) {
35515                for (var i in m_oVisibleMenus) {
35516                    if (Lang.hasOwnProperty(m_oVisibleMenus, i)) {
35517                        oMenu = m_oVisibleMenus[i];
35518                        oMenu.hide();
35519                    }
35520                }
35521            }
35522            
35523        }
35524    
35525    
35526        /**
35527        * @method onMenuDestroy
35528        * @description "destroy" event handler for a menu.
35529        * @private
35530        * @param {String} p_sType String representing the name of the event 
35531        * that was fired.
35532        * @param {Array} p_aArgs Array of arguments sent when the event 
35533        * was fired.
35534        * @param {YAHOO.widget.Menu} p_oMenu The menu that fired the event.
35535        */
35536        function onMenuDestroy(p_sType, p_aArgs, p_oMenu) {
35537    
35538            if (m_oMenus[p_oMenu.id]) {
35539    
35540                this.removeMenu(p_oMenu);
35541    
35542            }
35543    
35544        }
35545    
35546    
35547        /**
35548        * @method onMenuFocus
35549        * @description "focus" event handler for a MenuItem instance.
35550        * @private
35551        * @param {String} p_sType String representing the name of the event 
35552        * that was fired.
35553        * @param {Array} p_aArgs Array of arguments sent when the event 
35554        * was fired.
35555        */
35556        function onMenuFocus(p_sType, p_aArgs) {
35557    
35558            var oItem = p_aArgs[1];
35559    
35560            if (oItem) {
35561    
35562                m_oFocusedMenuItem = oItem;
35563            
35564            }
35565    
35566        }
35567    
35568    
35569        /**
35570        * @method onMenuBlur
35571        * @description "blur" event handler for a MenuItem instance.
35572        * @private
35573        * @param {String} p_sType String representing the name of the event  
35574        * that was fired.
35575        * @param {Array} p_aArgs Array of arguments sent when the event 
35576        * was fired.
35577        */
35578        function onMenuBlur(p_sType, p_aArgs) {
35579    
35580            m_oFocusedMenuItem = null;
35581    
35582        }
35583
35584    
35585        /**
35586        * @method onMenuVisibleConfigChange
35587        * @description Event handler for when the "visible" configuration  
35588        * property of a Menu instance changes.
35589        * @private
35590        * @param {String} p_sType String representing the name of the event  
35591        * that was fired.
35592        * @param {Array} p_aArgs Array of arguments sent when the event 
35593        * was fired.
35594        */
35595        function onMenuVisibleConfigChange(p_sType, p_aArgs) {
35596    
35597            var bVisible = p_aArgs[0],
35598                sId = this.id;
35599            
35600            if (bVisible) {
35601    
35602                m_oVisibleMenus[sId] = this;
35603                
35604                YAHOO.log(this + " added to the collection of visible menus.", 
35605                    "info", _MENUMANAGER);
35606            
35607            }
35608            else if (m_oVisibleMenus[sId]) {
35609            
35610                delete m_oVisibleMenus[sId];
35611                
35612                YAHOO.log(this + " removed from the collection of visible menus.", 
35613                    "info", _MENUMANAGER);
35614            
35615            }
35616        
35617        }
35618    
35619    
35620        /**
35621        * @method onItemDestroy
35622        * @description "destroy" event handler for a MenuItem instance.
35623        * @private
35624        * @param {String} p_sType String representing the name of the event  
35625        * that was fired.
35626        * @param {Array} p_aArgs Array of arguments sent when the event 
35627        * was fired.
35628        */
35629        function onItemDestroy(p_sType, p_aArgs) {
35630    
35631            removeItem(this);
35632    
35633        }
35634
35635
35636        /**
35637        * @method removeItem
35638        * @description Removes a MenuItem instance from the MenuManager's collection of MenuItems.
35639        * @private
35640        * @param {MenuItem} p_oMenuItem The MenuItem instance to be removed.
35641        */    
35642        function removeItem(p_oMenuItem) {
35643
35644            var sId = p_oMenuItem.id;
35645    
35646            if (sId && m_oItems[sId]) {
35647    
35648                if (m_oFocusedMenuItem == p_oMenuItem) {
35649    
35650                    m_oFocusedMenuItem = null;
35651    
35652                }
35653    
35654                delete m_oItems[sId];
35655                
35656                p_oMenuItem.destroyEvent.unsubscribe(onItemDestroy);
35657    
35658                YAHOO.log(p_oMenuItem + " successfully unregistered.", "info", _MENUMANAGER);
35659    
35660            }
35661
35662        }
35663    
35664    
35665        /**
35666        * @method onItemAdded
35667        * @description "itemadded" event handler for a Menu instance.
35668        * @private
35669        * @param {String} p_sType String representing the name of the event  
35670        * that was fired.
35671        * @param {Array} p_aArgs Array of arguments sent when the event 
35672        * was fired.
35673        */
35674        function onItemAdded(p_sType, p_aArgs) {
35675    
35676            var oItem = p_aArgs[0],
35677                sId;
35678    
35679            if (oItem instanceof YAHOO.widget.MenuItem) { 
35680    
35681                sId = oItem.id;
35682        
35683                if (!m_oItems[sId]) {
35684            
35685                    m_oItems[sId] = oItem;
35686        
35687                    oItem.destroyEvent.subscribe(onItemDestroy);
35688        
35689                    YAHOO.log(oItem + " successfully registered.", "info", _MENUMANAGER);
35690        
35691                }
35692    
35693            }
35694        
35695        }
35696    
35697    
35698        return {
35699    
35700            // Privileged methods
35701    
35702    
35703            /**
35704            * @method addMenu
35705            * @description Adds a menu to the collection of known menus.
35706            * @param {YAHOO.widget.Menu} p_oMenu Object specifying the Menu  
35707            * instance to be added.
35708            */
35709            addMenu: function (p_oMenu) {
35710    
35711                var oDoc;
35712    
35713                if (p_oMenu instanceof YAHOO.widget.Menu && p_oMenu.id && 
35714                    !m_oMenus[p_oMenu.id]) {
35715        
35716                    m_oMenus[p_oMenu.id] = p_oMenu;
35717                
35718            
35719                    if (!m_bInitializedEventHandlers) {
35720            
35721                        oDoc = document;
35722                
35723                        Event.on(oDoc, _MOUSEOVER, onDOMEvent, this, true);
35724                        Event.on(oDoc, _MOUSEOUT, onDOMEvent, this, true);
35725                        Event.on(oDoc, _MOUSEDOWN, onDOMEvent, this, true);
35726                        Event.on(oDoc, _MOUSEUP, onDOMEvent, this, true);
35727                        Event.on(oDoc, _CLICK, onDOMEvent, this, true);
35728                        Event.on(oDoc, _TOUCHSTART, onDOMEvent, this, true);
35729                        Event.on(oDoc, _TOUCHEND, onDOMEvent, this, true);
35730                        Event.on(oDoc, _KEYDOWN, onDOMEvent, this, true);
35731                        Event.on(oDoc, _KEYUP, onDOMEvent, this, true);
35732                        Event.on(oDoc, _KEYPRESS, onDOMEvent, this, true);
35733    
35734                        Event.onFocus(oDoc, onDOMEvent, this, true);
35735                        Event.onBlur(oDoc, onDOMEvent, this, true);						
35736    
35737                        m_bInitializedEventHandlers = true;
35738                        
35739                        YAHOO.log("DOM event handlers initialized.", "info", _MENUMANAGER);
35740            
35741                    }
35742            
35743                    p_oMenu.cfg.subscribeToConfigEvent(_VISIBLE, onMenuVisibleConfigChange);
35744                    p_oMenu.destroyEvent.subscribe(onMenuDestroy, p_oMenu, this);
35745                    p_oMenu.itemAddedEvent.subscribe(onItemAdded);
35746                    p_oMenu.focusEvent.subscribe(onMenuFocus);
35747                    p_oMenu.blurEvent.subscribe(onMenuBlur);
35748        
35749                    YAHOO.log(p_oMenu + " successfully registered.", "info", _MENUMANAGER);
35750        
35751                }
35752        
35753            },
35754    
35755        
35756            /**
35757            * @method removeMenu
35758            * @description Removes a menu from the collection of known menus.
35759            * @param {YAHOO.widget.Menu} p_oMenu Object specifying the Menu  
35760            * instance to be removed.
35761            */
35762            removeMenu: function (p_oMenu) {
35763    
35764                var sId,
35765                    aItems,
35766                    i;
35767        
35768                if (p_oMenu) {
35769    
35770                    sId = p_oMenu.id;
35771        
35772                    if ((sId in m_oMenus) && (m_oMenus[sId] == p_oMenu)) {
35773
35774                        // Unregister each menu item
35775
35776                        aItems = p_oMenu.getItems();
35777
35778                        if (aItems && aItems.length > 0) {
35779
35780                            i = aItems.length - 1;
35781
35782                            do {
35783
35784                                removeItem(aItems[i]);
35785
35786                            }
35787                            while (i--);
35788
35789                        }
35790
35791
35792                        // Unregister the menu
35793
35794                        delete m_oMenus[sId];
35795            
35796                        YAHOO.log(p_oMenu + " successfully unregistered.", "info", _MENUMANAGER);
35797        
35798
35799                        /*
35800                             Unregister the menu from the collection of 
35801                             visible menus
35802                        */
35803
35804                        if ((sId in m_oVisibleMenus) && (m_oVisibleMenus[sId] == p_oMenu)) {
35805            
35806                            delete m_oVisibleMenus[sId];
35807                            
35808                            YAHOO.log(p_oMenu + " unregistered from the" + 
35809                                        " collection of visible menus.", "info", _MENUMANAGER);
35810       
35811                        }
35812
35813
35814                        // Unsubscribe event listeners
35815
35816                        if (p_oMenu.cfg) {
35817
35818                            p_oMenu.cfg.unsubscribeFromConfigEvent(_VISIBLE, 
35819                                onMenuVisibleConfigChange);
35820                            
35821                        }
35822
35823                        p_oMenu.destroyEvent.unsubscribe(onMenuDestroy, 
35824                            p_oMenu);
35825                
35826                        p_oMenu.itemAddedEvent.unsubscribe(onItemAdded);
35827                        p_oMenu.focusEvent.unsubscribe(onMenuFocus);
35828                        p_oMenu.blurEvent.unsubscribe(onMenuBlur);
35829
35830                    }
35831                
35832                }
35833    
35834            },
35835        
35836        
35837            /**
35838            * @method hideVisible
35839            * @description Hides all visible, dynamically positioned menus 
35840            * (excluding instances of YAHOO.widget.MenuBar).
35841            */
35842            hideVisible: function () {
35843        
35844                var oMenu;
35845        
35846                for (var i in m_oVisibleMenus) {
35847        
35848                    if (Lang.hasOwnProperty(m_oVisibleMenus, i)) {
35849        
35850                        oMenu = m_oVisibleMenus[i];
35851        
35852                        if (!(oMenu instanceof YAHOO.widget.MenuBar) && 
35853                            oMenu.cfg.getProperty(_POSITION) == _DYNAMIC) {
35854        
35855                            oMenu.hide();
35856        
35857                        }
35858        
35859                    }
35860        
35861                }        
35862    
35863            },
35864
35865
35866            /**
35867            * @method getVisible
35868            * @description Returns a collection of all visible menus registered
35869            * with the menu manger.
35870            * @return {Object}
35871            */
35872            getVisible: function () {
35873            
35874                return m_oVisibleMenus;
35875            
35876            },
35877
35878    
35879            /**
35880            * @method getMenus
35881            * @description Returns a collection of all menus registered with the 
35882            * menu manger.
35883            * @return {Object}
35884            */
35885            getMenus: function () {
35886    
35887                return m_oMenus;
35888            
35889            },
35890    
35891    
35892            /**
35893            * @method getMenu
35894            * @description Returns a menu with the specified id.
35895            * @param {String} p_sId String specifying the id of the 
35896            * <code>&#60;div&#62;</code> element representing the menu to
35897            * be retrieved.
35898            * @return {YAHOO.widget.Menu}
35899            */
35900            getMenu: function (p_sId) {
35901                
35902                var returnVal;
35903                
35904                if (p_sId in m_oMenus) {
35905                
35906                    returnVal = m_oMenus[p_sId];
35907                
35908                }
35909            
35910                return returnVal;
35911            
35912            },
35913    
35914    
35915            /**
35916            * @method getMenuItem
35917            * @description Returns a menu item with the specified id.
35918            * @param {String} p_sId String specifying the id of the 
35919            * <code>&#60;li&#62;</code> element representing the menu item to
35920            * be retrieved.
35921            * @return {YAHOO.widget.MenuItem}
35922            */
35923            getMenuItem: function (p_sId) {
35924    
35925                var returnVal;
35926    
35927                if (p_sId in m_oItems) {
35928    
35929                    returnVal = m_oItems[p_sId];
35930                
35931                }
35932                
35933                return returnVal;
35934            
35935            },
35936
35937
35938            /**
35939            * @method getMenuItemGroup
35940            * @description Returns an array of menu item instances whose 
35941            * corresponding <code>&#60;li&#62;</code> elements are child 
35942            * nodes of the <code>&#60;ul&#62;</code> element with the 
35943            * specified id.
35944            * @param {String} p_sId String specifying the id of the 
35945            * <code>&#60;ul&#62;</code> element representing the group of 
35946            * menu items to be retrieved.
35947            * @return {Array}
35948            */
35949            getMenuItemGroup: function (p_sId) {
35950
35951                var oUL = Dom.get(p_sId),
35952                    aItems,
35953                    oNode,
35954                    oItem,
35955                    sId,
35956                    returnVal;
35957    
35958
35959                if (oUL && oUL.tagName && oUL.tagName.toUpperCase() == _UL) {
35960
35961                    oNode = oUL.firstChild;
35962
35963                    if (oNode) {
35964
35965                        aItems = [];
35966                        
35967                        do {
35968
35969                            sId = oNode.id;
35970
35971                            if (sId) {
35972                            
35973                                oItem = this.getMenuItem(sId);
35974                                
35975                                if (oItem) {
35976                                
35977                                    aItems[aItems.length] = oItem;
35978                                
35979                                }
35980                            
35981                            }
35982                        
35983                        }
35984                        while ((oNode = oNode.nextSibling));
35985
35986
35987                        if (aItems.length > 0) {
35988
35989                            returnVal = aItems;
35990                        
35991                        }
35992
35993                    }
35994                
35995                }
35996
35997                return returnVal;
35998            
35999            },
36000
36001    
36002            /**
36003            * @method getFocusedMenuItem
36004            * @description Returns a reference to the menu item that currently 
36005            * has focus.
36006            * @return {YAHOO.widget.MenuItem}
36007            */
36008            getFocusedMenuItem: function () {
36009    
36010                return m_oFocusedMenuItem;
36011    
36012            },
36013    
36014    
36015            /**
36016            * @method getFocusedMenu
36017            * @description Returns a reference to the menu that currently 
36018            * has focus.
36019            * @return {YAHOO.widget.Menu}
36020            */
36021            getFocusedMenu: function () {
36022
36023                var returnVal;
36024    
36025                if (m_oFocusedMenuItem) {
36026    
36027                    returnVal = m_oFocusedMenuItem.parent.getRoot();
36028                
36029                }
36030    
36031                return returnVal;
36032    
36033            },
36034    
36035        
36036            /**
36037            * @method toString
36038            * @description Returns a string representing the menu manager.
36039            * @return {String}
36040            */
36041            toString: function () {
36042            
36043                return _MENUMANAGER;
36044            
36045            }
36046    
36047        };
36048    
36049    }();
36050
36051})();
36052
36053
36054
36055(function () {
36056
36057    var Lang = YAHOO.lang,
36058
36059    // String constants
36060    
36061        _MENU = "Menu",
36062        _DIV_UPPERCASE = "DIV",
36063        _DIV_LOWERCASE = "div",
36064        _ID = "id",
36065        _SELECT = "SELECT",
36066        _XY = "xy",
36067        _Y = "y",
36068        _UL_UPPERCASE = "UL",
36069        _UL_LOWERCASE = "ul",
36070        _FIRST_OF_TYPE = "first-of-type",
36071        _LI = "LI",
36072        _OPTGROUP = "OPTGROUP",
36073        _OPTION = "OPTION",
36074        _DISABLED = "disabled",
36075        _NONE = "none",
36076        _SELECTED = "selected",
36077        _GROUP_INDEX = "groupindex",
36078        _INDEX = "index",
36079        _SUBMENU = "submenu",
36080        _VISIBLE = "visible",
36081        _HIDE_DELAY = "hidedelay",
36082        _POSITION = "position",
36083        _DYNAMIC = "dynamic",
36084        _STATIC = "static",
36085        _DYNAMIC_STATIC = _DYNAMIC + "," + _STATIC,
36086        _URL = "url",
36087        _HASH = "#",
36088        _TARGET = "target",
36089        _MAX_HEIGHT = "maxheight",
36090        _TOP_SCROLLBAR = "topscrollbar",
36091        _BOTTOM_SCROLLBAR = "bottomscrollbar",
36092        _UNDERSCORE = "_",
36093        _TOP_SCROLLBAR_DISABLED = _TOP_SCROLLBAR + _UNDERSCORE + _DISABLED,
36094        _BOTTOM_SCROLLBAR_DISABLED = _BOTTOM_SCROLLBAR + _UNDERSCORE + _DISABLED,
36095        _MOUSEMOVE = "mousemove",
36096        _SHOW_DELAY = "showdelay",
36097        _SUBMENU_HIDE_DELAY = "submenuhidedelay",
36098        _IFRAME = "iframe",
36099        _CONSTRAIN_TO_VIEWPORT = "constraintoviewport",
36100        _PREVENT_CONTEXT_OVERLAP = "preventcontextoverlap",
36101        _SUBMENU_ALIGNMENT = "submenualignment",
36102        _AUTO_SUBMENU_DISPLAY = "autosubmenudisplay",
36103        _CLICK_TO_HIDE = "clicktohide",
36104        _CONTAINER = "container",
36105        _SCROLL_INCREMENT = "scrollincrement",
36106        _MIN_SCROLL_HEIGHT = "minscrollheight",
36107        _CLASSNAME = "classname",
36108        _SHADOW = "shadow",
36109        _KEEP_OPEN = "keepopen",
36110        _HD = "hd",
36111        _HAS_TITLE = "hastitle",
36112        _CONTEXT = "context",
36113        _EMPTY_STRING = "",
36114        _MOUSEDOWN = "mousedown",
36115        _KEYDOWN = "keydown",
36116        _HEIGHT = "height",
36117        _WIDTH = "width",
36118        _PX = "px",
36119        _EFFECT = "effect",
36120        _MONITOR_RESIZE = "monitorresize",
36121        _DISPLAY = "display",
36122        _BLOCK = "block",
36123        _VISIBILITY = "visibility",
36124        _ABSOLUTE = "absolute",
36125        _ZINDEX = "zindex",
36126        _YUI_MENU_BODY_SCROLLED = "yui-menu-body-scrolled",
36127        _NON_BREAKING_SPACE = "&#32;",
36128        _SPACE = " ",
36129        _MOUSEOVER = "mouseover",
36130        _MOUSEOUT = "mouseout",
36131        _ITEM_ADDED = "itemAdded",
36132        _ITEM_REMOVED = "itemRemoved",
36133        _HIDDEN = "hidden",
36134        _YUI_MENU_SHADOW = "yui-menu-shadow",
36135        _YUI_MENU_SHADOW_VISIBLE = _YUI_MENU_SHADOW + "-visible",
36136        _YUI_MENU_SHADOW_YUI_MENU_SHADOW_VISIBLE = _YUI_MENU_SHADOW + _SPACE + _YUI_MENU_SHADOW_VISIBLE;
36137
36138
36139/**
36140* The Menu class creates a container that holds a vertical list representing 
36141* a set of options or commands.  Menu is the base class for all 
36142* menu containers. 
36143* @param {String} p_oElement String specifying the id attribute of the 
36144* <code>&#60;div&#62;</code> element of the menu.
36145* @param {String} p_oElement String specifying the id attribute of the 
36146* <code>&#60;select&#62;</code> element to be used as the data source 
36147* for the menu.
36148* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
36149* level-one-html.html#ID-22445964">HTMLDivElement</a>} p_oElement Object 
36150* specifying the <code>&#60;div&#62;</code> element of the menu.
36151* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
36152* level-one-html.html#ID-94282980">HTMLSelectElement</a>} p_oElement 
36153* Object specifying the <code>&#60;select&#62;</code> element to be used as 
36154* the data source for the menu.
36155* @param {Object} p_oConfig Optional. Object literal specifying the 
36156* configuration for the menu. See configuration class documentation for 
36157* more details.
36158* @namespace YAHOO.widget
36159* @class Menu
36160* @constructor
36161* @extends YAHOO.widget.Overlay
36162*/
36163YAHOO.widget.Menu = function (p_oElement, p_oConfig) {
36164
36165    if (p_oConfig) {
36166        this.parent = p_oConfig.parent;
36167        this.lazyLoad = p_oConfig.lazyLoad || p_oConfig.lazyload;
36168        this.itemData = p_oConfig.itemData || p_oConfig.itemdata;
36169    }
36170
36171    YAHOO.widget.Menu.superclass.constructor.call(this, p_oElement, p_oConfig);
36172};
36173
36174
36175
36176/**
36177* @method checkPosition
36178* @description Checks to make sure that the value of the "position" property 
36179* is one of the supported strings. Returns true if the position is supported.
36180* @private
36181* @param {Object} p_sPosition String specifying the position of the menu.
36182* @return {Boolean}
36183*/
36184function checkPosition(p_sPosition) {
36185
36186    var returnVal = false;
36187
36188    if (Lang.isString(p_sPosition)) {
36189
36190        returnVal = (_DYNAMIC_STATIC.indexOf((p_sPosition.toLowerCase())) != -1);
36191
36192    }
36193
36194    return returnVal;
36195
36196}
36197
36198
36199var Dom = YAHOO.util.Dom,
36200    Event = YAHOO.util.Event,
36201    Module = YAHOO.widget.Module,
36202    Overlay = YAHOO.widget.Overlay,
36203    Menu = YAHOO.widget.Menu,
36204    MenuManager = YAHOO.widget.MenuManager,
36205    CustomEvent = YAHOO.util.CustomEvent,
36206    UA = YAHOO.env.ua,
36207    
36208    m_oShadowTemplate,
36209
36210    bFocusListenerInitialized = false,
36211
36212    oFocusedElement,
36213
36214    EVENT_TYPES = [
36215    
36216        ["mouseOverEvent", _MOUSEOVER],
36217        ["mouseOutEvent", _MOUSEOUT],
36218        ["mouseDownEvent", _MOUSEDOWN],
36219        ["mouseUpEvent", "mouseup"],
36220        ["clickEvent", "click"],
36221        ["keyPressEvent", "keypress"],
36222        ["keyDownEvent", _KEYDOWN],
36223        ["keyUpEvent", "keyup"],
36224        ["focusEvent", "focus"],
36225        ["blurEvent", "blur"],
36226        ["itemAddedEvent", _ITEM_ADDED],
36227        ["itemRemovedEvent", _ITEM_REMOVED]
36228
36229    ],
36230
36231    VISIBLE_CONFIG =  { 
36232        key: _VISIBLE, 
36233        value: false, 
36234        validator: Lang.isBoolean
36235    }, 
36236
36237    CONSTRAIN_TO_VIEWPORT_CONFIG =  {
36238        key: _CONSTRAIN_TO_VIEWPORT, 
36239        value: true, 
36240        validator: Lang.isBoolean, 
36241        supercedes: [_IFRAME,"x",_Y,_XY]
36242    }, 
36243
36244    PREVENT_CONTEXT_OVERLAP_CONFIG =  {
36245        key: _PREVENT_CONTEXT_OVERLAP,
36246        value: true,
36247        validator: Lang.isBoolean,  
36248        supercedes: [_CONSTRAIN_TO_VIEWPORT]
36249    },
36250
36251    POSITION_CONFIG =  { 
36252        key: _POSITION, 
36253        value: _DYNAMIC, 
36254        validator: checkPosition, 
36255        supercedes: [_VISIBLE, _IFRAME]
36256    }, 
36257
36258    SUBMENU_ALIGNMENT_CONFIG =  { 
36259        key: _SUBMENU_ALIGNMENT, 
36260        value: ["tl","tr"]
36261    },
36262
36263    AUTO_SUBMENU_DISPLAY_CONFIG =  { 
36264        key: _AUTO_SUBMENU_DISPLAY, 
36265        value: true, 
36266        validator: Lang.isBoolean,
36267        suppressEvent: true
36268    }, 
36269
36270    SHOW_DELAY_CONFIG =  { 
36271        key: _SHOW_DELAY, 
36272        value: 250, 
36273        validator: Lang.isNumber, 
36274        suppressEvent: true
36275    }, 
36276
36277    HIDE_DELAY_CONFIG =  { 
36278        key: _HIDE_DELAY, 
36279        value: 0, 
36280        validator: Lang.isNumber, 
36281        suppressEvent: true
36282    }, 
36283
36284    SUBMENU_HIDE_DELAY_CONFIG =  { 
36285        key: _SUBMENU_HIDE_DELAY, 
36286        value: 250, 
36287        validator: Lang.isNumber,
36288        suppressEvent: true
36289    }, 
36290
36291    CLICK_TO_HIDE_CONFIG =  { 
36292        key: _CLICK_TO_HIDE, 
36293        value: true, 
36294        validator: Lang.isBoolean,
36295        suppressEvent: true
36296    },
36297
36298    CONTAINER_CONFIG =  { 
36299        key: _CONTAINER,
36300        suppressEvent: true
36301    }, 
36302
36303    SCROLL_INCREMENT_CONFIG =  { 
36304        key: _SCROLL_INCREMENT, 
36305        value: 1, 
36306        validator: Lang.isNumber,
36307        supercedes: [_MAX_HEIGHT],
36308        suppressEvent: true
36309    },
36310
36311    MIN_SCROLL_HEIGHT_CONFIG =  { 
36312        key: _MIN_SCROLL_HEIGHT, 
36313        value: 90, 
36314        validator: Lang.isNumber,
36315        supercedes: [_MAX_HEIGHT],
36316        suppressEvent: true
36317    },    
36318
36319    MAX_HEIGHT_CONFIG =  { 
36320        key: _MAX_HEIGHT, 
36321        value: 0, 
36322        validator: Lang.isNumber,
36323        supercedes: [_IFRAME],
36324        suppressEvent: true
36325    }, 
36326
36327    CLASS_NAME_CONFIG =  { 
36328        key: _CLASSNAME, 
36329        value: null, 
36330        validator: Lang.isString,
36331        suppressEvent: true
36332    }, 
36333
36334    DISABLED_CONFIG =  { 
36335        key: _DISABLED, 
36336        value: false, 
36337        validator: Lang.isBoolean,
36338        suppressEvent: true
36339    },
36340    
36341    SHADOW_CONFIG =  { 
36342        key: _SHADOW, 
36343        value: true, 
36344        validator: Lang.isBoolean,
36345        suppressEvent: true,
36346        supercedes: [_VISIBLE]
36347    },
36348    
36349    KEEP_OPEN_CONFIG = {
36350        key: _KEEP_OPEN, 
36351        value: false, 
36352        validator: Lang.isBoolean
36353    };
36354
36355
36356function onDocFocus(event) {
36357
36358    oFocusedElement = Event.getTarget(event);
36359
36360}
36361
36362
36363
36364YAHOO.lang.extend(Menu, Overlay, {
36365
36366
36367// Constants
36368
36369
36370/**
36371* @property CSS_CLASS_NAME
36372* @description String representing the CSS class(es) to be applied to the 
36373* menu's <code>&#60;div&#62;</code> element.
36374* @default "yuimenu"
36375* @final
36376* @type String
36377*/
36378CSS_CLASS_NAME: "yuimenu",
36379
36380
36381/**
36382* @property ITEM_TYPE
36383* @description Object representing the type of menu item to instantiate and 
36384* add when parsing the child nodes (either <code>&#60;li&#62;</code> element, 
36385* <code>&#60;optgroup&#62;</code> element or <code>&#60;option&#62;</code>) 
36386* of the menu's source HTML element.
36387* @default YAHOO.widget.MenuItem
36388* @final
36389* @type YAHOO.widget.MenuItem
36390*/
36391ITEM_TYPE: null,
36392
36393
36394/**
36395* @property GROUP_TITLE_TAG_NAME
36396* @description String representing the tagname of the HTML element used to 
36397* title the menu's item groups.
36398* @default H6
36399* @final
36400* @type String
36401*/
36402GROUP_TITLE_TAG_NAME: "h6",
36403
36404
36405/**
36406* @property OFF_SCREEN_POSITION
36407* @description Array representing the default x and y position that a menu 
36408* should have when it is positioned outside the viewport by the 
36409* "poistionOffScreen" method.
36410* @default "-999em"
36411* @final
36412* @type String
36413*/
36414OFF_SCREEN_POSITION: "-999em",
36415
36416
36417// Private properties
36418
36419
36420/** 
36421* @property _useHideDelay
36422* @description Boolean indicating if the "mouseover" and "mouseout" event 
36423* handlers used for hiding the menu via a call to "YAHOO.lang.later" have 
36424* already been assigned.
36425* @default false
36426* @private
36427* @type Boolean
36428*/
36429_useHideDelay: false,
36430
36431
36432/**
36433* @property _bHandledMouseOverEvent
36434* @description Boolean indicating the current state of the menu's 
36435* "mouseover" event.
36436* @default false
36437* @private
36438* @type Boolean
36439*/
36440_bHandledMouseOverEvent: false,
36441
36442
36443/**
36444* @property _bHandledMouseOutEvent
36445* @description Boolean indicating the current state of the menu's
36446* "mouseout" event.
36447* @default false
36448* @private
36449* @type Boolean
36450*/
36451_bHandledMouseOutEvent: false,
36452
36453
36454/**
36455* @property _aGroupTitleElements
36456* @description Array of HTML element used to title groups of menu items.
36457* @default []
36458* @private
36459* @type Array
36460*/
36461_aGroupTitleElements: null,
36462
36463
36464/**
36465* @property _aItemGroups
36466* @description Multi-dimensional Array representing the menu items as they
36467* are grouped in the menu.
36468* @default []
36469* @private
36470* @type Array
36471*/
36472_aItemGroups: null,
36473
36474
36475/**
36476* @property _aListElements
36477* @description Array of <code>&#60;ul&#62;</code> elements, each of which is 
36478* the parent node for each item's <code>&#60;li&#62;</code> element.
36479* @default []
36480* @private
36481* @type Array
36482*/
36483_aListElements: null,
36484
36485
36486/**
36487* @property _nCurrentMouseX
36488* @description The current x coordinate of the mouse inside the area of 
36489* the menu.
36490* @default 0
36491* @private
36492* @type Number
36493*/
36494_nCurrentMouseX: 0,
36495
36496
36497/**
36498* @property _bStopMouseEventHandlers
36499* @description Stops "mouseover," "mouseout," and "mousemove" event handlers 
36500* from executing.
36501* @default false
36502* @private
36503* @type Boolean
36504*/
36505_bStopMouseEventHandlers: false,
36506
36507
36508/**
36509* @property _sClassName
36510* @description The current value of the "classname" configuration attribute.
36511* @default null
36512* @private
36513* @type String
36514*/
36515_sClassName: null,
36516
36517
36518
36519// Public properties
36520
36521
36522/**
36523* @property lazyLoad
36524* @description Boolean indicating if the menu's "lazy load" feature is 
36525* enabled.  If set to "true," initialization and rendering of the menu's 
36526* items will be deferred until the first time it is made visible.  This 
36527* property should be set via the constructor using the configuration 
36528* object literal.
36529* @default false
36530* @type Boolean
36531*/
36532lazyLoad: false,
36533
36534
36535/**
36536* @property itemData
36537* @description Array of items to be added to the menu.  The array can contain 
36538* strings representing the text for each item to be created, object literals 
36539* representing the menu item configuration properties, or MenuItem instances.  
36540* This property should be set via the constructor using the configuration 
36541* object literal.
36542* @default null
36543* @type Array
36544*/
36545itemData: null,
36546
36547
36548/**
36549* @property activeItem
36550* @description Object reference to the item in the menu that has is selected.
36551* @default null
36552* @type YAHOO.widget.MenuItem
36553*/
36554activeItem: null,
36555
36556
36557/**
36558* @property parent
36559* @description Object reference to the menu's parent menu or menu item.  
36560* This property can be set via the constructor using the configuration 
36561* object literal.
36562* @default null
36563* @type YAHOO.widget.MenuItem
36564*/
36565parent: null,
36566
36567
36568/**
36569* @property srcElement
36570* @description Object reference to the HTML element (either 
36571* <code>&#60;select&#62;</code> or <code>&#60;div&#62;</code>) used to 
36572* create the menu.
36573* @default null
36574* @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
36575* level-one-html.html#ID-94282980">HTMLSelectElement</a>|<a 
36576* href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-html.
36577* html#ID-22445964">HTMLDivElement</a>
36578*/
36579srcElement: null,
36580
36581
36582
36583// Events
36584
36585
36586/**
36587* @event mouseOverEvent
36588* @description Fires when the mouse has entered the menu.  Passes back 
36589* the DOM Event object as an argument.
36590*/
36591
36592
36593/**
36594* @event mouseOutEvent
36595* @description Fires when the mouse has left the menu.  Passes back the DOM 
36596* Event object as an argument.
36597* @type YAHOO.util.CustomEvent
36598*/
36599
36600
36601/**
36602* @event mouseDownEvent
36603* @description Fires when the user mouses down on the menu.  Passes back the 
36604* DOM Event object as an argument.
36605* @type YAHOO.util.CustomEvent
36606*/
36607
36608
36609/**
36610* @event mouseUpEvent
36611* @description Fires when the user releases a mouse button while the mouse is 
36612* over the menu.  Passes back the DOM Event object as an argument.
36613* @type YAHOO.util.CustomEvent
36614*/
36615
36616
36617/**
36618* @event clickEvent
36619* @description Fires when the user clicks the on the menu.  Passes back the 
36620* DOM Event object as an argument.
36621* @type YAHOO.util.CustomEvent
36622*/
36623
36624
36625/**
36626* @event keyPressEvent
36627* @description Fires when the user presses an alphanumeric key when one of the
36628* menu's items has focus.  Passes back the DOM Event object as an argument.
36629* @type YAHOO.util.CustomEvent
36630*/
36631
36632
36633/**
36634* @event keyDownEvent
36635* @description Fires when the user presses a key when one of the menu's items 
36636* has focus.  Passes back the DOM Event object as an argument.
36637* @type YAHOO.util.CustomEvent
36638*/
36639
36640
36641/**
36642* @event keyUpEvent
36643* @description Fires when the user releases a key when one of the menu's items 
36644* has focus.  Passes back the DOM Event object as an argument.
36645* @type YAHOO.util.CustomEvent
36646*/
36647
36648
36649/**
36650* @event itemAddedEvent
36651* @description Fires when an item is added to the menu.
36652* @type YAHOO.util.CustomEvent
36653*/
36654
36655
36656/**
36657* @event itemRemovedEvent
36658* @description Fires when an item is removed to the menu.
36659* @type YAHOO.util.CustomEvent
36660*/
36661
36662
36663/**
36664* @method init
36665* @description The Menu class's initialization method. This method is 
36666* automatically called by the constructor, and sets up all DOM references 
36667* for pre-existing markup, and creates required markup if it is not 
36668* already present.
36669* @param {String} p_oElement String specifying the id attribute of the 
36670* <code>&#60;div&#62;</code> element of the menu.
36671* @param {String} p_oElement String specifying the id attribute of the 
36672* <code>&#60;select&#62;</code> element to be used as the data source 
36673* for the menu.
36674* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
36675* level-one-html.html#ID-22445964">HTMLDivElement</a>} p_oElement Object 
36676* specifying the <code>&#60;div&#62;</code> element of the menu.
36677* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
36678* level-one-html.html#ID-94282980">HTMLSelectElement</a>} p_oElement 
36679* Object specifying the <code>&#60;select&#62;</code> element to be used as 
36680* the data source for the menu.
36681* @param {Object} p_oConfig Optional. Object literal specifying the 
36682* configuration for the menu. See configuration class documentation for 
36683* more details.
36684*/
36685init: function (p_oElement, p_oConfig) {
36686
36687    this._aItemGroups = [];
36688    this._aListElements = [];
36689    this._aGroupTitleElements = [];
36690
36691    if (!this.ITEM_TYPE) {
36692
36693        this.ITEM_TYPE = YAHOO.widget.MenuItem;
36694
36695    }
36696
36697
36698    var oElement;
36699
36700    if (Lang.isString(p_oElement)) {
36701
36702        oElement = Dom.get(p_oElement);
36703
36704    }
36705    else if (p_oElement.tagName) {
36706
36707        oElement = p_oElement;
36708
36709    }
36710
36711
36712    if (oElement && oElement.tagName) {
36713
36714        switch(oElement.tagName.toUpperCase()) {
36715    
36716            case _DIV_UPPERCASE:
36717
36718                this.srcElement = oElement;
36719
36720                if (!oElement.id) {
36721
36722                    oElement.setAttribute(_ID, Dom.generateId());
36723
36724                }
36725
36726
36727                /* 
36728                    Note: we don't pass the user config in here yet 
36729                    because we only want it executed once, at the lowest 
36730                    subclass level.
36731                */ 
36732            
36733                Menu.superclass.init.call(this, oElement);
36734
36735                this.beforeInitEvent.fire(Menu);
36736
36737                YAHOO.log("Source element: " + this.srcElement.tagName, "info", this.toString());
36738    
36739            break;
36740    
36741            case _SELECT:
36742    
36743                this.srcElement = oElement;
36744
36745    
36746                /*
36747                    The source element is not something that we can use 
36748                    outright, so we need to create a new Overlay
36749
36750                    Note: we don't pass the user config in here yet 
36751                    because we only want it executed once, at the lowest 
36752                    subclass level.
36753                */ 
36754
36755                Menu.superclass.init.call(this, Dom.generateId());
36756
36757                this.beforeInitEvent.fire(Menu);
36758
36759                YAHOO.log("Source element: " + this.srcElement.tagName, "info", this.toString());
36760
36761            break;
36762
36763        }
36764
36765    }
36766    else {
36767
36768        /* 
36769            Note: we don't pass the user config in here yet 
36770            because we only want it executed once, at the lowest 
36771            subclass level.
36772        */ 
36773    
36774        Menu.superclass.init.call(this, p_oElement);
36775
36776        this.beforeInitEvent.fire(Menu);
36777
36778        YAHOO.log("No source element found.  Created element with id: " + this.id, "info", this.toString());
36779
36780    }
36781
36782
36783    if (this.element) {
36784        Dom.addClass(this.element, this.CSS_CLASS_NAME);
36785
36786        // Subscribe to Custom Events
36787        this.initEvent.subscribe(this._onInit);
36788        this.beforeRenderEvent.subscribe(this._onBeforeRender);
36789        this.renderEvent.subscribe(this._onRender);
36790        this.beforeShowEvent.subscribe(this._onBeforeShow);
36791        this.hideEvent.subscribe(this._onHide);
36792        this.showEvent.subscribe(this._onShow);
36793        this.beforeHideEvent.subscribe(this._onBeforeHide);
36794        this.mouseOverEvent.subscribe(this._onMouseOver);
36795        this.mouseOutEvent.subscribe(this._onMouseOut);
36796        this.clickEvent.subscribe(this._onClick);
36797        this.keyDownEvent.subscribe(this._onKeyDown);
36798        this.keyPressEvent.subscribe(this._onKeyPress);
36799        this.blurEvent.subscribe(this._onBlur);
36800
36801        if (!bFocusListenerInitialized) {
36802            Event.onFocus(document, onDocFocus);
36803            bFocusListenerInitialized = true;
36804        }
36805
36806        //	Fixes an issue in Firefox 2 and Webkit where Dom's "getX" and "getY" 
36807        //	methods return values that don't take scrollTop into consideration 
36808
36809        if ((UA.gecko && UA.gecko < 1.9) || (UA.webkit && UA.webkit < 523)) {
36810            this.cfg.subscribeToConfigEvent(_Y, this._onYChange);
36811        }
36812
36813
36814        if (p_oConfig) {
36815            this.cfg.applyConfig(p_oConfig, true);
36816        }
36817
36818        // Register the Menu instance with the MenuManager
36819        MenuManager.addMenu(this);
36820
36821        this.initEvent.fire(Menu);
36822    }
36823},
36824
36825
36826
36827// Private methods
36828
36829
36830/**
36831* @method _initSubTree
36832* @description Iterates the childNodes of the source element to find nodes 
36833* used to instantiate menu and menu items.
36834* @private
36835*/
36836_initSubTree: function () {
36837
36838    var oSrcElement = this.srcElement,
36839        sSrcElementTagName,
36840        nGroup,
36841        sGroupTitleTagName,
36842        oNode,
36843        aListElements,
36844        nListElements,
36845        i;
36846
36847
36848    if (oSrcElement) {
36849    
36850        sSrcElementTagName = 
36851            (oSrcElement.tagName && oSrcElement.tagName.toUpperCase());
36852
36853
36854        if (sSrcElementTagName == _DIV_UPPERCASE) {
36855    
36856            //  Populate the collection of item groups and item group titles
36857    
36858            oNode = this.body.firstChild;
36859    
36860
36861            if (oNode) {
36862    
36863                nGroup = 0;
36864                sGroupTitleTagName = this.GROUP_TITLE_TAG_NAME.toUpperCase();
36865        
36866                do {
36867        
36868
36869                    if (oNode && oNode.tagName) {
36870        
36871                        switch (oNode.tagName.toUpperCase()) {
36872        
36873                            case sGroupTitleTagName:
36874                            
36875                                this._aGroupTitleElements[nGroup] = oNode;
36876        
36877                            break;
36878        
36879                            case _UL_UPPERCASE:
36880        
36881                                this._aListElements[nGroup] = oNode;
36882                                this._aItemGroups[nGroup] = [];
36883                                nGroup++;
36884        
36885                            break;
36886        
36887                        }
36888                    
36889                    }
36890        
36891                }
36892                while ((oNode = oNode.nextSibling));
36893        
36894        
36895                /*
36896                    Apply the "first-of-type" class to the first UL to mimic 
36897                    the ":first-of-type" CSS3 psuedo class.
36898                */
36899        
36900                if (this._aListElements[0]) {
36901        
36902                    Dom.addClass(this._aListElements[0], _FIRST_OF_TYPE);
36903        
36904                }
36905            
36906            }
36907    
36908        }
36909    
36910    
36911        oNode = null;
36912    
36913        YAHOO.log("Searching DOM for items to initialize.", "info", this.toString());
36914    
36915
36916        if (sSrcElementTagName) {
36917    
36918            switch (sSrcElementTagName) {
36919        
36920                case _DIV_UPPERCASE:
36921
36922                    aListElements = this._aListElements;
36923                    nListElements = aListElements.length;
36924        
36925                    if (nListElements > 0) {
36926        
36927                        YAHOO.log("Found " + nListElements + " item groups to initialize.", 
36928                                    "info", this.toString());
36929        
36930                        i = nListElements - 1;
36931        
36932                        do {
36933        
36934                            oNode = aListElements[i].firstChild;
36935            
36936                            if (oNode) {
36937
36938                                YAHOO.log("Scanning " + 
36939                                    aListElements[i].childNodes.length + 
36940                                    " child nodes for items to initialize.", "info", this.toString());
36941            
36942                                do {
36943                
36944                                    if (oNode && oNode.tagName && 
36945                                        oNode.tagName.toUpperCase() == _LI) {
36946                
36947                                        YAHOO.log("Initializing " + 
36948                                            oNode.tagName + " node.", "info", this.toString());
36949        
36950                                        this.addItem(new this.ITEM_TYPE(oNode, 
36951                                                    { parent: this }), i);
36952            
36953                                    }
36954                        
36955                                }
36956                                while ((oNode = oNode.nextSibling));
36957                            
36958                            }
36959                    
36960                        }
36961                        while (i--);
36962        
36963                    }
36964        
36965                break;
36966        
36967                case _SELECT:
36968        
36969                    YAHOO.log("Scanning " +  
36970                        oSrcElement.childNodes.length + 
36971                        " child nodes for items to initialize.", "info", this.toString());
36972        
36973                    oNode = oSrcElement.firstChild;
36974        
36975                    do {
36976        
36977                        if (oNode && oNode.tagName) {
36978                        
36979                            switch (oNode.tagName.toUpperCase()) {
36980            
36981                                case _OPTGROUP:
36982                                case _OPTION:
36983            
36984                                    YAHOO.log("Initializing " +  
36985                                        oNode.tagName + " node.", "info", this.toString());
36986            
36987                                    this.addItem(
36988                                            new this.ITEM_TYPE(
36989                                                    oNode, 
36990                                                    { parent: this }
36991                                                )
36992                                            );
36993            
36994                                break;
36995            
36996                            }
36997    
36998                        }
36999        
37000                    }
37001                    while ((oNode = oNode.nextSibling));
37002        
37003                break;
37004        
37005            }
37006    
37007        }    
37008    
37009    }
37010
37011},
37012
37013
37014/**
37015* @method _getFirstEnabledItem
37016* @description Returns the first enabled item in the menu.
37017* @return {YAHOO.widget.MenuItem}
37018* @private
37019*/
37020_getFirstEnabledItem: function () {
37021
37022    var aItems = this.getItems(),
37023        nItems = aItems.length,
37024        oItem,
37025        returnVal;
37026    
37027
37028    for(var i=0; i<nItems; i++) {
37029
37030        oItem = aItems[i];
37031
37032        if (oItem && !oItem.cfg.getProperty(_DISABLED) && oItem.element.style.display != _NONE) {
37033
37034            returnVal = oItem;
37035            break;
37036
37037        }
37038    
37039    }
37040    
37041    return returnVal;
37042    
37043},
37044
37045
37046/**
37047* @method _addItemToGroup
37048* @description Adds a menu item to a group.
37049* @private
37050* @param {Number} p_nGroupIndex Number indicating the group to which the 
37051* item belongs.
37052* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem 
37053* instance to be added to the menu.
37054* @param {HTML} p_oItem String or markup specifying the content of the item to be added 
37055* to the menu. The item is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
37056* @param {Object} p_oItem Object literal containing a set of menu item 
37057* configuration properties.
37058* @param {Number} p_nItemIndex Optional. Number indicating the index at 
37059* which the menu item should be added.
37060* @return {YAHOO.widget.MenuItem}
37061*/
37062_addItemToGroup: function (p_nGroupIndex, p_oItem, p_nItemIndex) {
37063
37064    var oItem,
37065        nGroupIndex,
37066        aGroup,
37067        oGroupItem,
37068        bAppend,
37069        oNextItemSibling,
37070        nItemIndex,
37071        returnVal;
37072
37073
37074    function getNextItemSibling(p_aArray, p_nStartIndex) {
37075
37076        return (p_aArray[p_nStartIndex] || getNextItemSibling(p_aArray, (p_nStartIndex+1)));
37077
37078    }
37079
37080
37081    if (p_oItem instanceof this.ITEM_TYPE) {
37082
37083        oItem = p_oItem;
37084        oItem.parent = this;
37085
37086    }
37087    else if (Lang.isString(p_oItem)) {
37088
37089        oItem = new this.ITEM_TYPE(p_oItem, { parent: this });
37090    
37091    }
37092    else if (Lang.isObject(p_oItem)) {
37093
37094        p_oItem.parent = this;
37095
37096        oItem = new this.ITEM_TYPE(p_oItem.text, p_oItem);
37097
37098    }
37099
37100
37101    if (oItem) {
37102
37103        if (oItem.cfg.getProperty(_SELECTED)) {
37104
37105            this.activeItem = oItem;
37106        
37107        }
37108
37109
37110        nGroupIndex = Lang.isNumber(p_nGroupIndex) ? p_nGroupIndex : 0;
37111        aGroup = this._getItemGroup(nGroupIndex);
37112
37113
37114
37115        if (!aGroup) {
37116
37117            aGroup = this._createItemGroup(nGroupIndex);
37118
37119        }
37120
37121
37122        if (Lang.isNumber(p_nItemIndex)) {
37123
37124            bAppend = (p_nItemIndex >= aGroup.length);            
37125
37126
37127            if (aGroup[p_nItemIndex]) {
37128    
37129                aGroup.splice(p_nItemIndex, 0, oItem);
37130    
37131            }
37132            else {
37133    
37134                aGroup[p_nItemIndex] = oItem;
37135    
37136            }
37137
37138
37139            oGroupItem = aGroup[p_nItemIndex];
37140
37141            if (oGroupItem) {
37142
37143                if (bAppend && (!oGroupItem.element.parentNode || 
37144                        oGroupItem.element.parentNode.nodeType == 11)) {
37145        
37146                    this._aListElements[nGroupIndex].appendChild(oGroupItem.element);
37147    
37148                }
37149                else {
37150    
37151                    oNextItemSibling = getNextItemSibling(aGroup, (p_nItemIndex+1));
37152    
37153                    if (oNextItemSibling && (!oGroupItem.element.parentNode || 
37154                            oGroupItem.element.parentNode.nodeType == 11)) {
37155            
37156                        this._aListElements[nGroupIndex].insertBefore(
37157                                oGroupItem.element, oNextItemSibling.element);
37158        
37159                    }
37160    
37161                }
37162    
37163
37164                oGroupItem.parent = this;
37165        
37166                this._subscribeToItemEvents(oGroupItem);
37167    
37168                this._configureSubmenu(oGroupItem);
37169                
37170                this._updateItemProperties(nGroupIndex);
37171        
37172                YAHOO.log("Item inserted." + 
37173                    " Text: " + oGroupItem.cfg.getProperty("text") + ", " + 
37174                    " Index: " + oGroupItem.index + ", " + 
37175                    " Group Index: " + oGroupItem.groupIndex, "info", this.toString());
37176
37177                this.itemAddedEvent.fire(oGroupItem);
37178                this.changeContentEvent.fire();
37179
37180                returnVal = oGroupItem;
37181    
37182            }
37183
37184        }
37185        else {
37186    
37187            nItemIndex = aGroup.length;
37188    
37189            aGroup[nItemIndex] = oItem;
37190
37191            oGroupItem = aGroup[nItemIndex];
37192    
37193
37194            if (oGroupItem) {
37195    
37196                if (!Dom.isAncestor(this._aListElements[nGroupIndex], oGroupItem.element)) {
37197    
37198                    this._aListElements[nGroupIndex].appendChild(oGroupItem.element);
37199    
37200                }
37201    
37202                oGroupItem.element.setAttribute(_GROUP_INDEX, nGroupIndex);
37203                oGroupItem.element.setAttribute(_INDEX, nItemIndex);
37204        
37205                oGroupItem.parent = this;
37206    
37207                oGroupItem.index = nItemIndex;
37208                oGroupItem.groupIndex = nGroupIndex;
37209        
37210                this._subscribeToItemEvents(oGroupItem);
37211    
37212                this._configureSubmenu(oGroupItem);
37213    
37214                if (nItemIndex === 0) {
37215        
37216                    Dom.addClass(oGroupItem.element, _FIRST_OF_TYPE);
37217        
37218                }
37219
37220                YAHOO.log("Item added." + 
37221                    " Text: " + oGroupItem.cfg.getProperty("text") + ", " + 
37222                    " Index: " + oGroupItem.index + ", " + 
37223                    " Group Index: " + oGroupItem.groupIndex, "info", this.toString());
37224        
37225
37226                this.itemAddedEvent.fire(oGroupItem);
37227                this.changeContentEvent.fire();
37228
37229                returnVal = oGroupItem;
37230    
37231            }
37232    
37233        }
37234
37235    }
37236    
37237    return returnVal;
37238    
37239},
37240
37241
37242/**
37243* @method _removeItemFromGroupByIndex
37244* @description Removes a menu item from a group by index.  Returns the menu 
37245* item that was removed.
37246* @private
37247* @param {Number} p_nGroupIndex Number indicating the group to which the menu 
37248* item belongs.
37249* @param {Number} p_nItemIndex Number indicating the index of the menu item 
37250* to be removed.
37251* @return {YAHOO.widget.MenuItem}
37252*/
37253_removeItemFromGroupByIndex: function (p_nGroupIndex, p_nItemIndex) {
37254
37255    var nGroupIndex = Lang.isNumber(p_nGroupIndex) ? p_nGroupIndex : 0,
37256        aGroup = this._getItemGroup(nGroupIndex),
37257        aArray,
37258        oItem,
37259        oUL;
37260
37261    if (aGroup) {
37262
37263        aArray = aGroup.splice(p_nItemIndex, 1);
37264        oItem = aArray[0];
37265    
37266        if (oItem) {
37267    
37268            // Update the index and className properties of each member        
37269            
37270            this._updateItemProperties(nGroupIndex);
37271    
37272            if (aGroup.length === 0) {
37273    
37274                // Remove the UL
37275    
37276                oUL = this._aListElements[nGroupIndex];
37277    
37278                if (oUL && oUL.parentNode) {
37279                    oUL.parentNode.removeChild(oUL);
37280                }
37281    
37282                // Remove the group from the array of items
37283    
37284                this._aItemGroups.splice(nGroupIndex, 1);
37285    
37286    
37287                // Remove the UL from the array of ULs
37288    
37289                this._aListElements.splice(nGroupIndex, 1);
37290    
37291    
37292                /*
37293                     Assign the "first-of-type" class to the new first UL 
37294                     in the collection
37295                */
37296    
37297                oUL = this._aListElements[0];
37298    
37299                if (oUL) {
37300    
37301                    Dom.addClass(oUL, _FIRST_OF_TYPE);
37302    
37303                }            
37304    
37305            }
37306    
37307
37308            this.itemRemovedEvent.fire(oItem);
37309            this.changeContentEvent.fire();
37310    
37311        }
37312
37313    }
37314
37315    // Return a reference to the item that was removed
37316
37317    return oItem;
37318    
37319},
37320
37321
37322/**
37323* @method _removeItemFromGroupByValue
37324* @description Removes a menu item from a group by reference.  Returns the 
37325* menu item that was removed.
37326* @private
37327* @param {Number} p_nGroupIndex Number indicating the group to which the
37328* menu item belongs.
37329* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem 
37330* instance to be removed.
37331* @return {YAHOO.widget.MenuItem}
37332*/    
37333_removeItemFromGroupByValue: function (p_nGroupIndex, p_oItem) {
37334
37335    var aGroup = this._getItemGroup(p_nGroupIndex),
37336        nItems,
37337        nItemIndex,
37338        returnVal,
37339        i;
37340
37341    if (aGroup) {
37342
37343        nItems = aGroup.length;
37344        nItemIndex = -1;
37345    
37346        if (nItems > 0) {
37347    
37348            i = nItems-1;
37349        
37350            do {
37351        
37352                if (aGroup[i] == p_oItem) {
37353        
37354                    nItemIndex = i;
37355                    break;    
37356        
37357                }
37358        
37359            }
37360            while (i--);
37361        
37362            if (nItemIndex > -1) {
37363        
37364                returnVal = this._removeItemFromGroupByIndex(p_nGroupIndex, nItemIndex);
37365        
37366            }
37367    
37368        }
37369    
37370    }
37371    
37372    return returnVal;
37373
37374},
37375
37376
37377/**
37378* @method _updateItemProperties
37379* @description Updates the "index," "groupindex," and "className" properties 
37380* of the menu items in the specified group. 
37381* @private
37382* @param {Number} p_nGroupIndex Number indicating the group of items to update.
37383*/
37384_updateItemProperties: function (p_nGroupIndex) {
37385
37386    var aGroup = this._getItemGroup(p_nGroupIndex),
37387        nItems = aGroup.length,
37388        oItem,
37389        oLI,
37390        i;
37391
37392
37393    if (nItems > 0) {
37394
37395        i = nItems - 1;
37396
37397        // Update the index and className properties of each member
37398    
37399        do {
37400
37401            oItem = aGroup[i];
37402
37403            if (oItem) {
37404    
37405                oLI = oItem.element;
37406
37407                oItem.index = i;
37408                oItem.groupIndex = p_nGroupIndex;
37409
37410                oLI.setAttribute(_GROUP_INDEX, p_nGroupIndex);
37411                oLI.setAttribute(_INDEX, i);
37412
37413                Dom.removeClass(oLI, _FIRST_OF_TYPE);
37414
37415            }
37416    
37417        }
37418        while (i--);
37419
37420
37421        if (oLI) {
37422
37423            Dom.addClass(oLI, _FIRST_OF_TYPE);
37424
37425        }
37426
37427    }
37428
37429},
37430
37431
37432/**
37433* @method _createItemGroup
37434* @description Creates a new menu item group (array) and its associated 
37435* <code>&#60;ul&#62;</code> element. Returns an aray of menu item groups.
37436* @private
37437* @param {Number} p_nIndex Number indicating the group to create.
37438* @return {Array}
37439*/
37440_createItemGroup: function (p_nIndex) {
37441
37442    var oUL,
37443        returnVal;
37444
37445    if (!this._aItemGroups[p_nIndex]) {
37446
37447        this._aItemGroups[p_nIndex] = [];
37448
37449        oUL = document.createElement(_UL_LOWERCASE);
37450
37451        this._aListElements[p_nIndex] = oUL;
37452
37453        returnVal = this._aItemGroups[p_nIndex];
37454
37455    }
37456    
37457    return returnVal;
37458
37459},
37460
37461
37462/**
37463* @method _getItemGroup
37464* @description Returns the menu item group at the specified index.
37465* @private
37466* @param {Number} p_nIndex Number indicating the index of the menu item group 
37467* to be retrieved.
37468* @return {Array}
37469*/
37470_getItemGroup: function (p_nIndex) {
37471
37472    var nIndex = Lang.isNumber(p_nIndex) ? p_nIndex : 0,
37473        aGroups = this._aItemGroups,
37474        returnVal;
37475
37476    if (nIndex in aGroups) {
37477
37478        returnVal = aGroups[nIndex];
37479
37480    }
37481    
37482    return returnVal;
37483
37484},
37485
37486
37487/**
37488* @method _configureSubmenu
37489* @description Subscribes the menu item's submenu to its parent menu's events.
37490* @private
37491* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem 
37492* instance with the submenu to be configured.
37493*/
37494_configureSubmenu: function (p_oItem) {
37495
37496    var oSubmenu = p_oItem.cfg.getProperty(_SUBMENU);
37497
37498    if (oSubmenu) {
37499            
37500        /*
37501            Listen for configuration changes to the parent menu 
37502            so they they can be applied to the submenu.
37503        */
37504
37505        this.cfg.configChangedEvent.subscribe(this._onParentMenuConfigChange, oSubmenu, true);
37506
37507        this.renderEvent.subscribe(this._onParentMenuRender, oSubmenu, true);
37508
37509    }
37510
37511},
37512
37513
37514
37515
37516/**
37517* @method _subscribeToItemEvents
37518* @description Subscribes a menu to a menu item's event.
37519* @private
37520* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem 
37521* instance whose events should be subscribed to.
37522*/
37523_subscribeToItemEvents: function (p_oItem) {
37524
37525    p_oItem.destroyEvent.subscribe(this._onMenuItemDestroy, p_oItem, this);
37526    p_oItem.cfg.configChangedEvent.subscribe(this._onMenuItemConfigChange, p_oItem, this);
37527
37528},
37529
37530
37531/**
37532* @method _onVisibleChange
37533* @description Change event handler for the the menu's "visible" configuration
37534* property.
37535* @private
37536* @param {String} p_sType String representing the name of the event that 
37537* was fired.
37538* @param {Array} p_aArgs Array of arguments sent when the event was fired.
37539*/
37540_onVisibleChange: function (p_sType, p_aArgs) {
37541
37542    var bVisible = p_aArgs[0];
37543    
37544    if (bVisible) {
37545
37546        Dom.addClass(this.element, _VISIBLE);
37547
37548    }
37549    else {
37550
37551        Dom.removeClass(this.element, _VISIBLE);
37552
37553    }
37554
37555},
37556
37557
37558/**
37559* @method _cancelHideDelay
37560* @description Cancels the call to "hideMenu."
37561* @private
37562*/
37563_cancelHideDelay: function () {
37564
37565    var oTimer = this.getRoot()._hideDelayTimer;
37566
37567    if (oTimer) {
37568
37569        oTimer.cancel();
37570
37571    }
37572
37573},
37574
37575
37576/**
37577* @method _execHideDelay
37578* @description Hides the menu after the number of milliseconds specified by 
37579* the "hidedelay" configuration property.
37580* @private
37581*/
37582_execHideDelay: function () {
37583
37584    this._cancelHideDelay();
37585
37586    var oRoot = this.getRoot();
37587
37588    oRoot._hideDelayTimer = Lang.later(oRoot.cfg.getProperty(_HIDE_DELAY), this, function () {
37589    
37590        if (oRoot.activeItem) {
37591            if (oRoot.hasFocus()) {
37592                oRoot.activeItem.focus();
37593            }
37594            oRoot.clearActiveItem();
37595        }
37596
37597        if (oRoot == this && !(this instanceof YAHOO.widget.MenuBar) && 
37598            this.cfg.getProperty(_POSITION) == _DYNAMIC) {
37599            this.hide();
37600        }
37601    });
37602
37603},
37604
37605
37606/**
37607* @method _cancelShowDelay
37608* @description Cancels the call to the "showMenu."
37609* @private
37610*/
37611_cancelShowDelay: function () {
37612    var oTimer = this.getRoot()._showDelayTimer;
37613    if (oTimer) {
37614        oTimer.cancel();
37615    }
37616},
37617
37618
37619/**
37620* @method _execSubmenuHideDelay
37621* @description Hides a submenu after the number of milliseconds specified by 
37622* the "submenuhidedelay" configuration property have elapsed.
37623* @private
37624* @param {YAHOO.widget.Menu} p_oSubmenu Object specifying the submenu that  
37625* should be hidden.
37626* @param {Number} p_nMouseX The x coordinate of the mouse when it left 
37627* the specified submenu's parent menu item.
37628* @param {Number} p_nHideDelay The number of milliseconds that should ellapse
37629* before the submenu is hidden.
37630*/
37631_execSubmenuHideDelay: function (p_oSubmenu, p_nMouseX, p_nHideDelay) {
37632
37633    p_oSubmenu._submenuHideDelayTimer = Lang.later(50, this, function () {
37634
37635        if (this._nCurrentMouseX > (p_nMouseX + 10)) {
37636
37637            p_oSubmenu._submenuHideDelayTimer = Lang.later(p_nHideDelay, p_oSubmenu, function () {
37638        
37639                this.hide();
37640
37641            });
37642
37643        }
37644        else {
37645
37646            p_oSubmenu.hide();
37647        
37648        }
37649    
37650    });
37651
37652},
37653
37654
37655
37656// Protected methods
37657
37658
37659/**
37660* @method _disableScrollHeader
37661* @description Disables the header used for scrolling the body of the menu.
37662* @protected
37663*/
37664_disableScrollHeader: function () {
37665
37666    if (!this._bHeaderDisabled) {
37667
37668        Dom.addClass(this.header, _TOP_SCROLLBAR_DISABLED);
37669        this._bHeaderDisabled = true;
37670
37671    }
37672
37673},
37674
37675
37676/**
37677* @method _disableScrollFooter
37678* @description Disables the footer used for scrolling the body of the menu.
37679* @protected
37680*/
37681_disableScrollFooter: function () {
37682
37683    if (!this._bFooterDisabled) {
37684
37685        Dom.addClass(this.footer, _BOTTOM_SCROLLBAR_DISABLED);
37686        this._bFooterDisabled = true;
37687
37688    }
37689
37690},
37691
37692
37693/**
37694* @method _enableScrollHeader
37695* @description Enables the header used for scrolling the body of the menu.
37696* @protected
37697*/
37698_enableScrollHeader: function () {
37699
37700    if (this._bHeaderDisabled) {
37701
37702        Dom.removeClass(this.header, _TOP_SCROLLBAR_DISABLED);
37703        this._bHeaderDisabled = false;
37704
37705    }
37706
37707},
37708
37709
37710/**
37711* @method _enableScrollFooter
37712* @description Enables the footer used for scrolling the body of the menu.
37713* @protected
37714*/
37715_enableScrollFooter: function () {
37716
37717    if (this._bFooterDisabled) {
37718
37719        Dom.removeClass(this.footer, _BOTTOM_SCROLLBAR_DISABLED);
37720        this._bFooterDisabled = false;
37721
37722    }
37723
37724},
37725
37726
37727/**
37728* @method _onMouseOver
37729* @description "mouseover" event handler for the menu.
37730* @protected
37731* @param {String} p_sType String representing the name of the event that 
37732* was fired.
37733* @param {Array} p_aArgs Array of arguments sent when the event was fired.
37734*/
37735_onMouseOver: function (p_sType, p_aArgs) {
37736
37737    var oEvent = p_aArgs[0],
37738        oItem = p_aArgs[1],
37739        oTarget = Event.getTarget(oEvent),
37740        oRoot = this.getRoot(),
37741        oSubmenuHideDelayTimer = this._submenuHideDelayTimer,
37742        oParentMenu,
37743        nShowDelay,
37744        bShowDelay,
37745        oActiveItem,
37746        oItemCfg,
37747        oSubmenu;
37748
37749
37750    var showSubmenu = function () {
37751
37752        if (this.parent.cfg.getProperty(_SELECTED)) {
37753
37754            this.show();
37755
37756        }
37757
37758    };
37759
37760
37761    if (!this._bStopMouseEventHandlers) {
37762    
37763        if (!this._bHandledMouseOverEvent && (oTarget == this.element || 
37764                Dom.isAncestor(this.element, oTarget))) {
37765    
37766            // Menu mouseover logic
37767
37768            if (this._useHideDelay) {
37769                this._cancelHideDelay();
37770            }
37771    
37772            this._nCurrentMouseX = 0;
37773    
37774            Event.on(this.element, _MOUSEMOVE, this._onMouseMove, this, true);
37775
37776
37777            /*
37778                If the mouse is moving from the submenu back to its corresponding menu item, 
37779                don't hide the submenu or clear the active MenuItem.
37780            */
37781
37782            if (!(oItem && Dom.isAncestor(oItem.element, Event.getRelatedTarget(oEvent)))) {
37783
37784                this.clearActiveItem();
37785
37786            }
37787    
37788
37789            if (this.parent && oSubmenuHideDelayTimer) {
37790    
37791                oSubmenuHideDelayTimer.cancel();
37792    
37793                this.parent.cfg.setProperty(_SELECTED, true);
37794    
37795                oParentMenu = this.parent.parent;
37796    
37797                oParentMenu._bHandledMouseOutEvent = true;
37798                oParentMenu._bHandledMouseOverEvent = false;
37799    
37800            }
37801    
37802    
37803            this._bHandledMouseOverEvent = true;
37804            this._bHandledMouseOutEvent = false;
37805        
37806        }
37807    
37808    
37809        if (oItem && !oItem.handledMouseOverEvent && !oItem.cfg.getProperty(_DISABLED) && 
37810            (oTarget == oItem.element || Dom.isAncestor(oItem.element, oTarget))) {
37811    
37812            // Menu Item mouseover logic
37813    
37814            nShowDelay = this.cfg.getProperty(_SHOW_DELAY);
37815            bShowDelay = (nShowDelay > 0);
37816    
37817    
37818            if (bShowDelay) {
37819            
37820                this._cancelShowDelay();
37821            
37822            }
37823    
37824    
37825            oActiveItem = this.activeItem;
37826        
37827            if (oActiveItem) {
37828        
37829                oActiveItem.cfg.setProperty(_SELECTED, false);
37830        
37831            }
37832    
37833    
37834            oItemCfg = oItem.cfg;
37835        
37836            // Select and focus the current menu item
37837        
37838            oItemCfg.setProperty(_SELECTED, true);
37839    
37840    
37841            if (this.hasFocus() || oRoot._hasFocus) {
37842            
37843                oItem.focus();
37844                
37845                oRoot._hasFocus = false;
37846            
37847            }
37848    
37849    
37850            if (this.cfg.getProperty(_AUTO_SUBMENU_DISPLAY)) {
37851    
37852                // Show the submenu this menu item
37853    
37854                oSubmenu = oItemCfg.getProperty(_SUBMENU);
37855            
37856                if (oSubmenu) {
37857            
37858                    if (bShowDelay) {
37859    
37860                        oRoot._showDelayTimer = 
37861                            Lang.later(oRoot.cfg.getProperty(_SHOW_DELAY), oSubmenu, showSubmenu);
37862            
37863                    }
37864                    else {
37865    
37866                        oSubmenu.show();
37867    
37868                    }
37869    
37870                }
37871    
37872            }                        
37873    
37874            oItem.handledMouseOverEvent = true;
37875            oItem.handledMouseOutEvent = false;
37876    
37877        }
37878    
37879    }
37880
37881},
37882
37883
37884/**
37885* @method _onMouseOut
37886* @description "mouseout" event handler for the menu.
37887* @protected
37888* @param {String} p_sType String representing the name of the event that 
37889* was fired.
37890* @param {Array} p_aArgs Array of arguments sent when the event was fired.
37891*/
37892_onMouseOut: function (p_sType, p_aArgs) {
37893
37894    var oEvent = p_aArgs[0],
37895        oItem = p_aArgs[1],
37896        oRelatedTarget = Event.getRelatedTarget(oEvent),
37897        bMovingToSubmenu = false,
37898        oItemCfg,
37899        oSubmenu,
37900        nSubmenuHideDelay,
37901        nShowDelay;
37902
37903
37904    YAHOO.log("onMouseout: this == " + this);
37905
37906    if (!this._bStopMouseEventHandlers) {
37907    
37908        if (oItem && !oItem.cfg.getProperty(_DISABLED)) {
37909    
37910            oItemCfg = oItem.cfg;
37911            oSubmenu = oItemCfg.getProperty(_SUBMENU);
37912    
37913    
37914            if (oSubmenu && (oRelatedTarget == oSubmenu.element || Dom.isAncestor(oSubmenu.element, oRelatedTarget))) {
37915                bMovingToSubmenu = true;
37916            }
37917    
37918            if (!oItem.handledMouseOutEvent && ((oRelatedTarget != oItem.element && !Dom.isAncestor(oItem.element, oRelatedTarget)) || bMovingToSubmenu)) {
37919                if (!bMovingToSubmenu) {
37920                    oItem.cfg.setProperty(_SELECTED, false);
37921                    if (oSubmenu) {
37922                        
37923                        nSubmenuHideDelay = this.cfg.getProperty(_SUBMENU_HIDE_DELAY);
37924                        nShowDelay = this.cfg.getProperty(_SHOW_DELAY);
37925                        if (!(this instanceof YAHOO.widget.MenuBar) && nSubmenuHideDelay > 0 && nSubmenuHideDelay >= nShowDelay) {
37926                            this._execSubmenuHideDelay(oSubmenu, Event.getPageX(oEvent), nSubmenuHideDelay);
37927                        } else {
37928                            oSubmenu.hide();
37929                        }
37930                    }
37931                }
37932    
37933                oItem.handledMouseOutEvent = true;
37934                oItem.handledMouseOverEvent = false;
37935            }
37936        }
37937
37938        YAHOO.log("onMouseout: oRelatedTarget = " + oRelatedTarget.className);
37939        YAHOO.log("onMouseout: this.element = " + this.element.id);
37940        YAHOO.log("onMouseout: Ancestorthis.element = " + Dom.isAncestor(this.element, oRelatedTarget));
37941        YAHOO.log("onMouseout: canHide = " + this._didMouseLeave(o
37941RelatedTarget));        
37942
37943        if (!this._bHandledMouseOutEvent) {
37944            if (this._didMouseLeave(oRelatedTarget) || bMovingToSubmenu) {
37945                // Menu mouseout logic
37946                if (this._useHideDelay) {
37947                    this._execHideDelay();
37948                }
37949    
37950                Event.removeListener(this.element, _MOUSEMOVE, this._onMouseMove);
37951        
37952                this._nCurrentMouseX = Event.getPageX(oEvent);
37953        
37954                this._bHandledMouseOutEvent = true;
37955                this._bHandledMouseOverEvent = false;
37956            }
37957        }
37958    }
37959
37960},
37961
37962/**
37963 * Utilility method to determine if we really moused out of the menu based on the related target
37964 * @method _didMouseLeave
37965 * @protected
37966 * @param {HTMLElement} oRelatedTarget The related target based on which we're making the decision
37967 * @return {boolean} true if it's OK to hide based on the related target.
37968 */
37969_didMouseLeave : function(oRelatedTarget) {
37970    // Hide if we're not moving back to the element from somewhere inside the element, or we're moving to an element inside the menu.
37971    // The shadow is treated as an edge case, inside inside the menu, but we get no further mouseouts, because it overflows the element,
37972    // so we need to close when moving to the menu. 
37973    return (oRelatedTarget === this._shadow || (oRelatedTarget != this.element && !Dom.isAncestor(this.element, oRelatedTarget)));
37974},
37975
37976/**
37977* @method _onMouseMove
37978* @description "click" event handler for the menu.
37979* @protected
37980* @param {Event} p_oEvent Object representing the DOM event object passed 
37981* back by the event utility (YAHOO.util.Event).
37982* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that 
37983* fired the event.
37984*/
37985_onMouseMove: function (p_oEvent, p_oMenu) {
37986
37987    if (!this._bStopMouseEventHandlers) {
37988    
37989        this._nCurrentMouseX = Event.getPageX(p_oEvent);
37990    
37991    }
37992
37993},
37994
37995
37996/**
37997* @method _onClick
37998* @description "click" event handler for the menu.
37999* @protected
38000* @param {String} p_sType String representing the name of the event that 
38001* was fired.
38002* @param {Array} p_aArgs Array of arguments sent when the event was fired.
38003*/
38004_onClick: function (p_sType, p_aArgs) {
38005
38006    var oEvent = p_aArgs[0],
38007        oItem = p_aArgs[1],
38008        bInMenuAnchor = false,
38009        oSubmenu,
38010        oMenu,
38011        oRoot,
38012        sId,
38013        sURL,
38014        nHashPos,
38015        nLen;
38016
38017
38018    var hide = function () {
38019        
38020        oRoot = this.getRoot();
38021
38022        if (oRoot instanceof YAHOO.widget.MenuBar || 
38023            oRoot.cfg.getProperty(_POSITION) == _STATIC) {
38024
38025            oRoot.clearActiveItem();
38026
38027        }
38028        else {
38029
38030            oRoot.hide();
38031        
38032        }
38033    
38034    };
38035
38036
38037    if (oItem) {
38038    
38039        if (oItem.cfg.getProperty(_DISABLED)) {
38040        
38041            Event.preventDefault(oEvent);
38042
38043            hide.call(this);
38044
38045        }
38046        else {
38047
38048            oSubmenu = oItem.cfg.getProperty(_SUBMENU);
38049    
38050            
38051            /*
38052                 Check if the URL of the anchor is pointing to an element that is 
38053                 a child of the menu.
38054            */
38055            
38056            sURL = oItem.cfg.getProperty(_URL);
38057
38058        
38059            if (sURL) {
38060    
38061                nHashPos = sURL.indexOf(_HASH);
38062    
38063                nLen = sURL.length;
38064    
38065    
38066                if (nHashPos != -1) {
38067    
38068                    sURL = sURL.substr(nHashPos, nLen);
38069        
38070                    nLen = sURL.length;
38071    
38072    
38073                    if (nLen > 1) {
38074    
38075                        sId = sURL.substr(1, nLen);
38076    
38077                        oMenu = YAHOO.widget.MenuManager.getMenu(sId);
38078                        
38079                        if (oMenu) {
38080
38081                            bInMenuAnchor = 
38082                                (this.getRoot() === oMenu.getRoot());
38083
38084                        }
38085                        
38086                    }
38087                    else if (nLen === 1) {
38088    
38089                        bInMenuAnchor = true;
38090                    
38091                    }
38092    
38093                }
38094            
38095            }
38096
38097    
38098            if (bInMenuAnchor && !oItem.cfg.getProperty(_TARGET)) {
38099    
38100                Event.preventDefault(oEvent);
38101                
38102
38103                if (UA.webkit) {
38104                
38105                    oItem.focus();
38106                
38107                }
38108                else {
38109
38110                    oItem.focusEvent.fire();
38111                
38112                }
38113            
38114            }
38115    
38116    
38117            if (!oSubmenu && !this.cfg.getProperty(_KEEP_OPEN)) {
38118    
38119                hide.call(this);
38120    
38121            }
38122            
38123        }
38124    
38125    }
38126
38127},
38128
38129/*
38130    This function is called to prevent a bug in Firefox.  In Firefox,
38131    moving a DOM element into a stationary mouse pointer will cause the 
38132    browser to fire mouse events.  This can result in the menu mouse
38133    event handlers being called uncessarily, especially when menus are 
38134    moved into a stationary mouse pointer as a result of a 
38135    key event handler.
38136*/
38137/**
38138 * Utility method to stop mouseevents from being fired if the DOM
38139 * changes under a stationary mouse pointer (as opposed to the mouse moving
38140 * over a DOM element).
38141 * 
38142 * @method _stopMouseEventHandlers
38143 * @private
38144 */
38145_stopMouseEventHandlers: function() {
38146    this._bStopMouseEventHandlers = true;
38147
38148    Lang.later(10, this, function () {
38149        this._bStopMouseEventHandlers = false;
38150    });
38151},
38152
38153/**
38154* @method _onKeyDown
38155* @description "keydown" event handler for the menu.
38156* @protected
38157* @param {String} p_sType String representing the name of the event that 
38158* was fired.
38159* @param {Array} p_aArgs Array of arguments sent when the event was fired.
38160*/
38161_onKeyDown: function (p_sType, p_aArgs) {
38162
38163    var oEvent = p_aArgs[0],
38164        oItem = p_aArgs[1],
38165        oSubmenu,
38166        oItemCfg,
38167        oParentItem,
38168        oRoot,
38169        oNextItem,
38170        oBody,
38171        nBodyScrollTop,
38172        nBodyOffsetHeight,
38173        aItems,
38174        nItems,
38175        nNextItemOffsetTop,
38176        nScrollTarget,
38177        oParentMenu,
38178        oFocusedEl;
38179
38180
38181    if (this._useHideDelay) {
38182        this._cancelHideDelay();
38183    }
38184
38185    if (oItem && !oItem.cfg.getProperty(_DISABLED)) {
38186
38187        oItemCfg = oItem.cfg;
38188        oParentItem = this.parent;
38189
38190        switch(oEvent.keyCode) {
38191    
38192            case 38:    // Up arrow
38193            case 40:    // Down arrow
38194    
38195                oNextItem = (oEvent.keyCode == 38) ? 
38196                    oItem.getPreviousEnabledSibling() : 
38197                    oItem.getNextEnabledSibling();
38198        
38199                if (oNextItem) {
38200
38201                    this.clearActiveItem();
38202
38203                    oNextItem.cfg.setProperty(_SELECTED, true);
38204                    oNextItem.focus();
38205
38206                    if (this.cfg.getProperty(_MAX_HEIGHT) > 0 || Dom.hasClass(this.body, _YUI_MENU_BODY_SCROLLED)) {
38207
38208                        oBody = this.body;
38209                        nBodyScrollTop = oBody.scrollTop;
38210                        nBodyOffsetHeight = oBody.offsetHeight;
38211                        aItems = this.getItems();
38212                        nItems = aItems.length - 1;
38213                        nNextItemOffsetTop = oNextItem.element.offsetTop;
38214
38215
38216                        if (oEvent.keyCode == 40 ) {    // Down
38217                       
38218                            if (nNextItemOffsetTop >= (nBodyOffsetHeight + nBodyScrollTop)) {
38219
38220                                oBody.scrollTop = nNextItemOffsetTop - nBodyOffsetHeight;
38221
38222                            }
38223                            else if (nNextItemOffsetTop <= nBodyScrollTop) {
38224                            
38225                                oBody.scrollTop = 0;
38226                            
38227                            }
38228
38229
38230                            if (oNextItem == aItems[nItems]) {
38231
38232                                oBody.scrollTop = oNextItem.element.offsetTop;
38233
38234                            }
38235
38236                        }
38237                        else {  // Up
38238
38239                            if (nNextItemOffsetTop <= nBodyScrollTop) {
38240
38241                                oBody.scrollTop = nNextItemOffsetTop - oNextItem.element.offsetHeight;
38242                            
38243                            }
38244                            else if (nNextItemOffsetTop >= (nBodyScrollTop + nBodyOffsetHeight)) {
38245                            
38246                                oBody.scrollTop = nNextItemOffsetTop;
38247                            
38248                            }
38249
38250
38251                            if (oNextItem == aItems[0]) {
38252                            
38253                                oBody.scrollTop = 0;
38254                            
38255                            }
38256
38257                        }
38258
38259
38260                        nBodyScrollTop = oBody.scrollTop;
38261                        nScrollTarget = oBody.scrollHeight - oBody.offsetHeight;
38262
38263                        if (nBodyScrollTop === 0) {
38264
38265                            this._disableScrollHeader();
38266                            this._enableScrollFooter();
38267
38268                        }
38269                        else if (nBodyScrollTop == nScrollTarget) {
38270
38271                             this._enableScrollHeader();
38272                             this._disableScrollFooter();
38273
38274                        }
38275                        else {
38276
38277                            this._enableScrollHeader();
38278                            this._enableScrollFooter();
38279
38280                        }
38281
38282                    }
38283
38284                }
38285
38286    
38287                Event.preventDefault(oEvent);
38288
38289                this._stopMouseEventHandlers();
38290    
38291            break;
38292            
38293    
38294            case 39:    // Right arrow
38295    
38296                oSubmenu = oItemCfg.getProperty(_SUBMENU);
38297    
38298                if (oSubmenu) {
38299    
38300                    if (!oItemCfg.getProperty(_SELECTED)) {
38301        
38302                        oItemCfg.setProperty(_SELECTED, true);
38303        
38304                    }
38305    
38306                    oSubmenu.show();
38307                    oSubmenu.setInitialFocus();
38308                    oSubmenu.setInitialSelection();
38309    
38310                }
38311                else {
38312    
38313                    oRoot = this.getRoot();
38314                    
38315                    if (oRoot instanceof YAHOO.widget.MenuBar) {
38316    
38317                        oNextItem = oRoot.activeItem.getNextEnabledSibling();
38318    
38319                        if (oNextItem) {
38320                        
38321                            oRoot.clearActiveItem();
38322    
38323                            oNextItem.cfg.setProperty(_SELECTED, true);
38324    
38325                            oSubmenu = oNextItem.cfg.getProperty(_SUBMENU);
38326    
38327                            if (oSubmenu) {
38328    
38329                                oSubmenu.show();
38330                                oSubmenu.setInitialFocus();
38331                            
38332                            }
38333                            else {
38334    
38335                                oNextItem.focus();
38336                            
38337                            }
38338                        
38339                        }
38340                    
38341                    }
38342                
38343                }
38344    
38345    
38346                Event.preventDefault(oEvent);
38347
38348                this._stopMouseEventHandlers();
38349
38350            break;
38351    
38352    
38353            case 37:    // Left arrow
38354    
38355                if (oParentItem) {
38356    
38357                    oParentMenu = oParentItem.parent;
38358    
38359                    if (oParentMenu instanceof YAHOO.widget.MenuBar) {
38360    
38361                        oNextItem = 
38362                            oParentMenu.activeItem.getPreviousEnabledSibling();
38363    
38364                        if (oNextItem) {
38365                        
38366                            oParentMenu.clearActiveItem();
38367    
38368                            oNextItem.cfg.setProperty(_SELECTED, true);
38369    
38370                            oSubmenu = oNextItem.cfg.getProperty(_SUBMENU);
38371    
38372                            if (oSubmenu) {
38373                            
38374                                oSubmenu.show();
38375                                oSubmenu.setInitialFocus();
38376                            
38377                            }
38378                            else {
38379    
38380                                oNextItem.focus();
38381                            
38382                            }
38383                        
38384                        } 
38385                    
38386                    }
38387                    else {
38388    
38389                        this.hide();
38390    
38391                        oParentItem.focus();
38392                    
38393                    }
38394    
38395                }
38396    
38397                Event.preventDefault(oEvent);
38398
38399                this._stopMouseEventHandlers();
38400
38401            break;        
38402    
38403        }
38404
38405
38406    }
38407
38408
38409    if (oEvent.keyCode == 27) { // Esc key
38410
38411        if (this.cfg.getProperty(_POSITION) == _DYNAMIC) {
38412        
38413            this.hide();
38414
38415            if (this.parent) {
38416
38417                this.parent.focus();
38418            
38419            }
38420            else {
38421                // Focus the element that previously had focus
38422
38423                oFocusedEl = this._focusedElement;
38424
38425                if (oFocusedEl && oFocusedEl.focus) {
38426
38427                    try {
38428                        oFocusedEl.focus();
38429                    }
38430                    catch(ex) {
38431                    }
38432
38433                }
38434                
38435            }
38436
38437        }
38438        else if (this.activeItem) {
38439
38440            oSubmenu = this.activeItem.cfg.getProperty(_SUBMENU);
38441
38442            if (oSubmenu && oSubmenu.cfg.getProperty(_VISIBLE)) {
38443            
38444                oSubmenu.hide();
38445                this.activeItem.focus();
38446            
38447            }
38448            else {
38449
38450                this.activeItem.blur();
38451                this.activeItem.cfg.setProperty(_SELECTED, false);
38452        
38453            }
38454        
38455        }
38456
38457
38458        Event.preventDefault(oEvent);
38459    
38460    }
38461    
38462},
38463
38464
38465/**
38466* @method _onKeyPress
38467* @description "keypress" event handler for a Menu instance.
38468* @protected
38469* @param {String} p_sType The name of the event that was fired.
38470* @param {Array} p_aArgs Collection of arguments sent when the event 
38471* was fired.
38472*/
38473_onKeyPress: function (p_sType, p_aArgs) {
38474    
38475    var oEvent = p_aArgs[0];
38476
38477
38478    if (oEvent.keyCode == 40 || oEvent.keyCode == 38) {
38479
38480        Event.preventDefault(oEvent);
38481
38482    }
38483
38484},
38485
38486
38487/**
38488* @method _onBlur
38489* @description "blur" event handler for a Menu instance.
38490* @protected
38491* @param {String} p_sType The name of the event that was fired.
38492* @param {Array} p_aArgs Collection of arguments sent when the event 
38493* was fired.
38494*/
38495_onBlur: function (p_sType, p_aArgs) {
38496        
38497    if (this._hasFocus) {
38498        this._hasFocus = false;
38499    }
38500
38501},
38502
38503/**
38504* @method _onYChange
38505* @description "y" event handler for a Menu instance.
38506* @protected
38507* @param {String} p_sType The name of the event that was fired.
38508* @param {Array} p_aArgs Collection of arguments sent when the event 
38509* was fired.
38510*/
38511_onYChange: function (p_sType, p_aArgs) {
38512
38513    var oParent = this.parent,
38514        nScrollTop,
38515        oIFrame,
38516        nY;
38517
38518
38519    if (oParent) {
38520
38521        nScrollTop = oParent.parent.body.scrollTop;
38522
38523
38524        if (nScrollTop > 0) {
38525    
38526            nY = (this.cfg.getProperty(_Y) - nScrollTop);
38527            
38528            Dom.setY(this.element, nY);
38529
38530            oIFrame = this.iframe;            
38531    
38532
38533            if (oIFrame) {
38534    
38535                Dom.setY(oIFrame, nY);
38536    
38537            }
38538            
38539            this.cfg.setProperty(_Y, nY, true);
38540        
38541        }
38542    
38543    }
38544
38545},
38546
38547
38548/**
38549* @method _onScrollTargetMouseOver
38550* @description "mouseover" event handler for the menu's "header" and "footer" 
38551* elements.  Used to scroll the body of the menu up and down when the 
38552* menu's "maxheight" configuration property is set to a value greater than 0.
38553* @protected
38554* @param {Event} p_oEvent Object representing the DOM event object passed 
38555* back by the event utility (YAHOO.util.Event).
38556* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that 
38557* fired the event.
38558*/
38559_onScrollTargetMouseOver: function (p_oEvent, p_oMenu) {
38560
38561    var oBodyScrollTimer = this._bodyScrollTimer;
38562
38563
38564    if (oBodyScrollTimer) {
38565
38566        oBodyScrollTimer.cancel();
38567
38568    }
38569
38570
38571    this._cancelHideDelay();
38572
38573
38574    var oTarget = Event.getTarget(p_oEvent),
38575        oBody = this.body,
38576        nScrollIncrement = this.cfg.getProperty(_SCROLL_INCREMENT),
38577        nScrollTarget,
38578        fnScrollFunction;
38579
38580
38581    function scrollBodyDown() {
38582
38583        var nScrollTop = oBody.scrollTop;
38584
38585
38586        if (nScrollTop < nScrollTarget) {
38587
38588            oBody.scrollTop = (nScrollTop + nScrollIncrement);
38589
38590            this._enableScrollHeader();
38591
38592        }
38593        else {
38594
38595            oBody.scrollTop = nScrollTarget;
38596
38597            this._bodyScrollTimer.cancel();
38598
38599            this._disableScrollFooter();
38600
38601        }
38602
38603    }
38604
38605
38606    function scrollBodyUp() {
38607
38608        var nScrollTop = oBody.scrollTop;
38609
38610
38611        if (nScrollTop > 0) {
38612
38613            oBody.scrollTop = (nScrollTop - nScrollIncrement);
38614
38615            this._enableScrollFooter();
38616
38617        }
38618        else {
38619
38620            oBody.scrollTop = 0;
38621
38622            this._bodyScrollTimer.cancel();
38623
38624            this._disableScrollHeader();
38625
38626        }
38627
38628    }
38629
38630    
38631    if (Dom.hasClass(oTarget, _HD)) {
38632
38633        fnScrollFunction = scrollBodyUp;
38634    
38635    }
38636    else {
38637
38638        nScrollTarget = oBody.scrollHeight - oBody.offsetHeight;
38639
38640        fnScrollFunction = scrollBodyDown;
38641    
38642    }
38643    
38644
38645    this._bodyScrollTimer = Lang.later(10, this, fnScrollFunction, null, true);
38646
38647},
38648
38649
38650/**
38651* @method _onScrollTargetMouseOut
38652* @description "mouseout" event handler for the menu's "header" and "footer" 
38653* elements.  Used to stop scrolling the body of the menu up and down when the 
38654* menu's "maxheight" configuration property is set to a value greater than 0.
38655* @protected
38656* @param {Event} p_oEvent Object representing the DOM event object passed 
38657* back by the event utility (YAHOO.util.Event).
38658* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that 
38659* fired the event.
38660*/
38661_onScrollTargetMouseOut: function (p_oEvent, p_oMenu) {
38662
38663    var oBodyScrollTimer = this._bodyScrollTimer;
38664
38665    if (oBodyScrollTimer) {
38666
38667        oBodyScrollTimer.cancel();
38668
38669    }
38670    
38671    this._cancelHideDelay();
38672
38673},
38674
38675
38676
38677// Private methods
38678
38679
38680/**
38681* @method _onInit
38682* @description "init" event handler for the menu.
38683* @private
38684* @param {String}
38684 p_sType String representing the name of the event that 
38685* was fired.
38686* @param {Array} p_aArgs Array of arguments sent when the event was fired.
38687*/
38688_onInit: function (p_sType, p_aArgs) {
38689
38690    this.cfg.subscribeToConfigEvent(_VISIBLE, this._onVisibleChange);
38691
38692    var bRootMenu = !this.parent,
38693        bLazyLoad = this.lazyLoad;
38694
38695
38696    /*
38697        Automatically initialize a menu's subtree if:
38698
38699        1) This is the root menu and lazyload is off
38700        
38701        2) This is the root menu, lazyload is on, but the menu is 
38702           already visible
38703
38704        3) This menu is a submenu and lazyload is off
38705    */
38706
38707
38708
38709    if (((bRootMenu && !bLazyLoad) || 
38710        (bRootMenu && (this.cfg.getProperty(_VISIBLE) || 
38711        this.cfg.getProperty(_POSITION) == _STATIC)) || 
38712        (!bRootMenu && !bLazyLoad)) && this.getItemGroups().length === 0) {
38713
38714        if (this.srcElement) {
38715
38716            this._initSubTree();
38717        
38718        }
38719
38720
38721        if (this.itemData) {
38722
38723            this.addItems(this.itemData);
38724
38725        }
38726    
38727    }
38728    else if (bLazyLoad) {
38729
38730        this.cfg.fireQueue();
38731    
38732    }
38733
38734},
38735
38736
38737/**
38738* @method _onBeforeRender
38739* @description "beforerender" event handler for the menu.  Appends all of the 
38740* <code>&#60;ul&#62;</code>, <code>&#60;li&#62;</code> and their accompanying 
38741* title elements to the body element of the menu.
38742* @private
38743* @param {String} p_sType String representing the name of the event that 
38744* was fired.
38745* @param {Array} p_aArgs Array of arguments sent when the event was fired.
38746*/
38747_onBeforeRender: function (p_sType, p_aArgs) {
38748
38749    var oEl = this.element,
38750        nListElements = this._aListElements.length,
38751        bFirstList = true,
38752        i = 0,
38753        oUL,
38754        oGroupTitle;
38755
38756    if (nListElements > 0) {
38757
38758        do {
38759
38760            oUL = this._aListElements[i];
38761
38762            if (oUL) {
38763
38764                if (bFirstList) {
38765        
38766                    Dom.addClass(oUL, _FIRST_OF_TYPE);
38767                    bFirstList = false;
38768        
38769                }
38770
38771
38772                if (!Dom.isAncestor(oEl, oUL)) {
38773
38774                    this.appendToBody(oUL);
38775
38776                }
38777
38778
38779                oGroupTitle = this._aGroupTitleElements[i];
38780
38781                if (oGroupTitle) {
38782
38783                    if (!Dom.isAncestor(oEl, oGroupTitle)) {
38784
38785                        oUL.parentNode.insertBefore(oGroupTitle, oUL);
38786
38787                    }
38788
38789
38790                    Dom.addClass(oUL, _HAS_TITLE);
38791
38792                }
38793
38794            }
38795
38796            i++;
38797
38798        }
38799        while (i < nListElements);
38800
38801    }
38802
38803},
38804
38805
38806/**
38807* @method _onRender
38808* @description "render" event handler for the menu.
38809* @private
38810* @param {String} p_sType String representing the name of the event that 
38811* was fired.
38812* @param {Array} p_aArgs Array of arguments sent when the event was fired.
38813*/
38814_onRender: function (p_sType, p_aArgs) {
38815
38816    if (this.cfg.getProperty(_POSITION) == _DYNAMIC) { 
38817
38818        if (!this.cfg.getProperty(_VISIBLE)) {
38819
38820            this.positionOffScreen();
38821
38822        }
38823    
38824    }
38825
38826},
38827
38828
38829
38830
38831
38832/**
38833* @method _onBeforeShow
38834* @description "beforeshow" event handler for the menu.
38835* @private
38836* @param {String} p_sType String representing the name of the event that 
38837* was fired.
38838* @param {Array} p_aArgs Array of arguments sent when the event was fired.
38839*/
38840_onBeforeShow: function (p_sType, p_aArgs) {
38841
38842    var nOptions,
38843        n,
38844        oSrcElement,
38845        oContainer = this.cfg.getProperty(_CONTAINER);
38846
38847
38848    if (this.lazyLoad && this.getItemGroups().length === 0) {
38849
38850        if (this.srcElement) {
38851        
38852            this._initSubTree();
38853
38854        }
38855
38856
38857        if (this.itemData) {
38858
38859            if (this.parent && this.parent.parent && 
38860                this.parent.parent.srcElement && 
38861                this.parent.parent.srcElement.tagName.toUpperCase() == 
38862                _SELECT) {
38863
38864                nOptions = this.itemData.length;
38865    
38866                for(n=0; n<nOptions; n++) {
38867
38868                    if (this.itemData[n].tagName) {
38869
38870                        this.addItem((new this.ITEM_TYPE(this.itemData[n])));
38871    
38872                    }
38873    
38874                }
38875            
38876            }
38877            else {
38878
38879                this.addItems(this.itemData);
38880            
38881            }
38882        
38883        }
38884
38885
38886        oSrcElement = this.srcElement;
38887
38888        if (oSrcElement) {
38889
38890            if (oSrcElement.tagName.toUpperCase() == _SELECT) {
38891
38892                if (Dom.inDocument(oSrcElement)) {
38893
38894                    this.render(oSrcElement.parentNode);
38895                
38896                }
38897                else {
38898                
38899                    this.render(oContainer);
38900                
38901                }
38902
38903            }
38904            else {
38905
38906                this.render();
38907
38908            }
38909
38910        }
38911        else {
38912
38913            if (this.parent) {
38914
38915                this.render(this.parent.element);     
38916
38917            }
38918            else {
38919
38920                this.render(oContainer);
38921
38922            }                
38923
38924        }
38925
38926    }
38927
38928
38929
38930    var oParent = this.parent,
38931        aAlignment;
38932
38933
38934    if (!oParent && this.cfg.getProperty(_POSITION) == _DYNAMIC) {
38935
38936        this.cfg.refireEvent(_XY);
38937   
38938    }
38939
38940
38941    if (oParent) {
38942
38943        aAlignment = oParent.parent.cfg.getProperty(_SUBMENU_ALIGNMENT);
38944        
38945        this.cfg.setProperty(_CONTEXT, [oParent.element, aAlignment[0], aAlignment[1]]);
38946        this.align();
38947    
38948    }
38949
38950},
38951
38952
38953getConstrainedY: function (y) {
38954
38955    var oMenu = this,
38956    
38957        aContext = oMenu.cfg.getProperty(_CONTEXT),
38958        nInitialMaxHeight = oMenu.cfg.getProperty(_MAX_HEIGHT),
38959
38960        nMaxHeight,
38961
38962        oOverlapPositions = {
38963
38964            "trbr": true,
38965            "tlbl": true,
38966            "bltl": true,
38967            "brtr": true
38968
38969        },
38970
38971        bPotentialContextOverlap = (aContext && oOverlapPositions[aContext[1] + aContext[2]]),
38972    
38973        oMenuEl = oMenu.element,
38974        nMenuOffsetHeight = oMenuEl.offsetHeight,
38975    
38976        nViewportOffset = Overlay.VIEWPORT_OFFSET,
38977        viewPortHeight = Dom.getViewportHeight(),
38978        scrollY = Dom.getDocumentScrollTop(),
38979
38980        bCanConstrain = 
38981            (oMenu.cfg.getProperty(_MIN_SCROLL_HEIGHT) + nViewportOffset < viewPortHeight),
38982
38983        nAvailableHeight,
38984
38985        oContextEl,
38986        nContextElY,
38987        nContextElHeight,
38988
38989        bFlipped = false,
38990
38991        nTopRegionHeight,
38992        nBottomRegionHeight,
38993
38994        topConstraint = scrollY + nViewportOffset,
38995        bottomConstraint = scrollY + viewPortHeight - nMenuOffsetHeight - nViewportOffset,
38996
38997        yNew = y;
38998        
38999
39000    var flipVertical = function () {
39001
39002        var nNewY;
39003    
39004        // The Menu is below the context element, flip it above
39005        if ((oMenu.cfg.getProperty(_Y) - scrollY) > nContextElY) { 
39006            nNewY = (nContextElY - nMenuOffsetHeight);
39007        }
39008        else {	// The Menu is above the context element, flip it below
39009            nNewY = (nContextElY + nContextElHeight);
39010        }
39011
39012        oMenu.cfg.setProperty(_Y, (nNewY + scrollY), true);
39013        
39014        return nNewY;
39015    
39016    };
39017
39018
39019    /*
39020         Uses the context element's position to calculate the availble height 
39021         above and below it to display its corresponding Menu.
39022    */
39023
39024    var getDisplayRegionHeight = function () {
39025
39026        // The Menu is below the context element
39027        if ((oMenu.cfg.getProperty(_Y) - scrollY) > nContextElY) {
39028            return (nBottomRegionHeight - nViewportOffset);				
39029        }
39030        else {	// The Menu is above the context element
39031            return (nTopRegionHeight - nViewportOffset);				
39032        }
39033
39034    };
39035
39036
39037    /*
39038        Sets the Menu's "y" configuration property to the correct value based on its
39039        current orientation.
39040    */ 
39041
39042    var alignY = function () {
39043
39044        var nNewY;
39045
39046        if ((oMenu.cfg.getProperty(_Y) - scrollY) > nContextElY) { 
39047            nNewY = (nContextElY + nContextElHeight);
39048        }
39049        else {	
39050            nNewY = (nContextElY - oMenuEl.offsetHeight);
39051        }
39052
39053        oMenu.cfg.setProperty(_Y, (nNewY + scrollY), true);
39054    
39055    };
39056
39057
39058    //	Resets the maxheight of the Menu to the value set by the user
39059
39060    var resetMaxHeight = function () {
39061
39062        oMenu._setScrollHeight(this.cfg.getProperty(_MAX_HEIGHT));
39063
39064        oMenu.hideEvent.unsubscribe(resetMaxHeight);
39065    
39066    };
39067
39068
39069    /*
39070        Trys to place the Menu in the best possible position (either above or 
39071        below its corresponding context element).
39072    */
39073
39074    var setVerticalPosition = function () {
39075
39076        var nDisplayRegionHeight = getDisplayRegionHeight(),
39077            bMenuHasItems = (oMenu.getItems().length > 0),
39078            nMenuMinScrollHeight,
39079            fnReturnVal;
39080
39081
39082        if (nMenuOffsetHeight > nDisplayRegionHeight) {
39083
39084            nMenuMinScrollHeight = 
39085                bMenuHasItems ? oMenu.cfg.getProperty(_MIN_SCROLL_HEIGHT) : nMenuOffsetHeight;
39086
39087
39088            if ((nDisplayRegionHeight > nMenuMinScrollHeight) && bMenuHasItems) {
39089                nMaxHeight = nDisplayRegionHeight;
39090            }
39091            else {
39092                nMaxHeight = nInitialMaxHeight;
39093            }
39094
39095
39096            oMenu._setScrollHeight(nMaxHeight);
39097            oMenu.hideEvent.subscribe(resetMaxHeight);
39098            
39099
39100            // Re-align the Menu since its height has just changed
39101            // as a result of the setting of the maxheight property.
39102
39103            alignY();
39104            
39105
39106            if (nDisplayRegionHeight < nMenuMinScrollHeight) {
39107
39108                if (bFlipped) {
39109    
39110                    /*
39111                         All possible positions and values for the "maxheight" 
39112                         configuration property have been tried, but none were 
39113                         successful, so fall back to the original size and position.
39114                    */
39115
39116                    flipVertical();
39117                    
39118                }
39119                else {
39120    
39121                    flipVertical();
39122
39123                    bFlipped = true;
39124    
39125                    fnReturnVal = setVerticalPosition();
39126    
39127                }
39128                
39129            }
39130        
39131        }
39132        else if (nMaxHeight && (nMaxHeight !== nInitialMaxHeight)) {
39133        
39134            oMenu._setScrollHeight(nInitialMaxHeight);
39135            oMenu.hideEvent.subscribe(resetMaxHeight);
39136
39137            // Re-align the Menu since its height has just changed
39138            // as a result of the setting of the maxheight property.
39139
39140            alignY();
39141        
39142        }
39143
39144        return fnReturnVal;
39145
39146    };
39147
39148
39149    // Determine if the current value for the Menu's "y" configuration property will
39150    // result in the Menu being positioned outside the boundaries of the viewport
39151
39152    if (y < topConstraint || y  > bottomConstraint) {
39153
39154        // The current value for the Menu's "y" configuration property WILL
39155        // result in the Menu being positioned outside the boundaries of the viewport
39156
39157        if (bCanConstrain) {
39158
39159            if (oMenu.cfg.getProperty(_PREVENT_CONTEXT_OVERLAP) && bPotentialContextOverlap) {
39160        
39161                //	SOLUTION #1:
39162                //	If the "preventcontextoverlap" configuration property is set to "true", 
39163                //	try to flip and/or scroll the Menu to both keep it inside the boundaries of the 
39164                //	viewport AND from overlaping its context element (MenuItem or MenuBarItem).
39165
39166                oContextEl = aContext[0];
39167                nContextElHeight = oContextEl.offsetHeight;
39168                nContextElY = (Dom.getY(oContextEl) - scrollY);
39169    
39170                nTopRegionHeight = nContextElY;
39171                nBottomRegionHeight = (viewPortHeight - (nContextElY + nContextElHeight));
39172    
39173                setVerticalPosition();
39174                
39175                yNew = oMenu.cfg.getProperty(_Y);
39176        
39177            }
39178            else if (!(oMenu instanceof YAHOO.widget.MenuBar) && 
39179                nMenuOffsetHeight >= viewPortHeight) {
39180
39181                //	SOLUTION #2:
39182                //	If the Menu exceeds the height of the viewport, introduce scroll bars
39183                //	to keep the Menu inside the boundaries of the viewport
39184
39185                nAvailableHeight = (viewPortHeight - (nViewportOffset * 2));
39186        
39187                if (nAvailableHeight > oMenu.cfg.getProperty(_MIN_SCROLL_HEIGHT)) {
39188        
39189                    oMenu._setScrollHeight(nAvailableHeight);
39190                    oMenu.hideEvent.subscribe(resetMaxHeight);
39191        
39192                    alignY();
39193                    
39194                    yNew = oMenu.cfg.getProperty(_Y);
39195                
39196                }
39197        
39198            }	
39199            else {
39200
39201                //	SOLUTION #3:
39202            
39203                if (y < topConstraint) {
39204                    yNew  = topConstraint;
39205                } else if (y  > bottomConstraint) {
39206                    yNew  = bottomConstraint;
39207                }				
39208            
39209            }
39210
39211        }
39212        else {
39213            //	The "y" configuration property cannot be set to a value that will keep
39214            //	entire Menu inside the boundary of the viewport.  Therefore, set  
39215            //	the "y" configuration property to scrollY to keep as much of the 
39216            //	Menu inside the viewport as possible.
39217            yNew = nViewportOffset + scrollY;
39218        }	
39219
39220    }
39221
39222    return yNew;
39223
39224},
39225
39226
39227/**
39228* @method _onHide
39229* @description "hide" event handler for the menu.
39230* @private
39231* @param {String} p_sType String representing the name of the event that 
39232* was fired.
39233* @param {Array} p_aArgs Array of arguments sent when the event was fired.
39234*/
39235_onHide: function (p_sType, p_aArgs) {
39236
39237    if (this.cfg.getProperty(_POSITION) === _DYNAMIC) {
39238    
39239        this.positionOffScreen();
39240    
39241    }
39242
39243},
39244
39245
39246/**
39247* @method _onShow
39248* @description "show" event handler for the menu.
39249* @private
39250* @param {String} p_sType String representing the name of the event that 
39251* was fired.
39252* @param {Array} p_aArgs Array of arguments sent when the event was fired.
39253*/
39254_onShow: function (p_sType, p_aArgs) {
39255
39256    var oParent = this.parent,
39257        oParentMenu,
39258        oElement,
39259        nOffsetWidth,
39260        sWidth;        
39261
39262
39263    function disableAutoSubmenuDisplay(p_oEvent) {
39264
39265        var oTarget;
39266
39267        if (p_oEvent.type == _MOUSEDOWN || (p_oEvent.type == _KEYDOWN && p_oEvent.keyCode == 27)) {
39268
39269            /*  
39270                Set the "autosubmenudisplay" to "false" if the user
39271                clicks outside the menu bar.
39272            */
39273
39274            oTarget = Event.getTarget(p_oEvent);
39275
39276            if (oTarget != oParentMenu.element || !Dom.isAncestor(oParentMenu.element, oTarget)) {
39277
39278                oParentMenu.cfg.setProperty(_AUTO_SUBMENU_DISPLAY, false);
39279
39280                Event.removeListener(document, _MOUSEDOWN, disableAutoSubmenuDisplay);
39281                Event.removeListener(document, _KEYDOWN, disableAutoSubmenuDisplay);
39282
39283            }
39284        
39285        }
39286
39287    }
39288
39289
39290    function onSubmenuHide(p_sType, p_aArgs, p_sWidth) {
39291    
39292        this.cfg.setProperty(_WIDTH, _EMPTY_STRING);
39293        this.hideEvent.unsubscribe(onSubmenuHide, p_sWidth);
39294    
39295    }
39296
39297
39298    if (oParent) {
39299
39300        oParentMenu = oParent.parent;
39301
39302
39303        if (!oParentMenu.cfg.getProperty(_AUTO_SUBMENU_DISPLAY) && 
39304            (oParentMenu instanceof YAHOO.widget.MenuBar || 
39305            oParentMenu.cfg.getProperty(_POSITION) == _STATIC)) {
39306
39307            oParentMenu.cfg.setProperty(_AUTO_SUBMENU_DISPLAY, true);
39308
39309            Event.on(document, _MOUSEDOWN, disableAutoSubmenuDisplay);                             
39310            Event.on(document, _KEYDOWN, disableAutoSubmenuDisplay);
39311
39312        }
39313
39314
39315        //	The following fixes an issue with the selected state of a MenuItem 
39316        //	not rendering correctly when a submenu is aligned to the left of
39317        //	its parent Menu instance.
39318
39319        if ((this.cfg.getProperty("x") < oParentMenu.cfg.getProperty("x")) && 
39320            (UA.gecko && UA.gecko < 1.9) && !this.cfg.getProperty(_WIDTH)) {
39321
39322            oElement = this.element;
39323            nOffsetWidth = oElement.offsetWidth;
39324            
39325            /*
39326                Measuring the difference of the offsetWidth before and after
39327                setting the "width" style attribute allows us to compute the 
39328                about of padding and borders applied to the element, which in 
39329                turn allows us to set the "width" property correctly.
39330            */
39331            
39332            oElement.style.width = nOffsetWidth + _PX;
39333            
39334            sWidth = (nOffsetWidth - (oElement.offsetWidth - nOffsetWidth)) + _PX;
39335            
39336            this.cfg.setProperty(_WIDTH, sWidth);
39337        
39338            this.hideEvent.subscribe(onSubmenuHide, sWidth);
39339        
39340        }
39341
39342    }
39343
39344
39345    /*
39346        Dynamically positioned, root Menus focus themselves when visible, and 
39347        will then, when hidden, restore focus to the UI control that had focus 
39348        before the Menu was made visible.
39349    */ 
39350
39351    if (this === this.getRoot() && this.cfg.getProperty(_POSITION) === _DYNAMIC) {
39352
39353        this._focusedElement = oFocusedElement;
39354        
39355        this.focus();
39356    
39357    }
39358
39359
39360},
39361
39362
39363/**
39364* @method _onBeforeHide
39365* @description "beforehide" event handler for the menu.
39366* @private
39367* @param {String} p_sType String representing the name of the event that 
39368* was fired.
39369* @param {Array} p_aArgs Array of arguments sent when the event was fired.
39370*/
39371_onBeforeHide: function (p_sType, p_aArgs) {
39372
39373    var oActiveItem = this.activeItem,
39374        oRoot = this.getRoot(),
39375        oConfig,
39376        oSubmenu;
39377
39378
39379    if (oActiveItem) {
39380
39381        oConfig = oActiveItem.cfg;
39382
39383        oConfig.setProperty(_SELECTED, false);
39384
39385        oSubmenu = oConfig.getProperty(_SUBMENU);
39386
39387        if (oSubmenu) {
39388
39389            oSubmenu.hide();
39390
39391        }
39392
39393    }
39394
39395
39396    /*
39397        Focus can get lost in IE when the mouse is moving from a submenu back to its parent Menu.  
39398        For this reason, it is necessary to maintain the focused state in a private property 
39399        so that the _onMouseOver event handler is able to determined whether or not to set focus
39400        to MenuItems as the user is moving the mouse.
39401    */ 
39402
39403    if (UA.ie && this.cfg.getProperty(_POSITION) === _DYNAMIC && this.parent) {
39404
39405        oRoot._hasFocus = this.hasFocus();
39406    
39407    }
39408
39409
39410    if (oRoot == this) {
39411
39412        oRoot.blur();
39413    
39414    }
39415
39416},
39417
39418
39419/**
39420* @method _onParentMenuConfigChange
39421* @description "configchange" event handler for a submenu.
39422* @private
39423* @param {String}
39423 p_sType String representing the name of the event that 
39424* was fired.
39425* @param {Array} p_aArgs Array of arguments sent when the event was fired.
39426* @param {YAHOO.widget.Menu} p_oSubmenu Object representing the submenu that 
39427* subscribed to the event.
39428*/
39429_onParentMenuConfigChange: function (p_sType, p_aArgs, p_oSubmenu) {
39430    
39431    var sPropertyName = p_aArgs[0][0],
39432        oPropertyValue = p_aArgs[0][1];
39433
39434    switch(sPropertyName) {
39435
39436        case _IFRAME:
39437        case _CONSTRAIN_TO_VIEWPORT:
39438        case _HIDE_DELAY:
39439        case _SHOW_DELAY:
39440        case _SUBMENU_HIDE_DELAY:
39441        case _CLICK_TO_HIDE:
39442        case _EFFECT:
39443        case _CLASSNAME:
39444        case _SCROLL_INCREMENT:
39445        case _MAX_HEIGHT:
39446        case _MIN_SCROLL_HEIGHT:
39447        case _MONITOR_RESIZE:
39448        case _SHADOW:
39449        case _PREVENT_CONTEXT_OVERLAP:
39450        case _KEEP_OPEN:
39451
39452            p_oSubmenu.cfg.setProperty(sPropertyName, oPropertyValue);
39453                
39454        break;
39455        
39456        case _SUBMENU_ALIGNMENT:
39457
39458            if (!(this.parent.parent instanceof YAHOO.widget.MenuBar)) {
39459        
39460                p_oSubmenu.cfg.setProperty(sPropertyName, oPropertyValue);
39461        
39462            }
39463        
39464        break;
39465        
39466    }
39467    
39468},
39469
39470
39471/**
39472* @method _onParentMenuRender
39473* @description "render" event handler for a submenu.  Renders a  
39474* submenu in response to the firing of its parent's "render" event.
39475* @private
39476* @param {String} p_sType String representing the name of the event that 
39477* was fired.
39478* @param {Array} p_aArgs Array of arguments sent when the event was fired.
39479* @param {YAHOO.widget.Menu} p_oSubmenu Object representing the submenu that 
39480* subscribed to the event.
39481*/
39482_onParentMenuRender: function (p_sType, p_aArgs, p_oSubmenu) {
39483
39484    var oParentMenu = p_oSubmenu.parent.parent,
39485        oParentCfg = oParentMenu.cfg,
39486
39487        oConfig = {
39488
39489            constraintoviewport: oParentCfg.getProperty(_CONSTRAIN_TO_VIEWPORT),
39490
39491            xy: [0,0],
39492
39493            clicktohide: oParentCfg.getProperty(_CLICK_TO_HIDE),
39494                
39495            effect: oParentCfg.getProperty(_EFFECT),
39496
39497            showdelay: oParentCfg.getProperty(_SHOW_DELAY),
39498            
39499            hidedelay: oParentCfg.getProperty(_HIDE_DELAY),
39500
39501            submenuhidedelay: oParentCfg.getProperty(_SUBMENU_HIDE_DELAY),
39502
39503            classname: oParentCfg.getProperty(_CLASSNAME),
39504            
39505            scrollincrement: oParentCfg.getProperty(_SCROLL_INCREMENT),
39506            
39507            maxheight: oParentCfg.getProperty(_MAX_HEIGHT),
39508
39509            minscrollheight: oParentCfg.getProperty(_MIN_SCROLL_HEIGHT),
39510            
39511            iframe: oParentCfg.getProperty(_IFRAME),
39512            
39513            shadow: oParentCfg.getProperty(_SHADOW),
39514
39515            preventcontextoverlap: oParentCfg.getProperty(_PREVENT_CONTEXT_OVERLAP),
39516            
39517            monitorresize: oParentCfg.getProperty(_MONITOR_RESIZE),
39518
39519            keepopen: oParentCfg.getProperty(_KEEP_OPEN)
39520
39521        },
39522        
39523        oLI;
39524
39525
39526    
39527    if (!(oParentMenu instanceof YAHOO.widget.MenuBar)) {
39528
39529        oConfig[_SUBMENU_ALIGNMENT] = oParentCfg.getProperty(_SUBMENU_ALIGNMENT);
39530
39531    }
39532
39533
39534    p_oSubmenu.cfg.applyConfig(oConfig);
39535
39536
39537    if (!this.lazyLoad) {
39538
39539        oLI = this.parent.element;
39540
39541        if (this.element.parentNode == oLI) {
39542    
39543            this.render();
39544    
39545        }
39546        else {
39547
39548            this.render(oLI);
39549    
39550        }
39551
39552    }
39553    
39554},
39555
39556
39557/**
39558* @method _onMenuItemDestroy
39559* @description "destroy" event handler for the menu's items.
39560* @private
39561* @param {String} p_sType String representing the name of the event 
39562* that was fired.
39563* @param {Array} p_aArgs Array of arguments sent when the event was fired.
39564* @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item 
39565* that fired the event.
39566*/
39567_onMenuItemDestroy: function (p_sType, p_aArgs, p_oItem) {
39568
39569    this._removeItemFromGroupByValue(p_oItem.groupIndex, p_oItem);
39570
39571},
39572
39573
39574/**
39575* @method _onMenuItemConfigChange
39576* @description "configchange" event handler for the menu's items.
39577* @private
39578* @param {String} p_sType String representing the name of the event that 
39579* was fired.
39580* @param {Array} p_aArgs Array of arguments sent when the event was fired.
39581* @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item 
39582* that fired the event.
39583*/
39584_onMenuItemConfigChange: function (p_sType, p_aArgs, p_oItem) {
39585
39586    var sPropertyName = p_aArgs[0][0],
39587        oPropertyValue = p_aArgs[0][1],
39588        oSubmenu;
39589
39590
39591    switch(sPropertyName) {
39592
39593        case _SELECTED:
39594
39595            if (oPropertyValue === true) {
39596
39597                this.activeItem = p_oItem;
39598            
39599            }
39600
39601        break;
39602
39603        case _SUBMENU:
39604
39605            oSubmenu = p_aArgs[0][1];
39606
39607            if (oSubmenu) {
39608
39609                this._configureSubmenu(p_oItem);
39610
39611            }
39612
39613        break;
39614
39615    }
39616
39617},
39618
39619
39620
39621// Public event handlers for configuration properties
39622
39623
39624/**
39625* @method configVisible
39626* @description Event handler for when the "visible" configuration property 
39627* the menu changes.
39628* @param {String} p_sType String representing the name of the event that 
39629* was fired.
39630* @param {Array} p_aArgs Array of arguments sent when the event was fired.
39631* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that 
39632* fired the event.
39633*/
39634configVisible: function (p_sType, p_aArgs, p_oMenu) {
39635
39636    var bVisible,
39637        sDisplay;
39638
39639    if (this.cfg.getProperty(_POSITION) == _DYNAMIC) {
39640
39641        Menu.superclass.configVisible.call(this, p_sType, p_aArgs, p_oMenu);
39642
39643    }
39644    else {
39645
39646        bVisible = p_aArgs[0];
39647        sDisplay = Dom.getStyle(this.element, _DISPLAY);
39648
39649        Dom.setStyle(this.element, _VISIBILITY, _VISIBLE);
39650
39651        if (bVisible) {
39652
39653            if (sDisplay != _BLOCK) {
39654                this.beforeShowEvent.fire();
39655                Dom.setStyle(this.element, _DISPLAY, _BLOCK);
39656                this.showEvent.fire();
39657            }
39658        
39659        }
39660        else {
39661
39662            if (sDisplay == _BLOCK) {
39663                this.beforeHideEvent.fire();
39664                Dom.setStyle(this.element, _DISPLAY, _NONE);
39665                this.hideEvent.fire();
39666            }
39667        
39668        }
39669
39670    }
39671
39672},
39673
39674
39675/**
39676* @method configPosition
39677* @description Event handler for when the "position" configuration property 
39678* of the menu changes.
39679* @param {String} p_sType String representing the name of the event that 
39680* was fired.
39681* @param {Array} p_aArgs Array of arguments sent when the event was fired.
39682* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that 
39683* fired the event.
39684*/
39685configPosition: function (p_sType, p_aArgs, p_oMenu) {
39686
39687    var oElement = this.element,
39688        sCSSPosition = p_aArgs[0] == _STATIC ? _STATIC : _ABSOLUTE,
39689        oCfg = this.cfg,
39690        nZIndex;
39691
39692
39693    Dom.setStyle(oElement, _POSITION, sCSSPosition);
39694
39695
39696    if (sCSSPosition == _STATIC) {
39697
39698        // Statically positioned menus are visible by default
39699        
39700        Dom.setStyle(oElement, _DISPLAY, _BLOCK);
39701
39702        oCfg.setProperty(_VISIBLE, true);
39703
39704    }
39705    else {
39706
39707        /*
39708            Even though the "visible" property is queued to 
39709            "false" by default, we need to set the "visibility" property to 
39710            "hidden" since Overlay's "configVisible" implementation checks the 
39711            element's "visibility" style property before deciding whether 
39712            or not to show an Overlay instance.
39713        */
39714
39715        Dom.setStyle(oElement, _VISIBILITY, _HIDDEN);
39716    
39717    }
39718
39719
39720     if (sCSSPosition == _ABSOLUTE) {
39721         nZIndex = oCfg.getProperty(_ZINDEX);
39722
39723         if (!nZIndex || nZIndex === 0) {
39724             oCfg.setProperty(_ZINDEX, 1);
39725         }
39726
39727     }
39728
39729},
39730
39731
39732/**
39733* @method configIframe
39734* @description Event handler for when the "iframe" configuration property of 
39735* the menu changes.
39736* @param {String} p_sType String representing the name of the event that 
39737* was fired.
39738* @param {Array} p_aArgs Array of arguments sent when the event was fired.
39739* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that 
39740* fired the event.
39741*/
39742configIframe: function (p_sType, p_aArgs, p_oMenu) {    
39743
39744    if (this.cfg.getProperty(_POSITION) == _DYNAMIC) {
39745
39746        Menu.superclass.configIframe.call(this, p_sType, p_aArgs, p_oMenu);
39747
39748    }
39749
39750},
39751
39752
39753/**
39754* @method configHideDelay
39755* @description Event handler for when the "hidedelay" configuration property 
39756* of the menu changes.
39757* @param {String}
39757 p_sType String representing the name of the event that 
39758* was fired.
39759* @param {Array} p_aArgs Array of arguments sent when the event was fired.
39760* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that 
39761* fired the event.
39762*/
39763configHideDelay: function (p_sType, p_aArgs, p_oMenu) {
39764
39765    var nHideDelay = p_aArgs[0];
39766
39767    this._useHideDelay = (nHideDelay > 0);
39768
39769},
39770
39771
39772/**
39773* @method configContainer
39774* @description Event handler for when the "container" configuration property 
39775* of the menu changes.
39776* @param {String} p_sType String representing the name of the event that 
39777* was fired.
39778* @param {Array} p_aArgs Array of arguments sent when the event was fired.
39779* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that 
39780* fired the event.
39781*/
39782configContainer: function (p_sType, p_aArgs, p_oMenu) {
39783
39784    var oElement = p_aArgs[0];
39785
39786    if (Lang.isString(oElement)) {
39787
39788        this.cfg.setProperty(_CONTAINER, Dom.get(oElement), true);
39789
39790    }
39791
39792},
39793
39794
39795/**
39796* @method _clearSetWidthFlag
39797* @description Change event listener for the "width" configuration property.  This listener is 
39798* added when a Menu's "width" configuration property is set by the "_setScrollHeight" method, and 
39799* is used to set the "_widthSetForScroll" property to "false" if the "width" configuration property 
39800* is changed after it was set by the "_setScrollHeight" method.  If the "_widthSetForScroll" 
39801* property is set to "false", and the "_setScrollHeight" method is in the process of tearing down 
39802* scrolling functionality, it will maintain the Menu's new width rather than reseting it.
39803* @private
39804*/
39805_clearSetWidthFlag: function () {
39806
39807    this._widthSetForScroll = false;
39808    
39809    this.cfg.unsubscribeFromConfigEvent(_WIDTH, this._clearSetWidthFlag);
39810
39811},
39812
39813/**
39814 * @method _subscribeScrollHandlers
39815 * @param {HTMLElement} oHeader The scroll header element
39816 * @param {HTMLElement} oFooter The scroll footer element
39817 */
39818_subscribeScrollHandlers : function(oHeader, oFooter) {
39819    var fnMouseOver = this._onScrollTargetMouseOver;
39820    var fnMouseOut = this._onScrollTargetMouseOut;
39821
39822    Event.on(oHeader, _MOUSEOVER, fnMouseOver, this, true);
39823    Event.on(oHeader, _MOUSEOUT, fnMouseOut, this, true);
39824    Event.on(oFooter, _MOUSEOVER, fnMouseOver, this, true);
39825    Event.on(oFooter, _MOUSEOUT, fnMouseOut, this, true);
39826},
39827
39828/**
39829 * @method _unsubscribeScrollHandlers 
39830 * @param {HTMLElement} oHeader The scroll header element
39831 * @param {HTMLElement} oFooter The scroll footer element
39832 */
39833_unsubscribeScrollHandlers : function(oHeader, oFooter) {
39834    var fnMouseOver = this._onScrollTargetMouseOver;
39835    var fnMouseOut = this._onScrollTargetMouseOut;
39836    
39837    Event.removeListener(oHeader, _MOUSEOVER, fnMouseOver);
39838    Event.removeListener(oHeader, _MOUSEOUT, fnMouseOut);
39839    Event.removeListener(oFooter, _MOUSEOVER, fnMouseOver);
39840    Event.removeListener(oFooter, _MOUSEOUT, fnMouseOut);
39841},
39842
39843/**
39844* @method _setScrollHeight
39845* @description 
39846* @param {String} p_nScrollHeight Number representing the scrolling height of the Menu.
39847* @private
39848*/
39849_setScrollHeight: function (p_nScrollHeight) {
39850
39851    var nScrollHeight = p_nScrollHeight,
39852        bRefireIFrameAndShadow = false,
39853        bSetWidth = false,
39854        oElement,
39855        oBody,
39856        oHeader,
39857        oFooter,
39858        nMinScrollHeight,
39859        nHeight,
39860        nOffsetWidth,
39861        sWidth;
39862
39863    if (this.getItems().length > 0) {
39864
39865        oElement = this.element;
39866        oBody = this.body;
39867        oHeader = this.header;
39868        oFooter = this.footer;
39869        nMinScrollHeight = this.cfg.getProperty(_MIN_SCROLL_HEIGHT);
39870
39871        if (nScrollHeight > 0 && nScrollHeight < nMinScrollHeight) {
39872            nScrollHeight = nMinScrollHeight;
39873        }
39874
39875        Dom.setStyle(oBody, _HEIGHT, _EMPTY_STRING);
39876        Dom.removeClass(oBody, _YUI_MENU_BODY_SCROLLED);
39877        oBody.scrollTop = 0;
39878
39879        //	Need to set a width for the Menu to fix the following problems in 
39880        //	Firefox 2 and IE:
39881
39882        //	#1) Scrolled Menus will render at 1px wide in Firefox 2
39883
39884        //	#2) There is a bug in gecko-based browsers where an element whose 
39885        //	"position" property is set to "absolute" and "overflow" property is 
39886        //	set to "hidden" will not render at the correct width when its 
39887        //	offsetParent's "position" property is also set to "absolute."  It is 
39888        //	possible to work around this bug by specifying a value for the width 
39889        //	property in addition to overflow.
39890
39891        //	#3) In IE it is necessary to give the Menu a width before the 
39892        //	scrollbars are rendered to prevent the Menu from rendering with a 
39893        //	width that is 100% of the browser viewport.
39894
39895        bSetWidth = ((UA.gecko && UA.gecko < 1.9) || UA.ie);
39896
39897        if (nScrollHeight > 0 && bSetWidth && !this.cfg.getProperty(_WIDTH)) {
39898
39899            nOffsetWidth = oElement.offsetWidth;
39900    
39901            /*
39902                Measuring the difference of the offsetWidth before and after
39903                setting the "width" style attribute allows us to compute the 
39904                about of padding and borders applied to the element, which in 
39905                turn allows us to set the "width" property correctly.
39906            */
39907            
39908            oElement.style.width = nOffsetWidth + _PX;
39909    
39910            sWidth = (nOffsetWidth - (oElement.offsetWidth - nOffsetWidth)) + _PX;
39911
39912
39913            this.cfg.unsubscribeFromConfigEvent(_WIDTH, this._clearSetWidthFlag);
39914
39915            YAHOO.log("Setting the \"width\" configuration property to " + sWidth + " for srolling.", 
39916                "info", this.toString());
39917
39918            this.cfg.setProperty(_WIDTH, sWidth);
39919
39920
39921            /*
39922                Set a flag (_widthSetForScroll) to maintain some history regarding how the 
39923                "width" configuration property was set.  If the "width" configuration property 
39924                is set by something other than the "_setScrollHeight" method, it will be 
39925                necessary to maintain that new value and not clear the width if scrolling 
39926                is turned off.
39927            */
39928
39929            this._widthSetForScroll = true;
39930
39931            this.cfg.subscribeToConfigEvent(_WIDTH, this._clearSetWidthFlag);
39932    
39933        }
39934
39935
39936        if (nScrollHeight > 0 && (!oHeader && !oFooter)) {
39937
39938            YAHOO.log("Creating header and footer for scrolling.", "info", this.toString());
39939
39940            this.setHeader(_NON_BREAKING_SPACE);
39941            this.setFooter(_NON_BREAKING_SPACE);
39942
39943            oHeader = this.header;
39944            oFooter = this.footer;
39945
39946            Dom.addClass(oHeader, _TOP_SCROLLBAR);
39947            Dom.addClass(oFooter, _BOTTOM_SCROLLBAR);
39948
39949            oElement.insertBefore(oHeader, oBody);
39950            oElement.appendChild(oFooter);
39951        
39952        }
39953
39954        nHeight = nScrollHeight;
39955
39956        if (oHeader && oFooter) {
39957            nHeight = (nHeight - (oHeader.offsetHeight + oFooter.offsetHeight));
39958        }
39959    
39960    
39961        if ((nHeight > 0) && (oBody.offsetHeight > nScrollHeight)) {
39962
39963            YAHOO.log("Setting up styles and event handlers for scrolling.", 
39964                "info", this.toString());
39965    
39966            Dom.addClass(oBody, _YUI_MENU_BODY_SCROLLED);
39967            Dom.setStyle(oBody, _HEIGHT, (nHeight + _PX));
39968
39969            if (!this._hasScrollEventHandlers) {
39970                this._subscribeScrollHandlers(oHeader, oFooter);
39971                this._hasScrollEventHandlers = true;
39972            }
39973    
39974            this._disableScrollHeader();
39975            this._enableScrollFooter();
39976            
39977            bRefireIFrameAndShadow = true;			
39978    
39979        }
39980        else if (oHeader && oFooter) {
39981
39982            YAHOO.log("Removing styles and event handlers for scrolling.", "info", this.toString());
39983    
39984
39985            /*
39986                Only clear the the "width" configuration property if it was set the 
39987                "_setScrollHeight" method and wasn't changed by some other means after it was set.
39988            */	
39989    
39990            if (this._widthSetForScroll) {
39991    
39992                YAHOO.log("Clearing width used for scrolling.", "info", this.toString());
39993
39994                this._widthSetForScroll = false;
39995
39996                this.cfg.unsubscribeFromConfigEvent(_WIDTH, this._clearSetWidthFlag);
39997    
39998                this.cfg.setProperty(_WIDTH, _EMPTY_STRING);
39999            
40000            }
40001    
40002    
40003            this._enableScrollHeader();
40004            this._enableScrollFooter();
40005    
40006            if (this._hasScrollEventHandlers) {
40007                this._unsubscribeScrollHandlers(oHeader, oFooter);    
40008                this._hasScrollEventHandlers = false;
40009            }
40010
40011            oElement.removeChild(oHeader);
40012            oElement.removeChild(oFooter);
40013    
40014            this.header = null;
40015            this.footer = null;
40016            
40017            bRefireIFrameAndShadow = true;
40018        
40019        }
40020
40021
40022        if (bRefireIFrameAndShadow) {
40023    
40024            this.cfg.refireEvent(_IFRAME);
40025            this.cfg.refireEvent(_SHADOW);
40026        
40027        }
40028    
40029    }
40030
40031},
40032
40033
40034/**
40035* @method _setMaxHeight
40036* @description "renderEvent" handler used to defer the setting of the 
40037* "maxheight" configuration property until the menu is rendered in lazy 
40038* load scenarios.
40039* @param {String} p_sType The name of the event that was fired.
40040* @param {Array} p_aArgs Collection of arguments sent when the event 
40041* was fired.
40042* @param {Number} p_nMaxHeight Number representing the value to set for the 
40043* "maxheight" configuration property.
40044* @private
40045*/
40046_setMaxHeight: function (p_sType, p_aArgs, p_nMaxHeight) {
40047
40048    this._setScrollHeight(p_nMaxHeight);
40049    this.renderEvent.unsubscribe(this._setMaxHeight);
40050
40051},
40052
40053
40054/**
40055* @method configMaxHeight
40056* @description Event handler for when the "maxheight" configuration property of 
40057* a Menu changes.
40058* @param {String} p_sType The name of the event that was fired.
40059* @param {Array} p_aArgs Collection of arguments sent when the event 
40060* was fired.
40061* @param {YAHOO.widget.Menu} p_oMenu The Menu instance fired
40062* the event.
40063*/
40064configMaxHeight: function (p_sType, p_aArgs, p_oMenu) {
40065
40066    var nMaxHeight = p_aArgs[0];
40067
40068    if (this.lazyLoad && !this.body && nMaxHeight > 0) {
40069    
40070        this.renderEvent.subscribe(this._setMaxHeight, nMaxHeight, this);
40071
40072    }
40073    else {
40074
40075        this._setScrollHeight(nMaxHeight);
40076    
40077    }
40078
40079},
40080
40081
40082/**
40083* @method configClassName
40084* @description Event handler for when the "classname" configuration property of 
40085* a menu changes.
40086* @param {String} p_sType The name of the event that was fired.
40087* @param {Array} p_aArgs Collection of arguments sent when the event was fired.
40088* @param {YAHOO.widget.Menu} p_oMenu The Menu instance fired the event.
40089*/
40090configClassName: function (p_sType, p_aArgs, p_oMenu) {
40091
40092    var sClassName = p_aArgs[0];
40093
40094    if (this._sClassName) {
40095
40096        Dom.removeClass(this.element, this._sClassName);
40097
40098    }
40099
40100    Dom.addClass(this.element, sClassName);
40101    this._sClassName = sClassName;
40102
40103},
40104
40105
40106/**
40107* @method _onItemAdded
40108* @description "itemadded" event handler for a Menu instance.
40109* @private
40110* @param {String} p_sType The name of the event that was fired.
40111* @param {Array} p_aArgs Collection of arguments sent when the event 
40112* was fired.
40113*/
40114_onItemAdded: function (p_sType, p_aArgs) {
40115
40116    var oItem = p_aArgs[0];
40117    
40118    if (oItem) {
40119
40120        oItem.cfg.setProperty(_DISABLED, true);
40121    
40122    }
40123
40124},
40125
40126
40127/**
40128* @method configDisabled
40129* @description Event handler for when the "disabled" configuration property of 
40130* a menu changes.
40131* @param {String} p_sType The name of the event that was fired.
40132* @param {Array} p_aArgs Collection of arguments sent when the event was fired.
40133* @param {YAHOO.widget.Menu} p_oMenu The Menu instance fired the event.
40134*/
40135configDisabled: function (p_sType, p_aArgs, p_oMenu) {
40136
40137    var bDisabled = p_aArgs[0],
40138        aItems = this.getItems(),
40139        nItems,
40140        i;
40141
40142    if (Lang.isArray(aItems)) {
40143
40144        nItems = aItems.length;
40145    
40146        if (nItems > 0) {
40147        
40148            i = nItems - 1;
40149    
40150            do {
40151    
40152                aItems[i].cfg.setProperty(_DISABLED, bDisabled);
40153            
40154            }
40155            while (i--);
40156        
40157        }
40158
40159
40160        if (bDisabled) {
40161
40162            this.clearActiveItem(true);
40163
40164            Dom.addClass(this.element, _DISABLED);
40165
40166            this.itemAddedEvent.subscribe(this._onItemAdded);
40167
40168        }
40169        else {
40170
40171            Dom.removeClass(this.element, _DISABLED);
40172
40173            this.itemAddedEvent.unsubscribe(this._onItemAdded);
40174
40175        }
40176        
40177    }
40178
40179},
40180
40181/**
40182 * Resizes the shadow to match the container bounding element
40183 * 
40184 * @method _sizeShadow
40185 * @protected
40186 */
40187_sizeShadow : function () {
40188
40189    var oElement = this.element,
40190        oShadow = this._shadow;
40191
40192    if (oShadow && oElement) {
40193        // Clear the previous width
40194        if (oShadow.style.width && oShadow.style.height) {
40195            oShadow.style.width = _EMPTY_STRING;
40196            oShadow.style.height = _EMPTY_STRING;
40197        }
40198
40199        oShadow.style.width = (oElement.offsetWidth + 6) + _PX;
40200        oShadow.style.height = (oElement.offsetHeight + 1) + _PX;
40201    }
40202},
40203
40204/**
40205 * Replaces the shadow element in the DOM with the current shadow element (this._shadow)
40206 * 
40207 * @method _replaceShadow
40208 * @protected 
40209 */
40210_replaceShadow : function () {
40211    this.element.appendChild(this._shadow);
40212},
40213
40214/**
40215 * Adds the classname marker for a visible shadow, to the shadow element
40216 * 
40217 * @method _addShadowVisibleClass
40218 * @protected
40219 */
40220_addShadowVisibleClass : function () {
40221    Dom.addClass(this._shadow, _YUI_MENU_SHADOW_VISIBLE);
40222},
40223
40224/**
40225 * Removes the classname marker for a visible shadow, from the shadow element
40226 * 
40227 * @method _removeShadowVisibleClass
40228 * @protected
40229 */
40230_removeShadowVisibleClass : function () {
40231    Dom.removeClass(this._shadow, _YUI_MENU_SHADOW_VISIBLE);
40232},
40233
40234/**
40235 * Removes the shadow element from the DOM, and unsubscribes all the listeners used to keep it in sync. Used
40236 * to handle setting the shadow to false.
40237 * 
40238 * @method _removeShadow
40239 * @protected
40240 */
40241_removeShadow : function() {
40242
40243    var p = (this._shadow && this._shadow.parentNode);
40244
40245    if (p) {
40246        p.removeChild(this._shadow);
40247    }
40248
40249    this.beforeShowEvent.unsubscribe(this._addShadowVisibleClass);
40250    this.beforeHideEvent.unsubscribe(this._removeShadowVisibleClass);
40251
40252    this.cfg.unsubscribeFromConfigEvent(_WIDTH, this._sizeShadow);
40253    this.cfg.unsubscribeFromConfigEvent(_HEIGHT, this._sizeShadow);
40254    this.cfg.unsubscribeFromConfigEvent(_MAX_HEIGHT, this._sizeShadow);
40255    this.cfg.unsubscribeFromConfigEvent(_MAX_HEIGHT, this._replaceShadow);
40256
40257    this.changeContentEvent.unsubscribe(this._sizeShadow);
40258
40259    Module.textResizeEvent.unsubscribe(this._sizeShadow);
40260},
40261
40262/**
40263 * Used to create the shadow element, add it to the DOM, and subscribe listeners to keep it in sync.
40264 *
40265 * @method _createShadow
40266 * @protected
40267 */
40268_createShadow : function () {
40269
40270    var oShadow = this._shadow,
40271        oElement;
40272
40273    if (!oShadow) {
40274        oElement = this.element;
40275
40276        if (!m_oShadowTemplate) {
40277            m_oShadowTemplate = document.createElement(_DIV_LOWERCASE);
40278            m_oShadowTemplate.className = _YUI_MENU_SHADOW_YUI_MENU_SHADOW_VISIBLE;
40279        }
40280
40281        oShadow = m_oShadowTemplate.cloneNode(false);
40282
40283        oElement.appendChild(oShadow);
40284        
40285        this._shadow = oShadow;
40286
40287        this.beforeShowEvent.subscribe(this._addShadowVisibleClass);
40288        this.beforeHideEvent.subscribe(this._removeShadowVisibleClass);
40289
40290        if (UA.ie) {
40291            /*
40292                 Need to call sizeShadow & syncIframe via setTimeout for 
40293                 IE 7 Quirks Mode and IE 6 Standards Mode and Quirks Mode 
40294                 or the shadow and iframe shim will not be sized and 
40295                 positioned properly.
40296            */
40297            Lang.later(0, this, function () {
40298                this._sizeShadow(); 
40299                this.syncIframe();
40300            });
40301
40302            this.cfg.subscribeToConfigEvent(_WIDTH, this._sizeShadow);
40303            this.cfg.subscribeToConfigEvent(_HEIGHT, this._sizeShadow);
40304            this.cfg.subscribeToConfigEvent(_MAX_HEIGHT, this._sizeShadow);
40305            this.changeContentEvent.subscribe(this._sizeShadow);
40306
40307            Module.textResizeEvent.subscribe(this._sizeShadow, this, true);
40308
40309            this.destroyEvent.subscribe(function () {
40310                Module.textResizeEvent.unsubscribe(this._sizeShadow, this);
40311            });
40312        }
40313
40314        this.cfg.subscribeToConfigEvent(_MAX_HEIGHT, this._replaceShadow);
40315    }
40316},
40317
40318/**
40319 * The beforeShow event handler used to set up the shadow lazily when the menu is made visible.
40320 * @method _shadowBeforeShow
40321 * @protected 
40322 */
40323_shadowBeforeShow : function () {
40324    if (this._shadow) {
40325
40326        // If called because the "shadow" event was refired - just append again and resize
40327        this._replaceShadow();
40328
40329        if (UA.ie) {
40330            this._sizeShadow();
40331        }
40332    } else {
40333        this._createShadow();
40334    }
40335
40336    this.beforeShowEvent.unsubscribe(this._shadowBeforeShow);
40337},
40338
40339/**
40340* @method configShadow
40341* @description Event handler for when the "shadow" configuration property of 
40342* a menu changes.
40343* @param {String} p_sType The name of the event that was fired.
40344* @param {Array} p_aArgs Collection of arguments sent when the event was fired.
40345* @param {YAHOO.widget.Menu} p_oMenu The Menu instance fired the event.
40346*/
40347configShadow: function (p_sType, p_aArgs, p_oMenu) {
40348
40349    var bShadow = p_aArgs[0];
40350
40351    if (bShadow && this.cfg.getProperty(_POSITION) == _DYNAMIC) {
40352        if (this.cfg.getProperty(_VISIBLE)) {
40353            if (this._shadow) {
40354                // If the "shadow" event was refired - just append again and resize
40355                this._replaceShadow();
40356                
40357                if (UA.ie) {
40358                    this._sizeShadow();
40359                }
40360            } else {
40361                this._createShadow();
40362            }
40363        } else {
40364            this.beforeShowEvent.subscribe(this._shadowBeforeShow);
40365        }
40366    } else if (!bShadow) {
40367        this.beforeShowEvent.unsubscribe(this._shadowBeforeShow);
40368        this._removeShadow();
40369    }
40370},
40371
40372// Public methods
40373
40374/**
40375* @method initEvents
40376* @description Initializes the custom events for the menu.
40377*/
40378initEvents: function () {
40379
40380    Menu.superclass.initEvents.call(this);
40381
40382    // Create custom events
40383
40384    var i = EVENT_TYPES.length - 1,
40385        aEventData,
40386        oCustomEvent;
40387
40388
40389    do {
40390
40391        aEventData = EVENT_TYPES[i];
40392
40393        oCustomEvent = this.createEvent(aEventData[1]);
40394        oCustomEvent.signature = CustomEvent.LIST;
40395        
40396        this[aEventData[0]] = oCustomEvent;
40397
40398    }
40399    while (i--);
40400
40401},
40402
40403
40404/**
40405* @method positionOffScreen
40406* @description Positions the menu outside of the boundaries of the browser's 
40407* viewport.  Called automatically when a menu is hidden to ensure that 
40408* it doesn't force the browser to render uncessary scrollbars.
40409*/
40410positionOffScreen: function () {
40411
40412    var oIFrame = this.iframe,
40413        oElement = this.element,
40414        sPos = this.OFF_SCREEN_POSITION;
40415    
40416    oElement.style.top = _EMPTY_STRING;
40417    oElement.style.left = _EMPTY_STRING;
40418    
40419    if (oIFrame) {
40420
40421        oIFrame.style.top = sPos;
40422        oIFrame.style.left = sPos;
40423    
40424    }
40425
40426},
40427
40428
40429/**
40430* @method getRoot
40431* @description Finds the menu's root menu.
40432*/
40433getRoot: function () {
40434
40435    var oItem = this.parent,
40436        oParentMenu,
40437        returnVal;
40438
40439    if (oItem) {
40440
40441        oParentMenu = oItem.parent;
40442
40443        returnVal = oParentMenu ? oParentMenu.getRoot() : this;
40444
40445    }
40446    else {
40447    
40448        returnVal = this;
40449    
40450    }
40451    
40452    return returnVal;
40453
40454},
40455
40456
40457/**
40458* @method toString
40459* @description Returns a string representing the menu.
40460* @return {String}
40461*/
40462toString: function () {
40463
40464    var sReturnVal = _MENU,
40465        sId = this.id;
40466
40467    if (sId) {
40468
40469        sReturnVal += (_SPACE + sId);
40470    
40471    }
40472
40473    return sReturnVal;
40474
40475},
40476
40477
40478/**
40479* @method setItemGroupTitle
40480* @description Sets the title of a group of menu items.
40481* @param {HTML} p_sGroupTitle String or markup specifying the title of the group. The title is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
40482* @param {Number} p_nGroupIndex Optional. Number specifying the group to which
40483* the title belongs.
40484*/
40485setItemGroupTitle: function (p_sGroupTitle, p_nGroupIndex) {
40486
40487    var nGroupIndex,
40488        oTitle,
40489        i,
40490        nFirstIndex;
40491        
40492    if (Lang.isString(p_sGroupTitle) && p_sGroupTitle.length > 0) {
40493
40494        nGroupIndex = Lang.isNumber(p_nGroupIndex) ? p_nGroupIndex : 0;
40495        oTitle = this._aGroupTitleElements[nGroupIndex];
40496
40497
40498        if (oTitle) {
40499
40500            oTitle.innerHTML = p_sGroupTitle;
40501            
40502        }
40503        else {
40504
40505            oTitle = document.createElement(this.GROUP_TITLE_TAG_NAME);
40506                    
40507            oTitle.innerHTML = p_sGroupTitle;
40508
40509            this._aGroupTitleElements[nGroupIndex] = oTitle;
40510
40511        }
40512
40513
40514        i = this._aGroupTitleElements.length - 1;
40515
40516        do {
40517
40518            if (this._aGroupTitleElements[i]) {
40519
40520                Dom.removeClass(this._aGroupTitleElements[i], _FIRST_OF_TYPE);
40521
40522                nFirstIndex = i;
40523
40524            }
40525
40526        }
40527        while (i--);
40528
40529
40530        if (nFirstIndex !== null) {
40531
40532            Dom.addClass(this._aGroupTitleElements[nFirstIndex], 
40533                _FIRST_OF_TYPE);
40534
40535        }
40536
40537        this.changeContentEvent.fire();
40538
40539    }
40540
40541},
40542
40543
40544
40545/**
40546* @method addItem
40547* @description Appends an item to the menu.
40548* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem 
40549* instance to be added to the menu.
40550* @param {HTML} p_oItem String or markup specifying content of the item to be added 
40551* to the menu. The item text is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
40552* @param {Object} p_oItem Object literal containing a set of menu item 
40553* configuration properties.
40554* @param {Number} p_nGroupIndex Optional. Number indicating the group to
40555* which the item belongs.
40556* @return {YAHOO.widget.MenuItem}
40557*/
40558addItem: function (p_oItem, p_nGroupIndex) {
40559
40560    return this._addItemToGroup(p_nGroupIndex, p_oItem);
40561
40562},
40563
40564
40565/**
40566* @method addItems
40567* @description Adds an array of items to the menu.
40568* @param {Array} p_aItems Array of items to be added to the menu.  The array 
40569* can contain strings specifying the markup for the content of each item to be created, object
40570* literals specifying each of the menu item configuration properties, 
40571* or MenuItem instances. The item content if provided as a string is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
40572* @param {Number} p_nGroupIndex Optional. Number specifying the group to 
40573* which the items belongs.
40574* @return {Array}
40575*/
40576addItems: function (p_aItems, p_nGroupIndex) {
40577
40578    var nItems,
40579        aItems,
40580        oItem,
40581        i,
40582        returnVal;
40583
40584
40585    if (Lang.isArray(p_aItems)) {
40586
40587        nItems = p_aItems.length;
40588        aItems = [];
40589
40590        for(i=0; i<nItems; i++) {
40591
40592            oItem = p_aItems[i];
40593
40594            if (oItem) {
40595
40596                if (Lang.isArray(oItem)) {
40597    
40598                    aItems[aItems.length] = this.addItems(oItem, i);
40599    
40600                }
40601                else {
40602    
40603                    aItems[aItems.length] = this._addItemToGroup(p_nGroupIndex, oItem);
40604                
40605                }
40606
40607            }
40608    
40609        }
40610
40611
40612        if (aItems.length) {
40613        
40614            returnVal = aItems;
40615        
40616        }
40617
40618    }
40619
40620    return returnVal;
40621
40622},
40623
40624
40625/**
40626* @method insertItem
40627* @description Inserts an item into the menu at the specified index.
40628* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem 
40629* instance to be added to the menu.
40630* @param {String} p_oItem String specifying the text of the item to be added 
40631* to the menu.
40632* @param {Object} p_oItem Object literal containing a set of menu item 
40633* configuration properties.
40634* @param {Number} p_nItemIndex Number indicating the ordinal position at which
40635* the item should be added.
40636* @param {Number} p_nGroupIndex Optional. Number indicating the group to which 
40637* the item belongs.
40638* @return {YAHOO.widget.MenuItem}
40639*/
40640insertItem: function (p_oItem, p_nItemIndex, p_nGroupIndex) {
40641    
40642    return this._addItemToGroup(p_nGroupIndex, p_oItem, p_nItemIndex);
40643
40644},
40645
40646
40647/**
40648* @method removeItem
40649* @description Removes the specified item from the menu.
40650* @param {YAHOO.widget.MenuItem} p_oObject Object reference for the MenuItem 
40651* instance to be removed from the menu.
40652* @param {Number} p_oObject Number specifying the index of the item 
40653* to be removed.
40654* @param {Number} p_nGroupIndex Optional. Number specifying the group to 
40655* which the item belongs.
40656* @return {YAHOO.widget.MenuItem}
40657*/
40658removeItem: function (p_oObject, p_nGroupIndex) {
40659
40660    var oItem,
40661        returnVal;
40662    
40663    if (!Lang.isUndefined(p_oObject)) {
40664
40665        if (p_oObject instanceof YAHOO.widget.MenuItem) {
40666
40667            oItem = this._removeItemFromGroupByValue(p_nGroupIndex, p_oObject);           
40668
40669        }
40670        else if (Lang.isNumber(p_oObject)) {
40671
40672            oItem = this._removeItemFromGroupByIndex(p_nGroupIndex, p_oObject);
40673
40674        }
40675
40676        if (oItem) {
40677
40678            oItem.destroy();
40679
40680            YAHOO.log("Item removed." + 
40681                " Text: " + oItem.cfg.getProperty("text") + ", " + 
40682                " Index: " + oItem.index + ", " + 
40683                " Group Index: " + oItem.groupIndex, "info", this.toString());
40684
40685            returnVal = oItem;
40686
40687        }
40688
40689    }
40690
40691    return returnVal;
40692
40693},
40694
40695
40696/**
40697* @method getItems
40698* @description Returns an array of all of the items in the menu.
40699* @return {Array}
40700*/
40701getItems: function () {
40702
40703    var aGroups = this._aItemGroups,
40704        nGroups,
40705        returnVal,
40706        aItems = [];
40707
40708
40709    if (Lang.isArray(aGroups)) {
40710
40711        nGroups = aGroups.length;
40712
40713        returnVal = ((nGroups == 1) ? aGroups[0] : (Array.prototype.concat.apply(aItems, aGroups)));
40714
40715    }
40716
40717    return returnVal;
40718
40719},
40720
40721
40722/**
40723* @method getItemGroups
40724* @description Multi-dimensional Array representing the menu items as they 
40725* are grouped in the menu.
40726* @return {Array}
40727*/        
40728getItemGroups: function () {
40729
40730    return this._aItemGroups;
40731
40732},
40733
40734
40735/**
40736* @method getItem
40737* @description Returns the item at the specified index.
40738* @param {Number} p_nItemIndex Number indicating the ordinal position of the 
40739* item to be retrieved.
40740* @param {Number} p_nGroupIndex Optional. Number indicating the group to which 
40741* the item belongs.
40742* @return {YAHOO.widget.MenuItem}
40743*/
40744getItem: function (p_nItemIndex, p_nGroupIndex) {
40745    
40746    var aGroup,
40747        returnVal;
40748    
40749    if (Lang.isNumber(p_nItemIndex)) {
40750
40751        aGroup = this._getItemGroup(p_nGroupIndex);
40752
40753        if (aGroup) {
40754
40755            returnVal = aGroup[p_nItemIndex];
40756        
40757        }
40758
40759    }
40760    
40761    return returnVal;
40762    
40763},
40764
40765
40766/**
40767* @method getSubmenus
40768* @description Returns an array of all of the submenus that are immediate 
40769* children of the menu.
40770* @return {Array}
40771*/
40772getSubmenus: function () {
40773
40774    var aItems = this.getItems(),
40775        nItems = aItems.length,
40776        aSubmenus,
40777        oSubmenu,
40778        oItem,
40779        i;
40780
40781
40782    if (nItems > 0) {
40783        
40784        aSubmenus = [];
40785
40786        for(i=0; i<nItems; i++) {
40787
40788            oItem = aItems[i];
40789            
40790            if (oItem) {
40791
40792                oSubmenu = oItem.cfg.getProperty(_SUBMENU);
40793                
40794                if (oSubmenu) {
40795
40796                    aSubmenus[aSubmenus.length] = oSubmenu;
40797
40798                }
40799            
40800            }
40801        
40802        }
40803    
40804    }
40805
40806    return aSubmenus;
40807
40808},
40809
40810
40811/**
40812* @method clearContent
40813* @description Removes all of the content from the menu, including the menu 
40814* items, group titles, header and footer.
40815*/
40816clearContent: function () {
40817
40818    var aItems = this.getItems(),
40819        nItems = aItems.length,
40820        oElement = this.element,
40821        oBody = this.body,
40822        oHeader = this.header,
40823        oFooter = this.footer,
40824        oItem,
40825        oSubmenu,
40826        i;
40827
40828
40829    if (nItems > 0) {
40830
40831        i = nItems - 1;
40832
40833        do {
40834
40835            oItem = aItems[i];
40836
40837            if (oItem) {
40838
40839                oSubmenu = oItem.cfg.getProperty(_SUBMENU);
40840
40841                if (oSubmenu) {
40842
40843                    this.cfg.configChangedEvent.unsubscribe(
40844                        this._onParentMenuConfigChange, oSubmenu);
40845
40846                    this.renderEvent.unsubscribe(this._onParentMenuRender, 
40847                        oSubmenu);
40848
40849                }
40850                
40851                this.removeItem(oItem, oItem.groupIndex);
40852
40853            }
40854        
40855        }
40856        while (i--);
40857
40858    }
40859
40860
40861    if (oHeader) {
40862
40863        Event.purgeElement(oHeader);
40864        oElement.removeChild(oHeader);
40865
40866    }
40867    
40868
40869    if (oFooter) {
40870
40871        Event.purgeElement(oFooter);
40872        oElement.removeChild(oFooter);
40873    }
40874
40875
40876    if (oBody) {
40877
40878        Event.purgeElement(oBody);
40879
40880        oBody.innerHTML = _EMPTY_STRING;
40881
40882    }
40883
40884    this.activeItem = null;
40885
40886    this._aItemGroups = [];
40887    this._aListElements = [];
40888    this._aGroupTitleElements = [];
40889
40890    this.cfg.setProperty(_WIDTH, null);
40891
40892},
40893
40894
40895/**
40896* @method destroy
40897* @description Removes the menu's <code>&#60;div&#62;</code> element 
40898* (and accompanying child nodes) from the document.
40899* @param {boolean} shallowPurge If true, only the parent element's DOM event listeners are purged. If false, or not provided, all children are also purged of DOM event listeners. 
40900* NOTE: The flag is a "shallowPurge" flag, as opposed to what may be a more intuitive "purgeChildren" flag to maintain backwards compatibility with behavior prior to 2.9.0.
40901* 
40902*/
40903destroy: function (shallowPurge) {
40904
40905    // Remove all items
40906
40907    this.clearContent();
40908
40909    this._aItemGroups = null;
40910    this._aListElements = null;
40911    this._aGroupTitleElements = null;
40912
40913
40914    // Continue with the superclass implementation of this method
40915
40916    Menu.superclass.destroy.call(this, shallowPurge);
40917    
40918    YAHOO.log("Destroyed.", "info", this.toString());
40919
40920},
40921
40922
40923/**
40924* @method setInitialFocus
40925* @description Sets focus to the menu's first enabled item.
40926*/
40927setInitialFocus: function () {
40928
40929    var oItem = this._getFirstEnabledItem();
40930    
40931    if (oItem) {
40932
40933        oItem.focus();
40934
40935    }
40936    
40937},
40938
40939
40940/**
40941* @method setInitialSelection
40942* @description Sets the "selected" configuration property of the menu's first 
40943* enabled item to "true."
40944*/
40945setInitialSelection: function () {
40946
40947    var oItem = this._getFirstEnabledItem();
40948    
40949    if (oItem) {
40950    
40951        oItem.cfg.setProperty(_SELECTED, true);
40952    }        
40953
40954},
40955
40956
40957/**
40958* @method clearActiveItem
40959* @description Sets the "selected" configuration property of the menu's active
40960* item to "false" and hides the item's submenu.
40961* @param {Boolean} p_bBlur Boolean indicating if the menu's active item 
40962* should be blurred.  
40963*/
40964clearActiveItem: function (p_bBlur) {
40965
40966    if (this.cfg.getProperty(_SHOW_DELAY) > 0) {
40967    
40968        this._cancelShowDelay();
40969    
40970    }
40971
40972
40973    var oActiveItem = this.activeItem,
40974        oConfig,
40975        oSubmenu;
40976
40977    if (oActiveItem) {
40978
40979        oConfig = oActiveItem.cfg;
40980
40981        if (p_bBlur) {
40982
40983            oActiveItem.blur();
40984            
40985            this.getRoot()._hasFocus = true;
40986        
40987        }
40988
40989        oConfig.setProperty(_SELECTED, false);
40990
40991        oSubmenu = oConfig.getProperty(_SUBMENU);
40992
40993
40994        if (oSubmenu) {
40995
40996            oSubmenu.hide();
40997
40998        }
40999
41000        this.activeItem = null;  
41001
41002    }
41003
41004},
41005
41006
41007/**
41008* @method focus
41009* @description Causes the menu to receive focus and fires the "focus" event.
41010*/
41011focus: function () {
41012
41013    if (!this.hasFocus()) {
41014
41015        this.setInitialFocus();
41016    
41017    }
41018
41019},
41020
41021
41022/**
41023* @method blur
41024* @description Causes the menu to lose focus and fires the "blur" event.
41025*/    
41026blur: function () {
41027
41028    var oItem;
41029
41030    if (this.hasFocus()) {
41031    
41032        oItem = MenuManager.getFocusedMenuItem();
41033        
41034        if (oItem) {
41035
41036            oItem.blur();
41037
41038        }
41039
41040    }
41041
41042},
41043
41044
41045/**
41046* @method hasFocus
41047* @description Returns a boolean indicating whether or not the menu has focus.
41048* @return {Boolean}
41049*/
41050hasFocus: function () {
41051
41052    return (MenuManager.getFocusedMenu() == this.getRoot());
41053
41054},
41055
41056
41057_doItemSubmenuSubscribe: function (p_sType, p_aArgs, p_oObject) {
41058
41059    var oItem = p_aArgs[0],
41060        oSubmenu = oItem.cfg.getProperty(_SUBMENU);
41061
41062    if (oSubmenu) {
41063        oSubmenu.subscribe.apply(oSubmenu, p_oObject);
41064    }
41065
41066},
41067
41068
41069_doSubmenuSubscribe: function (p_sType, p_aArgs, p_oObject) { 
41070
41071    var oSubmenu = this.cfg.getProperty(_SUBMENU);
41072    
41073    if (oSubmenu) {
41074        oSubmenu.subscribe.apply(oSubmenu, p_oObject);
41075    }
41076
41077},
41078
41079
41080/**
41081* Adds the specified CustomEvent subscriber to the menu and each of 
41082* its submenus.
41083* @method subscribe
41084* @param p_type     {string}   the type, or name of the event
41085* @param p_fn       {function} the function to exectute when the event fires
41086* @param p_obj      {Object}   An object to be passed along when the event 
41087*                              fires
41088* @param p_override {boolean}  If true, the obj passed in becomes the 
41089*                              execution scope of the listener
41090*/
41091subscribe: function () {
41092
41093    //	Subscribe to the event for this Menu instance
41094    Menu.superclass.subscribe.apply(this, arguments);
41095
41096    //	Subscribe to the "itemAdded" event so that all future submenus
41097    //	also subscribe to this event
41098    Menu.superclass.subscribe.call(this, _ITEM_ADDED, this._doItemSubmenuSubscribe, arguments);
41099
41100
41101    var aItems = this.getItems(),
41102        nItems,
41103        oItem,
41104        oSubmenu,
41105        i;
41106        
41107
41108    if (aItems) {
41109
41110        nItems = aItems.length;
41111        
41112        if (nItems > 0) {
41113        
41114            i = nItems - 1;
41115            
41116            do {
41117
41118                oItem = aItems[i];
41119                oSubmenu = oItem.cfg.getProperty(_SUBMENU);
41120                
41121                if (oSubmenu) {
41122                    oSubmenu.subscribe.apply(oSubmenu, arguments);
41123                }
41124                else {
41125                    oItem.cfg.subscribeToConfigEvent(_SUBMENU, this._doSubmenuSubscribe, arguments);
41126                }
41127
41128            }
41129            while (i--);
41130        
41131        }
41132
41133    }
41134
41135},
41136
41137
41138unsubscribe: function () {
41139
41140    //	Remove the event for this Menu instance
41141    Menu.superclass.unsubscribe.apply(this, arguments);
41142
41143    //	Remove the "itemAdded" event so that all future submenus don't have 
41144    //	the event handler
41145    Menu.superclass.unsubscribe.call(this, _ITEM_ADDED, this._doItemSubmenuSubscribe, arguments);
41146
41147
41148    var aItems = this.getItems(),
41149        nItems,
41150        oItem,
41151        oSubmenu,
41152        i;
41153        
41154
41155    if (aItems) {
41156
41157        nItems = aItems.length;
41158        
41159        if (nItems > 0) {
41160        
41161            i = nItems - 1;
41162            
41163            do {
41164
41165                oItem = aItems[i];
41166                oSubmenu = oItem.cfg.getProperty(_SUBMENU);
41167                
41168                if (oSubmenu) {
41169                    oSubmenu.unsubscribe.apply(oSubmenu, arguments);
41170                }
41171                else {
41172                    oItem.cfg.unsubscribeFromConfigEvent(_SUBMENU, this._doSubmenuSubscribe, arguments);
41173                }
41174
41175            }
41176            while (i--);
41177        
41178        }
41179
41180    }
41181
41182},
41183
41184
41185/**
41186* @description Initializes the class's configurable properties which can be
41187* changed using the menu's Config object ("cfg").
41188* @method initDefaultConfig
41189*/
41190initDefaultConfig: function () {
41191
41192    Menu.superclass.initDefaultConfig.call(this);
41193
41194    var oConfig = this.cfg;
41195
41196
41197    // Module documentation overrides
41198
41199    /**
41200    * @config effect
41201    * @description Object or array of objects representing the ContainerEffect 
41202    * classes that are active for animating the container.  When set this 
41203    * property is automatically applied to all submenus.
41204    * @type Object
41205    * @default null
41206    */
41207
41208    // Overlay documentation overrides
41209
41210
41211    /**
41212    * @config x
41213    * @description Number representing the absolute x-coordinate position of 
41214    * the Menu.  This property is only applied when the "position" 
41215    * configuration property is set to dynamic.
41216    * @type Number
41217    * @default null
41218    */
41219    
41220
41221    /**
41222    * @config y
41223    * @description Number representing the absolute y-coordinate position of 
41224    * the Menu.  This property is only applied when the "position" 
41225    * configuration property is set to dynamic.
41226    * @type Number
41227    * @default null
41228    */
41229
41230
41231    /**
41232    * @description Array of the absolute x and y positions of the Menu.  This 
41233    * property is only applied when the "position" configuration property is 
41234    * set to dynamic.
41235    * @config xy
41236    * @type Number[]
41237    * @default null
41238    */
41239    
41240
41241    /**
41242    * @config context
41243    * @description Array of context arguments for context-sensitive positioning.  
41244    * The format is: [id or element, element corner, context corner]. 
41245    * For example, setting this property to ["img1", "tl", "bl"] would 
41246    * align the Menu's top left corner to the context element's 
41247    * bottom left corner.  This property is only applied when the "position" 
41248    * configuration property is set to dynamic.
41249    * @type Array
41250    * @default null
41251    */
41252    
41253    
41254    /**
41255    * @config fixedcenter
41256    * @description Boolean indicating if the Menu should be anchored to the 
41257    * center of the viewport.  This property is only applied when the 
41258    * "position" configuration property is set to dynamic.
41259    * @type Boolean
41260    * @default false
41261    */
41262    
41263    
41264    /**
41265    * @config iframe
41266    * @description Boolean indicating whether or not the Menu should 
41267    * have an IFRAME shim; used to prevent SELECT elements from 
41268    * poking through an Overlay instance in IE6.  When set to "true", 
41269    * the iframe shim is created when the Menu instance is intially
41270    * made visible.  This property is only applied when the "position" 
41271    * configuration property is set to dynamic and is automatically applied 
41272    * to all submenus.
41273    * @type Boolean
41274    * @default true for IE6 and below, false for all other browsers.
41275    */
41276
41277
41278    // Add configuration attributes
41279
41280    /*
41281        Change the default value for the "visible" configuration 
41282        property to "false" by re-adding the property.
41283    */
41284
41285    /**
41286    * @config visible
41287    * @description Boolean indicating whether or not the menu is visible.  If 
41288    * the menu's "position" configuration property is set to "dynamic" (the 
41289    * default), this property toggles the menu's <code>&#60;div&#62;</code> 
41290    * element's "visibility" style property between "visible" (true) or 
41291    * "hidden" (false).  If the menu's "position" configuration property is 
41292    * set to "static" this property toggles the menu's 
41293    * <code>&#60;div&#62;</code> element's "display" style property 
41294    * between "block" (true) or "none" (false).
41295    * @default false
41296    * @type Boolean
41297    */
41298    oConfig.addProperty(
41299        VISIBLE_CONFIG.key, 
41300        {
41301            handler: this.configVisible, 
41302            value: VISIBLE_CONFIG.value, 
41303            validator: VISIBLE_CONFIG.validator
41304        }
41305     );
41306
41307
41308    /*
41309        Change the default value for the "constraintoviewport" configuration 
41310        property (inherited by YAHOO.widget.Overlay) to "true" by re-adding the property.
41311    */
41312
41313    /**
41314    * @config constraintoviewport
41315    * @description Boolean indicating if the menu will try to remain inside 
41316    * the boundaries of the size of viewport.  This property is only applied 
41317    * when the "position" configuration property is set to dynamic and is 
41318    * automatically applied to all submenus.
41319    * @default true
41320    * @type Boolean
41321    */
41322    oConfig.addProperty(
41323        CONSTRAIN_TO_VIEWPORT_CONFIG.key, 
41324        {
41325            handler: this.configConstrainToViewport, 
41326            value: CONSTRAIN_TO_VIEWPORT_CONFIG.value, 
41327            validator: CONSTRAIN_TO_VIEWPORT_CONFIG.validator, 
41328            supercedes: CONSTRAIN_TO_VIEWPORT_CONFIG.supercedes 
41329        } 
41330    );
41331
41332
41333    /*
41334        Change the default value for the "preventcontextoverlap" c
41334onfiguration 
41335        property (inherited by YAHOO.widget.Overlay) to "true" by re-adding the property.
41336    */
41337
41338    /**
41339    * @config preventcontextoverlap
41340    * @description Boolean indicating whether or not a submenu should overlap its parent MenuItem 
41341    * when the "constraintoviewport" configuration property is set to "true".
41342    * @type Boolean
41343    * @default true
41344    */
41345    oConfig.addProperty(PREVENT_CONTEXT_OVERLAP_CONFIG.key, {
41346
41347        value: PREVENT_CONTEXT_OVERLAP_CONFIG.value, 
41348        validator: PREVENT_CONTEXT_OVERLAP_CONFIG.validator, 
41349        supercedes: PREVENT_CONTEXT_OVERLAP_CONFIG.supercedes
41350
41351    });
41352
41353
41354    /**
41355    * @config position
41356    * @description String indicating how a menu should be positioned on the 
41357    * screen.  Possible values are "static" and "dynamic."  Static menus are 
41358    * visible by default and reside in the normal flow of the document 
41359    * (CSS position: static).  Dynamic menus are hidden by default, reside 
41360    * out of the normal flow of the document (CSS position: absolute), and 
41361    * can overlay other elements on the screen.
41362    * @default dynamic
41363    * @type String
41364    */
41365    oConfig.addProperty(
41366        POSITION_CONFIG.key, 
41367        {
41368            handler: this.configPosition,
41369            value: POSITION_CONFIG.value, 
41370            validator: POSITION_CONFIG.validator,
41371            supercedes: POSITION_CONFIG.supercedes
41372        }
41373    );
41374
41375
41376    /**
41377    * @config submenualignment
41378    * @description Array defining how submenus should be aligned to their 
41379    * parent menu item. The format is: [itemCorner, submenuCorner]. By default
41380    * a submenu's top left corner is aligned to its parent menu item's top 
41381    * right corner.
41382    * @default ["tl","tr"]
41383    * @type Array
41384    */
41385    oConfig.addProperty(
41386        SUBMENU_ALIGNMENT_CONFIG.key, 
41387        { 
41388            value: SUBMENU_ALIGNMENT_CONFIG.value,
41389            suppressEvent: SUBMENU_ALIGNMENT_CONFIG.suppressEvent
41390        }
41391    );
41392
41393
41394    /**
41395    * @config autosubmenudisplay
41396    * @description Boolean indicating if submenus are automatically made 
41397    * visible when the user mouses over the menu's items.
41398    * @default true
41399    * @type Boolean
41400    */
41401    oConfig.addProperty(
41402       AUTO_SUBMENU_DISPLAY_CONFIG.key, 
41403       { 
41404           value: AUTO_SUBMENU_DISPLAY_CONFIG.value, 
41405           validator: AUTO_SUBMENU_DISPLAY_CONFIG.validator,
41406           suppressEvent: AUTO_SUBMENU_DISPLAY_CONFIG.suppressEvent
41407       } 
41408    );
41409
41410
41411    /**
41412    * @config showdelay
41413    * @description Number indicating the time (in milliseconds) that should 
41414    * expire before a submenu is made visible when the user mouses over 
41415    * the menu's items.  This property is only applied when the "position" 
41416    * configuration property is set to dynamic and is automatically applied 
41417    * to all submenus.
41418    * @default 250
41419    * @type Number
41420    */
41421    oConfig.addProperty(
41422       SHOW_DELAY_CONFIG.key, 
41423       { 
41424           value: SHOW_DELAY_CONFIG.value, 
41425           validator: SHOW_DELAY_CONFIG.validator,
41426           suppressEvent: SHOW_DELAY_CONFIG.suppressEvent
41427       } 
41428    );
41429
41430
41431    /**
41432    * @config hidedelay
41433    * @description Number indicating the time (in milliseconds) that should 
41434    * expire before the menu is hidden.  This property is only applied when 
41435    * the "position" configuration property is set to dynamic and is 
41436    * automatically applied to all submenus.
41437    * @default 0
41438    * @type Number
41439    */
41440    oConfig.addProperty(
41441       HIDE_DELAY_CONFIG.key, 
41442       { 
41443           handler: this.configHideDelay,
41444           value: HIDE_DELAY_CONFIG.value, 
41445           validator: HIDE_DELAY_CONFIG.validator, 
41446           suppressEvent: HIDE_DELAY_CONFIG.suppressEvent
41447       } 
41448    );
41449
41450
41451    /**
41452    * @config submenuhidedelay
41453    * @description Number indicating the time (in milliseconds) that should 
41454    * expire before a submenu is hidden when the user mouses out of a menu item 
41455    * heading in the direction of a submenu.  The value must be greater than or 
41456    * equal to the value specified for the "showdelay" configuration property.
41457    * This property is only applied when the "position" configuration property 
41458    * is set to dynamic and is automatically applied to all submenus.
41459    * @default 250
41460    * @type Number
41461    */
41462    oConfig.addProperty(
41463       SUBMENU_HIDE_DELAY_CONFIG.key, 
41464       { 
41465           value: SUBMENU_HIDE_DELAY_CONFIG.value, 
41466           validator: SUBMENU_HIDE_DELAY_CONFIG.validator,
41467           suppressEvent: SUBMENU_HIDE_DELAY_CONFIG.suppressEvent
41468       } 
41469    );
41470
41471
41472    /**
41473    * @config clicktohide
41474    * @description Boolean indicating if the menu will automatically be 
41475    * hidden if the user clicks outside of it.  This property is only 
41476    * applied when the "position" configuration property is set to dynamic 
41477    * and is automatically applied to all submenus.
41478    * @default true
41479    * @type Boolean
41480    */
41481    oConfig.addProperty(
41482        CLICK_TO_HIDE_CONFIG.key,
41483        {
41484            value: CLICK_TO_HIDE_CONFIG.value,
41485            validator: CLICK_TO_HIDE_CONFIG.validator,
41486            suppressEvent: CLICK_TO_HIDE_CONFIG.suppressEvent
41487        }
41488    );
41489
41490
41491    /**
41492    * @config container
41493    * @description HTML element reference or string specifying the id 
41494    * attribute of the HTML element that the menu's markup should be 
41495    * rendered into.
41496    * @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
41497    * level-one-html.html#ID-58190037">HTMLElement</a>|String
41498    * @default document.body
41499    */
41500    oConfig.addProperty(
41501       CONTAINER_CONFIG.key, 
41502       { 
41503           handler: this.configContainer,
41504           value: document.body,
41505           suppressEvent: CONTAINER_CONFIG.suppressEvent
41506       } 
41507   );
41508
41509
41510    /**
41511    * @config scrollincrement
41512    * @description Number used to control the scroll speed of a menu.  Used to 
41513    * increment the "scrollTop" property of the menu's body by when a menu's 
41514    * content is scrolling.  When set this property is automatically applied 
41515    * to all submenus.
41516    * @default 1
41517    * @type Number
41518    */
41519    oConfig.addProperty(
41520        SCROLL_INCREMENT_CONFIG.key, 
41521        { 
41522            value: SCROLL_INCREMENT_CONFIG.value, 
41523            validator: SCROLL_INCREMENT_CONFIG.validator,
41524            supercedes: SCROLL_INCREMENT_CONFIG.supercedes,
41525            suppressEvent: SCROLL_INCREMENT_CONFIG.suppressEvent
41526        }
41527    );
41528
41529
41530    /**
41531    * @config minscrollheight
41532    * @description Number defining the minimum threshold for the "maxheight" 
41533    * configuration property.  When set this property is automatically applied 
41534    * to all submenus.
41535    * @default 90
41536    * @type Number
41537    */
41538    oConfig.addProperty(
41539        MIN_SCROLL_HEIGHT_CONFIG.key, 
41540        { 
41541            value: MIN_SCROLL_HEIGHT_CONFIG.value, 
41542            validator: MIN_SCROLL_HEIGHT_CONFIG.validator,
41543            supercedes: MIN_SCROLL_HEIGHT_CONFIG.supercedes,
41544            suppressEvent: MIN_SCROLL_HEIGHT_CONFIG.suppressEvent
41545        }
41546    );
41547
41548
41549    /**
41550    * @config maxheight
41551    * @description Number defining the maximum height (in pixels) for a menu's 
41552    * body element (<code>&#60;div class="bd"&#62;</code>).  Once a menu's body 
41553    * exceeds this height, the contents of the body are scrolled to maintain 
41554    * this value.  This value cannot be set lower than the value of the 
41555    * "minscrollheight" configuration property.
41556    * @default 0
41557    * @type Number
41558    */
41559    oConfig.addProperty(
41560       MAX_HEIGHT_CONFIG.key, 
41561       {
41562            handler: this.configMaxHeight,
41563            value: MAX_HEIGHT_CONFIG.value,
41564            validator: MAX_HEIGHT_CONFIG.validator,
41565            suppressEvent: MAX_HEIGHT_CONFIG.suppressEvent,
41566            supercedes: MAX_HEIGHT_CONFIG.supercedes            
41567       } 
41568    );
41569
41570
41571    /**
41572    * @config classname
41573    * @description String representing the CSS class to be applied to the 
41574    * menu's root <code>&#60;div&#62;</code> element.  The specified class(es)  
41575    * are appended in addition to the default class as specified by the menu's
41576    * CSS_CLASS_NAME constant. When set this property is automatically 
41577    * applied to all submenus.
41578    * @default null
41579    * @type String
41580    */
41581    oConfig.addProperty(
41582        CLASS_NAME_CONFIG.key, 
41583        { 
41584            handler: this.configClassName,
41585            value: CLASS_NAME_CONFIG.value, 
41586            validator: CLASS_NAME_CONFIG.validator,
41587            supercedes: CLASS_NAME_CONFIG.supercedes      
41588        }
41589    );
41590
41591
41592    /**
41593    * @config disabled
41594    * @description Boolean indicating if the menu should be disabled.  
41595    * Disabling a menu disables each of its items.  (Disabled menu items are 
41596    * dimmed and will not respond to user input or fire events.)  Disabled
41597    * menus have a corresponding "disabled" CSS class applied to their root
41598    * <code>&#60;div&#62;</code> element.
41599    * @default false
41600    * @type Boolean
41601    */
41602    oConfig.addProperty(
41603        DISABLED_CONFIG.key, 
41604        { 
41605            handler: this.configDisabled,
41606            value: DISABLED_CONFIG.value, 
41607            validator: DISABLED_CONFIG.validator,
41608            suppressEvent: DISABLED_CONFIG.suppressEvent
41609        }
41610    );
41611
41612
41613    /**
41614    * @config shadow
41615    * @description Boolean indicating if the menu should have a shadow.
41616    * @default true
41617    * @type Boolean
41618    */
41619    oConfig.addProperty(
41620        SHADOW_CONFIG.key, 
41621        { 
41622            handler: this.configShadow,
41623            value: SHADOW_CONFIG.value, 
41624            validator: SHADOW_CONFIG.validator
41625        }
41626    );
41627
41628
41629    /**
41630    * @config keepopen
41631    * @description Boolean indicating if the menu should remain open when clicked.
41632    * @default false
41633    * @type Boolean
41634    */
41635    oConfig.addProperty(
41636        KEEP_OPEN_CONFIG.key, 
41637        { 
41638            value: KEEP_OPEN_CONFIG.value, 
41639            validator: KEEP_OPEN_CONFIG.validator
41640        }
41641    );
41642
41643}
41644
41645}); // END YAHOO.lang.extend
41646
41647})();
41648
41649
41650
41651(function () {
41652
41653/**
41654* Creates an item for a menu.
41655* 
41656* @param {HTML} p_oObject Markup for the menu item content. The markup is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
41657* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
41658* one-html.html#ID-74680021">HTMLLIElement</a>} p_oObject Object specifying 
41659* the <code>&#60;li&#62;</code> element of the menu item.
41660* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
41661* one-html.html#ID-38450247">HTMLOptGroupElement</a>} p_oObject Object 
41662* specifying the <code>&#60;optgroup&#62;</code> element of the menu item.
41663* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
41664* one-html.html#ID-70901257">HTMLOptionElement</a>} p_oObject Object 
41665* specifying the <code>&#60;option&#62;</code> element of the menu item.
41666* @param {Object} p_oConfig Optional. Object literal specifying the 
41667* configuration for the menu item. See configuration class documentation 
41668* for more details.
41669* @class MenuItem
41670* @constructor
41671*/
41672YAHOO.widget.MenuItem = function (p_oObject, p_oConfig) {
41673
41674    if (p_oObject) {
41675
41676        if (p_oConfig) {
41677    
41678            this.parent = p_oConfig.parent;
41679            this.value = p_oConfig.value;
41680            this.id = p_oConfig.id;
41681
41682        }
41683
41684        this.init(p_oObject, p_oConfig);
41685
41686    }
41687
41688};
41689
41690
41691var Dom = YAHOO.util.Dom,
41692    Module = YAHOO.widget.Module,
41693    Menu = YAHOO.widget.Menu,
41694    MenuItem = YAHOO.widget.MenuItem,
41695    CustomEvent = YAHOO.util.CustomEvent,
41696    UA = YAHOO.env.ua,
41697    Lang = YAHOO.lang,
41698
41699    // Private string constants
41700
41701    _TEXT = "text",
41702    _HASH = "#",
41703    _HYPHEN = "-",
41704    _HELP_TEXT = "helptext",
41705    _URL = "url",
41706    _TARGET = "target",
41707    _EMPHASIS = "emphasis",
41708    _STRONG_EMPHASIS = "strongemphasis",
41709    _CHECKED = "checked",
41710    _SUBMENU = "submenu",
41711    _DISABLED = "disabled",
41712    _SELECTED = "selected",
41713    _HAS_SUBMENU = "hassubmenu",
41714    _CHECKED_DISABLED = "checked-disabled",
41715    _HAS_SUBMENU_DISABLED = "hassubmenu-disabled",
41716    _HAS_SUBMENU_SELECTED = "hassubmenu-selected",
41717    _CHECKED_SELECTED = "checked-selected",
41718    _ONCLICK = "onclick",
41719    _CLASSNAME = "classname",
41720    _EMPTY_STRING = "",
41721    _OPTION = "OPTION",
41722    _OPTGROUP = "OPTGROUP",
41723    _LI_UPPERCASE = "LI",
41724    _HREF = "href",
41725    _SELECT = "SELECT",
41726    _DIV = "DIV",
41727    _START_HELP_TEXT = "<em class=\"helptext\">",
41728    _START_EM = "<em>",
41729    _END_EM = "</em>",
41730    _START_STRONG = "<strong>",
41731    _END_STRONG = "</strong>",
41732    _PREVENT_CONTEXT_OVERLAP = "preventcontextoverlap",
41733    _OBJ = "obj",
41734    _SCOPE = "scope",
41735    _NONE = "none",
41736    _VISIBLE = "visible",
41737    _SPACE = " ",
41738    _MENUITEM = "MenuItem",
41739    _CLICK = "click",
41740    _SHOW = "show",
41741    _HIDE = "hide",
41742    _LI_LOWERCASE = "li",
41743    _ANCHOR_TEMPLATE = "<a href=\"#\"></a>",
41744
41745    EVENT_TYPES = [
41746    
41747        ["mouseOverEvent", "mouseover"],
41748        ["mouseOutEvent", "mouseout"],
41749        ["mouseDownEvent", "mousedown"],
41750        ["mouseUpEvent", "mouseup"],
41751        ["clickEvent", _CLICK],
41752        ["keyPressEvent", "keypress"],
41753        ["keyDownEvent", "keydown"],
41754        ["keyUpEvent", "keyup"],
41755        ["focusEvent", "focus"],
41756        ["blurEvent", "blur"],
41757        ["destroyEvent", "destroy"]
41758    
41759    ],
41760
41761    TEXT_CONFIG = { 
41762        key: _TEXT, 
41763        value: _EMPTY_STRING, 
41764        validator: Lang.isString, 
41765        suppressEvent: true 
41766    }, 
41767
41768    HELP_TEXT_CONFIG = { 
41769        key: _HELP_TEXT,
41770        supercedes: [_TEXT], 
41771        suppressEvent: true 
41772    },
41773
41774    URL_CONFIG = { 
41775        key: _URL, 
41776        value: _HASH, 
41777        suppressEvent: true 
41778    }, 
41779
41780    TARGET_CONFIG = { 
41781        key: _TARGET, 
41782        suppressEvent: true 
41783    }, 
41784
41785    EMPHASIS_CONFIG = { 
41786        key: _EMPHASIS, 
41787        value: false, 
41788        validator: Lang.isBoolean, 
41789        suppressEvent: true, 
41790        supercedes: [_TEXT]
41791    }, 
41792
41793    STRONG_EMPHASIS_CONFIG = { 
41794        key: _STRONG_EMPHASIS, 
41795        value: false, 
41796        validator: Lang.isBoolean, 
41797        suppressEvent: true,
41798        supercedes: [_TEXT]
41799    },
41800
41801    CHECKED_CONFIG = { 
41802        key: _CHECKED, 
41803        value: false, 
41804        validator: Lang.isBoolean, 
41805        suppressEvent: true, 
41806        supercedes: [_DISABLED, _SELECTED]
41807    }, 
41808
41809    SUBMENU_CONFIG = { 
41810        key: _SUBMENU,
41811        suppressEvent: true,
41812        supercedes: [_DISABLED, _SELECTED]
41813    },
41814
41815    DISABLED_CONFIG = { 
41816        key: _DISABLED, 
41817        value: false, 
41818        validator: Lang.isBoolean, 
41819        suppressEvent: true,
41820        supercedes: [_TEXT, _SELECTED]
41821    },
41822
41823    SELECTED_CONFIG = { 
41824        key: _SELECTED, 
41825        value: false, 
41826        validator: Lang.isBoolean, 
41827        suppressEvent: true
41828    },
41829
41830    ONCLICK_CONFIG = { 
41831        key: _ONCLICK,
41832        suppressEvent: true
41833    },
41834
41835    CLASS_NAME_CONFIG = { 
41836        key: _CLASSNAME, 
41837        value: null, 
41838        validator: Lang.isString,
41839        suppressEvent: true
41840    },
41841    
41842    KEY_LISTENER_CONFIG = {
41843        key: "keylistener", 
41844        value: null, 
41845        suppressEvent: true
41846    },
41847
41848    m_oMenuItemTemplate = null,
41849
41850    CLASS_NAMES = {};
41851
41852
41853/**
41854* @method getClassNameForState
41855* @description Returns a class name for the specified prefix and state.  If the class name does not 
41856* yet exist, it is created and stored in the CLASS_NAMES object to increase performance.
41857* @private
41858* @param {String} prefix String representing the prefix for the class name
41859* @param {String} state String representing a state - "disabled," "checked," etc.
41860*/  
41861var getClassNameForState = function (prefix, state) {
41862
41863    var oClassNames = CLASS_NAMES[prefix];
41864    
41865    if (!oClassNames) {
41866        CLASS_NAMES[prefix] = {};
41867        oClassNames = CLASS_NAMES[prefix];
41868    }
41869
41870
41871    var sClassName = oClassNames[state];
41872
41873    if (!sClassName) {
41874        sClassName = prefix + _HYPHEN + state;
41875        oClassNames[state] = sClassName;
41876    }
41877
41878    return sClassName;
41879    
41880};
41881
41882
41883/**
41884* @method addClassNameForState
41885* @description Applies a class name to a MenuItem instance's &#60;LI&#62; and &#60;A&#62; elements
41886* that represents a MenuItem's state - "disabled," "checked," etc.
41887* @private
41888* @param {String} state String representing a state - "disabled," "checked," etc.
41889*/  
41890var addClassNameForState = function (state) {
41891
41892    Dom.addClass(this.element, getClassNameForState(this.CSS_CLASS_NAME, state));
41893    Dom.addClass(this._oAnchor, getClassNameForState(this.CSS_LABEL_CLASS_NAME, state));
41894
41895};
41896
41897/**
41898* @method removeClassNameForState
41899* @description Removes a class name from a MenuItem instance's &#60;LI&#62; and &#60;A&#62; elements
41900* that represents a MenuItem's state - "disabled," "checked," etc.
41901* @private
41902* @param {String} state String representing a state - "disabled," "checked," etc.
41903*/  
41904var removeClassNameForState = function (state) {
41905
41906    Dom.removeClass(this.element, getClassNameForState(this.CSS_CLASS_NAME, state));
41907    Dom.removeClass(this._oAnchor, getClassNameForState(this.CSS_LABEL_CLASS_NAME, state));
41908
41909};
41910
41911
41912MenuItem.prototype = {
41913
41914    /**
41915    * @property CSS_CLASS_NAME
41916    * @description String representing the CSS class(es) to be applied to the 
41917    * <code>&#60;li&#62;</code> element of the menu item.
41918    * @default "yuimenuitem"
41919    * @final
41920    * @type String
41921    */
41922    CSS_CLASS_NAME: "yuimenuitem",
41923
41924
41925    /**
41926    * @property CSS_LABEL_CLASS_NAME
41927    * @description String representing the CSS class(es) to be applied to the 
41928    * menu item's <code>&#60;a&#62;</code> element.
41929    * @default "yuimenuitemlabel"
41930    * @final
41931    * @type String
41932    */
41933    CSS_LABEL_CLASS_NAME: "yuimenuitemlabel",
41934
41935
41936    /**
41937    * @property SUBMENU_TYPE
41938    * @description Object representing the type of menu to instantiate and 
41939    * add when parsing the child nodes of the menu item's source HTML element.
41940    * @final
41941    * @type YAHOO.widget.Menu
41942    */
41943    SUBMENU_TYPE: null,
41944
41945
41946
41947    // Private member variables
41948    
41949
41950    /**
41951    * @property _oAnchor
41952    * @description Object reference to the menu item's 
41953    * <code>&#60;a&#62;</code> element.
41954    * @default null 
41955    * @private
41956    * @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
41957    * one-html.html#ID-48250443">HTMLAnchorElement</a>
41958    */
41959    _oAnchor: null,
41960    
41961    
41962    /**
41963    * @property _oHelpTextEM
41964    * @description Object reference to the menu item's help text 
41965    * <code>&#60;em&#62;</code> element.
41966    * @default null
41967    * @private
41968    * @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
41969    * one-html.html#ID-58190037">HTMLElement</a>
41970    */
41971    _oHelpTextEM: null,
41972    
41973    
41974    /**
41975    * @property _oSubmenu
41976    * @description Object reference to the menu item's submenu.
41977    * @default null
41978    * @private
41979    * @type YAHOO.widget.Menu
41980    */
41981    _oSubmenu: null,
41982
41983
41984    /** 
41985    * @property _oOnclickAttributeValue
41986    * @description Object reference to the menu item's current value for the 
41987    * "onclick" configuration attribute.
41988    * @default null
41989    * @private
41990    * @type Object
41991    */
41992    _oOnclickAttributeValue: null,
41993
41994
41995    /**
41996    * @property _sClassName
41997    * @description The current value of the "classname" configuration attribute.
41998    * @default null
41999    * @private
42000    * @type String
42001    */
42002    _sClassName: null,
42003
42004
42005
42006    // Public properties
42007
42008
42009    /**
42010    * @property constructor
42011    * @description Object reference to the menu item's constructor function.
42012    * @default YAHOO.widget.MenuItem
42013    * @type YAHOO.widget.MenuItem
42014    */
42015    constructor: MenuItem,
42016
42017
42018    /**
42019    * @property index
42020    * @description Number indicating the ordinal position of the menu item in 
42021    * its group.
42022    * @default null
42023    * @type Number
42024    */
42025    index: null,
42026
42027
42028    /**
42029    * @property groupIndex
42030    * @description Number indicating the index of the group to which the menu 
42031    * item belongs.
42032    * @default null
42033    * @type Number
42034    */
42035    groupIndex: null,
42036
42037
42038    /**
42039    * @property parent
42040    * @description Object reference to the menu item's parent menu.
42041    * @default null
42042    * @type YAHOO.widget.Menu
42043    */
42044    parent: null,
42045
42046
42047    /**
42048    * @property element
42049    * @description Object reference to the menu item's 
42050    * <code>&#60;li&#62;</code> element.
42051    * @default <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level
42052    * -one-html.html#ID-74680021">HTMLLIElement</a>
42053    * @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
42054    * one-html.html#ID-74680021">HTMLLIElement</a>
42055    */
42056    element: null,
42057
42058
42059    /**
42060    * @property srcElement
42061    * @description Object reference to the HTML element (either 
42062    * <code>&#60;li&#62;</code>, <code>&#60;optgroup&#62;</code> or 
42063    * <code>&#60;option&#62;</code>) used create the menu item.
42064    * @default <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
42065    * level-one-html.html#ID-74680021">HTMLLIElement</a>|<a href="http://www.
42066    * w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-html.html#ID-38450247"
42067    * >HTMLOptGroupElement</a>|<a href="http://www.w3.org/TR/2000/WD-DOM-
42068    * Level-1-20000929/level-one-html.html#ID-70901257">HTMLOptionElement</a>
42069    * @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
42070    * one-html.html#ID-74680021">HTMLLIElement</a>|<a href="http://www.w3.
42071    * org/TR/2000/WD-DOM-Level-1-20000929/level-one-html.html#ID-38450247">
42072    * HTMLOptGroupElement</a>|<a href="http://www.w3.org/TR/2000/WD-DOM-
42073    * Level-1-20000929/level-one-html.html#ID-70901257">HTMLOptionElement</a>
42074    */
42075    srcElement: null,
42076
42077
42078    /**
42079    * @property value
42080    * @description Object reference to the menu item's value.
42081    * @default null
42082    * @type Object
42083    */
42084    value: null,
42085
42086
42087    /**
42088    * @property browser
42089    * @deprecated Use YAHOO.env.ua
42090    * @description String representing the browser.
42091    * @type String
42092    */
42093    browser: Module.prototype.browser,
42094
42095
42096    /**
42097    * @property id
42098    * @description Id of the menu item's root <code>&#60;li&#62;</code> 
42099    * element.  This property should be set via the constructor using the 
42100    * configuration object literal.  If an id is not specified, then one will 
42101    * be created using the "generateId" method of the Dom utility.
42102    * @default null
42103    * @type String
42104    */
42105    id: null,
42106
42107
42108
42109    // Events
42110
42111
42112    /**
42113    * @event destroyEvent
42114    * @description Fires when the menu item's <code>&#60;li&#62;</code> 
42115    * element is removed from its parent <code>&#60;ul&#62;</code> element.
42116    * @type YAHOO.util.CustomEvent
42117    */
42118
42119
42120    /**
42121    * @event mouseOverEvent
42122    * @description Fires when the mouse has entered the menu item.  Passes 
42123    * back the DOM Event object as an argument.
42124    * @type YAHOO.util.CustomEvent
42125    */
42126
42127
42128    /**
42129    * @event mouseOutEvent
42130    * @description Fires when the mouse has left the menu item.  Passes back 
42131    * the DOM Event object as an argument.
42132    * @type YAHOO.util.CustomEvent
42133    */
42134
42135
42136    /**
42137    * @event mouseDownEvent
42138    * @description Fires when the user mouses down on the menu item.  Passes 
42139    * back the DOM Event object as an argument.
42140    * @type YAHOO.util.CustomEvent
42141    */
42142
42143
42144    /**
42145    * @event mouseUpEvent
42146    * @description Fires when the user releases a mouse button while the mouse 
42147    * is over the menu item.  Passes back the DOM Event object as an argument.
42148    * @type YAHOO.util.CustomEvent
42149    */
42150
42151
42152    /**
42153    * @event clickEvent
42154    * @description Fires when the user clicks the on the menu item.  Passes 
42155    * back the DOM Event object as an argument.
42156    * @type YAHOO.util.CustomEvent
42157    */
42158
42159
42160    /**
42161    * @event keyPressEvent
42162    * @description Fires when the user presses an alphanumeric key when the 
42163    * menu item has focus.  Passes back the DOM Event object as an argument.
42164    * @type YAHOO.util.CustomEvent
42165    */
42166
42167
42168    /**
42169    * @event keyDownEvent
42170    * @description Fires when the user presses a key when the menu item has 
42171    * focus.  Passes back the DOM Event object as an argument.
42172    * @type YAHOO.util.CustomEvent
42173    */
42174
42175
42176    /**
42177    * @event keyUpEvent
42178    * @description Fires when the user releases a key when the menu item has 
42179    * focus.  Passes back the DOM Event object as an argument.
42180    * @type YAHOO.util.CustomEvent
42181    */
42182
42183
42184    /**
42185    * @event focusEvent
42186    * @description Fires when the menu item receives focus.
42187    * @type YAHOO.util.CustomEvent
42188    */
42189
42190
42191    /**
42192    * @event blurEvent
42193    * @description Fires when the menu item loses the input focus.
42194    * @type YAHOO.util.CustomEvent
42195    */
42196
42197
42198    /**
42199    * @method init
42200    * @description The MenuItem class's initialization method. This method is 
42201    * automatically called by the constructor, and sets up all DOM references 
42202    * for pre-existing markup, and creates required markup if it is not 
42203    * already present.
42204    * @param {HTML} p_oObject Markup for the menu item content. The markup is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
42205    * @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
42206    * one-html.html#ID-74680021">HTMLLIElement</a>} p_oObject Object specifying 
42207    * the <code>&#60;li&#62;</code> element of the menu item.
42208    * @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
42209    * one-html.html#ID-38450247">HTMLOptGroupElement</a>} p_oObject Object 
42210    * specifying the <code>&#60;optgroup&#62;</code> element of the menu item.
42211    * @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
42212    * one-html.html#ID-70901257">HTMLOptionElement</a>} p_oObject Object 
42213    * specifying the <code>&#60;option&#62;</code> element of the menu item.
42214    * @param {Object} p_oConfig Optional. Object literal specifying the 
42215    * configuration for the menu item. See configuration class documentation 
42216    * for more details.
42217    */
42218    init: function (p_oObject, p_oConfig) {
42219
42220
42221        if (!this.SUBMENU_TYPE) {
42222    
42223            this.SUBMENU_TYPE = Menu;
42224    
42225        }
42226
42227
42228        // Create the config object
42229
42230        this.cfg = new YAHOO.util.Config(this);
42231
42232        this.initDefaultConfig();
42233
42234        var oConfig = this.cfg,
42235            sURL = _HASH,
42236            oCustomEvent,
42237            aEventData,
42238            oAnchor,
42239            sTarget,
42240            sText,
42241            sId,
42242            i;
42243
42244
42245        if (Lang.isString(p_oObject)) {
42246
42247            this._createRootNodeStructure();
42248
42249            oConfig.queueProperty(_TEXT, p_oObject);
42250
42251        }
42252        else if (p_oObject && p_oObject.tagName) {
42253
42254            switch(p_oObject.tagName.toUpperCase()) {
42255
42256                case _OPTION:
42257
42258                    this._createRootNodeStructure();
42259
42260                    oConfig.queueProperty(_TEXT, p_oObject.text);
42261                    oConfig.queueProperty(_DISABLED, p_oObject.disabled);
42262
42263                    this.value = p_oObject.value;
42264
42265                    this.srcElement = p_oObject;
42266
42267                break;
42268
42269                case _OPTGROUP:
42270
42271                    this._createRootNodeStructure();
42272
42273                    oConfig.queueProperty(_TEXT, p_oObject.label);
42274                    oConfig.queueProperty(_DISABLED, p_oObject.disabled);
42275
42276                    this.srcElement = p_oObject;
42277
42278                    this._initSubTree();
42279
42280                break;
42281
42282                case _LI_UPPERCASE:
42283
42284                    // Get the anchor node (if it exists)
42285                    
42286                    oAnchor = Dom.getFirstChild(p_oObject);
42287
42288
42289                    // Capture the "text" and/or the "URL"
42290
42291                    if (oAnchor) {
42292
42293                        sURL = oAnchor.getAttribute(_HREF, 2);
42294                        sTarget = oAnchor.getAttribute(_TARGET);
42295
42296                        sText = oAnchor.innerHTML;
42297
42298                    }
42299
42300                    this.srcElement = p_oObject;
42301                    this.element = p_oObject;
42302                    this._oAnchor = oAnchor;
42303
42304                    /*
42305                        Set these properties silently to sync up the 
42306                        configuration object without making changes to the 
42307                        element's DOM
42308                    */ 
42309
42310                    oConfig.setProperty(_TEXT, sText, true);
42311                    oConfig.setProperty(_URL, sURL, true);
42312                    oConfig.setProperty(_TARGET, sTarget, true);
42313
42314                    this._initSubTree();
42315
42316                break;
42317
42318            }            
42319
42320        }
42321
42322
42323        if (this.element) {
42324
42325            sId = (this.srcElement || this.element).id;
42326
42327            if (!sId) {
42328
42329                sId = this.id || Dom.generateId();
42330
42331                this.element.id = sId;
42332
42333            }
42334
42335            this.id = sId;
42336
42337
42338            Dom.addClass(this.element, this.CSS_CLASS_NAME);
42339            Dom.addClass(this._oAnchor, this.CSS_LABEL_CLASS_NAME);
42340
42341
42342            i = EVENT_TYPES.length - 1;
42343
42344            do {
42345
42346                aEventData = EVENT_TYPES[i];
42347
42348                oCustomEvent = this.createEvent(aEventData[1]);
42349                oCustomEvent.signature = CustomEvent.LIST;
42350                
42351                this[aEventData[0]] = oCustomEvent;
42352
42353            }
42354            while (i--);
42355
42356
42357            if (p_oConfig) {
42358    
42359                oConfig.applyConfig(p_oConfig);
42360    
42361            }        
42362
42363            oConfig.fireQueue();
42364
42365        }
42366
42367    },
42368
42369
42370
42371    // Private methods
42372
42373    /**
42374    * @method _createRootNodeStructure
42375    * @description Creates the core DOM structure for the menu item.
42376    * @private
42377    */
42378    _createRootNodeStructure: function () {
42379
42380        var oElement,
42381            oAnchor;
42382
42383        if (!m_oMenuItemTemplate) {
42384
42385            m_oMenuItemTemplate = document.createElement(_LI_LOWERCASE);
42386            m_oMenuItemTemplate.innerHTML = _ANCHOR_TEMPLATE;
42387
42388        }
42389
42390        oElement = m_oMenuItemTemplate.cloneNode(true);
42391        oElement.className = this.CSS_CLASS_NAME;
42392
42393        oAnchor = oElement.firstChild;
42394        oAnchor.className = this.CSS_LABEL_CLASS_NAME;
42395
42396        this.element = oElement;
42397        this._oAnchor = oAnchor;
42398
42399    },
42400
42401
42402    /**
42403    * @method _initSubTree
42404    * @description Iterates the source element's childNodes collection and uses 
42405    * the child nodes to instantiate other menus.
42406    * @private
42407    */
42408    _initSubTree: function () {
42409
42410        var oSrcEl = this.srcElement,
42411            oConfig = this.cfg,
42412            oNode,
42413            aOptions,
42414            nOptions,
42415            oMenu,
42416            n;
42417
42418
42419        if (oSrcEl.childNodes.length > 0) {
42420
42421            if (this.parent.lazyLoad && this.parent.srcElement && 
42422                this.parent.srcElement.tagName.toUpperCase() == _SELECT) {
42423
42424                oConfig.setProperty(
42425                        _SUBMENU, 
42426                        { id: Dom.generateId(), itemdata: oSrcEl.childNodes }
42427                    );
42428
42429            }
42430            else {
42431
42432                oNode = oSrcEl.firstChild;
42433                aOptions = [];
42434    
42435                do {
42436    
42437                    if (oNode && oNode.tagName) {
42438    
42439                        switch(oNode.tagName.toUpperCase()) {
42440                
42441                            case _DIV:
42442                
42443                                oConfig.setProperty(_SUBMENU, oNode);
42444                
42445                            break;
42446         
42447                            case _OPTION:
42448        
42449                                aOptions[aOptions.length] = oNode;
42450        
42451                            break;
42452               
42453                        }
42454                    
42455                    }
42456                
42457                }        
42458                while((oNode = oNode.nextSibling));
42459    
42460    
42461                nOptions = aOptions.length;
42462    
42463                if (nOptions > 0) {
42464    
42465                    oMenu = new this.SUBMENU_TYPE(Dom.generateId());
42466                    
42467                    oConfig.setProperty(_SUBMENU, oMenu);
42468    
42469                    for(n=0; n<nOptions; n++) {
42470        
42471                        oMenu.addItem((new oMenu.ITEM_TYPE(aOptions[n])));
42472        
42473                    }
42474        
42475                }
42476            
42477            }
42478
42479        }
42480
42481    },
42482
42483
42484
42485    // Event handlers for configuration properties
42486
42487
42488    /**
42489    * @method configText
42490    * @description Event handler for when the "text" configuration property of 
42491    * the menu item changes.
42492    * @param {String} p_sType String representing the name of the event that 
42493    * was fired.
42494    * @param {Array} p_aArgs Array of arguments sent when the event was fired.
42495    * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item
42496    * that fired the event.
42497    */
42498    configText: function (p_sType, p_aArgs, p_oItem) {
42499
42500        var sText = p_aArgs[0],
42501            oConfig = this.cfg,
42502            oAnchor = this._oAnchor,
42503            sHelpText = oConfig.getProperty(_HELP_TEXT),
42504            sHelpTextHTML = _EMPTY_STRING,
42505            sEmphasisStartTag = _EMPTY_STRING,
42506            sEmphasisEndTag = _EMPTY_STRING;
42507
42508
42509        if (sText) {
42510
42511
42512            if (sHelpText) {
42513                    
42514                sHelpTextHTML = _START_HELP_TEXT + sHelpText + _END_EM;
42515            
42516            }
42517
42518
42519            if (oConfig.getProperty(_EMPHASIS)) {
42520
42521                sEmphasisStartTag = _START_EM;
42522                sEmphasisEndTag = _END_EM;
42523
42524            }
42525
42526
42527            if (oConfig.getProperty(_STRONG_EMPHASIS)) {
42528
42529                sEmphasisStartTag = _START_STRONG;
42530                sEmphasisEndTag = _END_STRONG;
42531            
42532            }
42533
42534
42535            oAnchor.innerHTML = (sEmphasisStartTag + sText + sEmphasisEndTag + sHelpTextHTML);
42536
42537        }
42538
42539    },
42540
42541
42542    /**
42543    * @method configHelpText
42544    * @description Event handler for when the "helptext" configuration property 
42545    * of the menu item changes.
42546    * @param {String} p_sType String representing the name of the event that 
42547    * was fired.
42548    * @param {Array} p_aArgs Array of arguments sent when the event was fired.
42549    * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item
42550    * that fired the event.
42551    */    
42552    configHelpText: function (p_sType, p_aArgs, p_oItem) {
42553
42554        this.cfg.refireEvent(_TEXT);
42555
42556    },
42557
42558
42559    /**
42560    * @method configURL
42561    * @description Event handler for when the "url" configuration property of 
42562    * the menu item changes.
42563    * @param {String} p_sType String representing the name of the event that 
42564    * was fired.
42565    * @param {Array} p_aArgs Array of arguments sent when the event was fired.
42566    * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item
42567    * that fired the event.
42568    */    
42569    configURL: function (p_sType, p_aArgs, p_oItem) {
42570
42571        var sURL = p_aArgs[0];
42572
42573        if (!sURL) {
42574
42575            sURL = _HASH;
42576
42577        }
42578
42579        var oAnchor = this._oAnchor;
42580
42581        if (UA.opera) {
42582
42583            oAnchor.removeAttribute(_HREF);
42584        
42585        }
42586
42587        oAnchor.setAttribute(_HREF, sURL);
42588
42589    },
42590
42591
42592    /**
42593    * @method configTarget
42594    * @description Event handler for when the "target" configuration property 
42595    * of the menu item changes.  
42596    * @param {String} p_sType String representing the name of the event that 
42597    * was fired.
42598    * @param {Array} p_aArgs Array of arguments sent when the event was fired.
42599    * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item
42600    * that fired the event.
42601    */    
42602    configTarget: function (p_sType, p_aArgs, p_oItem) {
42603
42604        var sTarget = p_aArgs[0],
42605            oAnchor = this._oAnchor;
42606
42607        if (sTarget && sTarget.length > 0) {
42608
42609            oAnchor.setAttribute(_TARGET, sTarget);
42610
42611        }
42612        else {
42613
42614            oAnchor.removeAttribute(_TARGET);
42615        
42616        }
42617
42618    },
42619
42620
42621    /**
42622    * @method configEmphasis
42623    * @description Event handler for when the "emphasis" configuration property
42624    * of the menu item changes.
42625    * @param {String} p_sType String representing the name of the event that 
42626    * was fired.
42627    * @param {Array} p_aArgs Array of arguments sent when the event was fired.
42628    * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item
42629    * that fired the event.
42630    */    
42631    configEmphasis: function (p_sType, p_aArgs, p_oItem) {
42632
42633        var bEmphasis = p_aArgs[0],
42634            oConfig = this.cfg;
42635
42636
42637        if (bEmphasis && oConfig.getProperty(_STRONG_EMPHASIS)) {
42638
42639            oConfig.setProperty(_STRONG_EMPHASIS, false);
42640
42641        }
42642
42643
42644        oConfig.refireEvent(_TEXT);
42645
42646    },
42647
42648
42649    /**
42650    * @method configStrongEmphasis
42651    * @description Event handler for when the "strongemphasis" configuration 
42652    * property of the menu item changes.
42653    * @param {String} p_sType String representing the name of the event that 
42654    * was fired.
42655    * @param {Array} p_aArgs Array of arguments sent when the event was fired.
42656    * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item
42657    * that fired the event.
42658    */    
42659    configStrongEmphasis: function (p_sType, p_aArgs, p_oItem) {
42660
42661        var bStrongEmphasis = p_aArgs[0],
42662            oConfig = this.cfg;
42663
42664
42665        if (bStrongEmphasis && oConfig.getProperty(_EMPHASIS)) {
42666
42667            oConfig.setProperty(_EMPHASIS, false);
42668
42669        }
42670
42671        oConfig.refireEvent(_TEXT);
42672
42673    },
42674
42675
42676    /**
42677    * @method configChecked
42678    * @description Event handler for when the "checked" configuration property 
42679    * of the menu item changes. 
42680    * @param {String} p_sType String representing the name of the event that 
42681    * was fired.
42682    * @param {Array} p_aArgs Array of arguments sent when the event was fired.
42683    * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item
42684    * that fired the event.
42685    */    
42686    configChecked: function (p_sType, p_aArgs, p_oItem) {
42687
42688        var bChecked = p_aArgs[0],
42689            oConfig = this.cfg;
42690
42691
42692        if (bChecked) {
42693
42694            addClassNameForState.call(this, _CHECKED);
42695
42696        }
42697        else {
42698
42699            removeClassNameForState.call(this, _CHECKED);
42700        }
42701
42702
42703        oConfig.refireEvent(_TEXT);
42704
42705
42706        if (oConfig.getProperty(_DISABLED)) {
42707
42708            oConfig.refireEvent(_DISABLED);
42709
42710        }
42711
42712
42713        if (oConfig.getProperty(_SELECTED)) {
42714
42715            oConfig.refireEvent(_SELECTED);
42716
42717        }
42718
42719    },
42720
42721
42722
42723    /**
42724    * @method configDisabled
42725    * @description Event handler for when the "disabled" configuration property 
42726    * of the menu item changes. 
42727    * @param {String} p_sType String representing the name of the event that 
42728    * was fired.
42729    * @param {Array} p_aArgs Array of arguments sent when the event was fired.
42730    * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item
42731    * that fired the event.
42732    */    
42733    configDisabled: function (p_sType, p_aArgs, p_oItem) {
42734
42735        var bDisabled = p_aArgs[0],
42736            oConfig = this.cfg,
42737            oSubmenu = oConfig.getProperty(_SUBMENU),
42738            bChecked = oConfig.getProperty(_CHECKED);
42739
42740
42741        if (bDisabled) {
42742
42743            if (oConfig.getProperty(_SELECTED)) {
42744
42745                oConfig.setProperty(_SELECTED, false);
42746
42747            }
42748
42749
42750            addClassNameForState.call(this, _DISABLED);
42751
42752
42753            if (oSubmenu) {
42754
42755                addClassNameForState.call(this, _HAS_SUBMENU_DISABLED);
42756            
42757            }
42758            
42759
42760            if (bChecked) {
42761
42762                addClassNameForState.call(this, _CHECKED_DISABLED);
42763
42764            }
42765
42766        }
42767        else {
42768
42769            removeClassNameForState.call(this, _DISABLED);
42770
42771
42772            if (oSubmenu) {
42773
42774                removeClassNameForState.call(this, _HAS_SUBMENU_DISABLED);
42775            
42776            }
42777            
42778
42779            if (bChecked) {
42780
42781                removeClassNameForState.call(this, _CHECKED_DISABLED);
42782
42783            }
42784
42785        }
42786
42787    },
42788
42789
42790    /**
42791    * @method configSelected
42792    * @description Event handler for when the "selected" configuration property 
42793    * of the menu item changes. 
42794    * @param {String} p_sType String representing the name of the event that 
42795    * was fired.
42796    * @param {Array} p_aArgs Array of arguments sent when the event was fired.
42797    * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item
42798    * that fired the event.
42799    */    
42800    configSelected: function (p_sType, p_aArgs, p_oItem) {
42801
42802        var oConfig = this.cfg,
42803            oAnchor = this._oAnchor,
42804            
42805            bSelected = p_aArgs[0],
42806            bChecked = oConfig.getProperty(_CHECKED),
42807            oSubmenu = oConfig.getProperty(_SUBMENU);
42808
42809
42810        if (UA.opera) {
42811
42812            oAnchor.blur();
42813        
42814        }
42815
42816
42817        if (bSelected && !oConfig.getProperty(_DISABLED)) {
42818
42819            addClassNameForState.call(this, _SELECTED);
42820
42821
42822            if (oSubmenu) {
42823
42824                addClassNameForState.call(this, _HAS_SUBMENU_SELECTED);
42825            
42826            }
42827
42828
42829            if (bChecked) {
42830
42831                addClassNameForState.call(this, _CHECKED_SELECTED);
42832
42833            }
42834
42835        }
42836        else {
42837
42838            removeClassNameForState.call(this, _SELECTED);
42839
42840
42841            if (oSubmenu) {
42842
42843                removeClassNameForState.call(this, _HAS_SUBMENU_SELECTED);
42844            
42845            }
42846
42847
42848            if (bChecked) {
42849
42850                removeClassNameForState.call(this, _CHECKED_SELECTED);
42851
42852            }
42853
42854        }
42855
42856
42857        if (this.hasFocus() && UA.opera) {
42858        
42859            oAnchor.focus();
42860        
42861        }
42862
42863    },
42864
42865
42866    /**
42867    * @method _onSubmenuBeforeHide
42868    * @description "beforehide" Custom Event handler for a submenu.
42869    * @private
42870    * @param {String} p_sType String representing the name of the event that 
42871    * was fired.
42872    * @param {Array} p_aArgs Array of arguments sent when the event was fired.
42873    */
42874    _onSubmenuBeforeHide: function (p_sType, p_aArgs) {
42875
42876        var oItem = this.parent,
42877            oMenu;
42878
42879        function onHide() {
42880
42881            oItem._oAnchor.blur();
42882            oMenu.beforeHideEvent.unsubscribe(onHide);
42883        
42884        }
42885
42886
42887        if (oItem.hasFocus()) {
42888
42889            oMenu = oItem.parent;
42890
42891            oMenu.beforeHideEvent.subscribe(onHide);
42892        
42893        }
42894    
42895    },
42896
42897
42898    /**
42899    * @method configSubmenu
42900    * @description Event handler for when the "submenu" configuration property 
42901    * of the menu item changes. 
42902    * @param {String} p_sType String representing the name of the event that 
42903    * was fired.
42904    * @param {Array} p_aArgs Array of arguments sent when the event was fired.
42905    * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item
42906    * that fired the event.
42907    */
42908    configSubmenu: function (p_sType, p_aArgs, p_oItem) {
42909
42910        var oSubmenu = p_aArgs[0],
42911            oConfig = this.cfg,
42912            bLazyLoad = this.parent && this.parent.lazyLoad,
42913            oMenu,
42914            sSubmenuId,
42915            oSubmenuConfig;
42916
42917
42918        if (oSubmenu) {
42919
42920            if (oSubmenu instanceof Menu) {
42921
42922                oMenu = oSubmenu;
42923                oMenu.parent = this;
42924                oMenu.lazyLoad = bLazyLoad;
42925
42926            }
42927            else if (Lang.isObject(oSubmenu) && oSubmenu.id && !oSubmenu.nodeType) {
42928
42929                sSubmenuId = oSubmenu.id;
42930                oSubmenuConfig = oSubmenu;
42931
42932                oSubmenuConfig.lazyload = bLazyLoad;
42933                oSubmenuConfig.parent = this;
42934
42935                oMenu = new this.SUBMENU_TYPE(sSubmenuId, oSubmenuConfig);
42936
42937
42938                // Set the value of the property to the Menu instance
42939
42940                oConfig.setProperty(_SUBMENU, oMenu, true);
42941
42942            }
42943            else {
42944
42945                oMenu = new this.SUBMENU_TYPE(oSubmenu, { lazyload: bLazyLoad, parent: this });
42946
42947
42948                // Set the value of the property to the Menu instance
42949                
42950                oConfig.setProperty(_SUBMENU, oMenu, true);
42951
42952            }
42953
42954
42955            if (oMenu) {
42956
42957                oMenu.cfg.setProperty(_PREVENT_CONTEXT_OVERLAP, true);
42958
42959                addClassNameForState.call(this, _HAS_SUBMENU);
42960
42961
42962                if (oConfig.getProperty(_URL) === _HASH) {
42963                
42964                    oConfig.setProperty(_URL, (_HASH + oMenu.id));
42965                
42966                }
42967
42968
42969                this._oSubmenu = oMenu;
42970
42971
42972                if (UA.opera) {
42973                
42974                    oMenu.beforeHideEvent.subscribe(this._onSubmenuBeforeHide);               
42975                
42976                }
42977            
42978            }
42979
42980        }
42981        else {
42982
42983            removeClassNameForState.call(this, _HAS_SUBMENU);
42984
42985            if (this._oSubmenu) {
42986
42987                this._oSubmenu.destroy();
42988
42989            }
42990
42991        }
42992
42993
42994        if (oConfig.getProperty(_DISABLED)) {
42995
42996            oConfig.refireEvent(_DISABLED);
42997
42998        }
42999
43000
43001        if (oConfig.getProperty(_SELECTED)) {
43002
43003            oConfig.refireEvent(_SELECTED);
43004
43005        }
43006
43007    },
43008
43009
43010    /**
43011    * @method configOnClick
43012    * @description Event handler for when the "onclick" configuration property 
43013    * of the menu item changes. 
43014    * @param {String} p_sType String representing the name of the event that 
43015    * was fired.
43016    * @param {Array} p_aArgs Array of arguments sent when the event was fired.
43017    * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item
43018    * that fired the event.
43019    */
43020    configOnClick: function (p_sType, p_aArgs, p_oItem) {
43021
43022        var oObject = p_aArgs[0];
43023
43024        /*
43025            Remove any existing listeners if a "click" event handler has 
43026            already been specified.
43027        */
43028
43029        if (this._oOnclickAttributeValue && (this._oOnclickAttributeValue != oObject)) {
43030
43031            this.clickEvent.unsubscribe(this._oOnclickAttributeValue.fn, 
43032                                this._oOnclickAttributeValue.obj);
43033
43034            this._oOnclickAttributeValue = null;
43035
43036        }
43037
43038
43039        if (!this._oOnclickAttributeValue && Lang.isObject(oObject) && 
43040            Lang.isFunction(oObject.fn)) {
43041            
43042            this.clickEvent.subscribe(oObject.fn, 
43043                ((_OBJ in oObject) ? oObject.obj : this), 
43044                ((_SCOPE in oObject) ? oObject.scope : null) );
43045
43046            this._oOnclickAttributeValue = oObject;
43047
43048        }
43049    
43050    },
43051
43052
43053    /**
43054    * @method configClassName
43055    * @description Event handler for when the "classname" configuration 
43056    * property of a menu item changes.
43057    * @param {String} p_sType String representing the name of the event that 
43058    * was fired.
43059    * @param {Array} p_aArgs Array of arguments sent when the event was fired.
43060    * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item
43061    * that fired the event.
43062    */
43063    configClassName: function (p_sType, p_aArgs, p_oItem) {
43064    
43065        var sClassName = p_aArgs[0];
43066    
43067        if (this._sClassName) {
43068    
43069            Dom.removeClass(this.element, this._sClassName);
43070    
43071        }
43072    
43073        Dom.addClass(this.element, sClassName);
43074        this._sClassName = sClassName;
43075    
43076    },
43077
43078
43079    /**
43080    * @method _dispatchClickEvent
43081    * @description Dispatches a DOM "click" event to the anchor element of a 
43082    * MenuItem instance.
43083    * @private	
43084    */
43085    _dispatchClickEvent: function () {
43086
43087        var oMenuItem = this,
43088            oAnchor;
43089
43090        if (!oMenuItem.cfg.getProperty(_DISABLED)) {
43091            oAnchor = Dom.getFirstChild(oMenuItem.element);
43092
43093            //	Dispatch a "click" event to the MenuItem's anchor so that its
43094            //	"click" event handlers will get called in response to the user 
43095            //	pressing the keyboard shortcut defined by the "keylistener"
43096            //	configuration property.
43097
43098            this._dispatchDOMClick(oAnchor);
43099        }
43100    },
43101
43102    /**
43103     * Utility method to dispatch a DOM click event on the HTMLElement passed in
43104     *
43105     * @method _dispatchDOMClick
43106     * @protected
43107     * @param {HTMLElement} el
43108     */    
43109    _dispatchDOMClick : function(el) {
43110        var oEvent;
43111
43112        // Choose the standards path for IE9
43113        if (UA.ie && UA.ie < 9) {
43114            el.fireEvent(_ONCLICK);
43115        } else {
43116            if ((UA.gecko && UA.gecko >= 1.9) || UA.opera || UA.webkit) {
43117                oEvent = document.createEvent("HTMLEvents");
43118                oEvent.initEvent(_CLICK, true, true);
43119            } else {
43120                oEvent = document.createEvent("MouseEvents");
43121                oEvent.initMouseEvent(_CLICK, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
43122            }
43123            el.dispatchEvent(oEvent);
43124        }
43125    },
43126
43127    /**
43128    * @method _createKeyListener
43129    * @description "show" event handler for a Menu instance - responsible for 
43130    * setting up the KeyListener instance for a MenuItem.
43131    * @private	
43132    * @param {String}
43132 type String representing the name of the event that 
43133    * was fired.
43134    * @param {Array} args Array of arguments sent when the event was fired.
43135    * @param {Array} keyData Array of arguments sent when the event was fired.
43136    */
43137    _createKeyListener: function (type, args, keyData) {
43138
43139        var oMenuItem = this,
43140            oMenu = oMenuItem.parent;
43141
43142        var oKeyListener = new YAHOO.util.KeyListener(
43143                                        oMenu.element.ownerDocument, 
43144                                        keyData, 
43145                                        {
43146                                            fn: oMenuItem._dispatchClickEvent, 
43147                                            scope: oMenuItem, 
43148                                            correctScope: true });
43149
43150
43151        if (oMenu.cfg.getProperty(_VISIBLE)) {
43152            oKeyListener.enable();
43153        }
43154
43155
43156        oMenu.subscribe(_SHOW, oKeyListener.enable, null, oKeyListener);
43157        oMenu.subscribe(_HIDE, oKeyListener.disable, null, oKeyListener);
43158        
43159        oMenuItem._keyListener = oKeyListener;
43160        
43161        oMenu.unsubscribe(_SHOW, oMenuItem._createKeyListener, keyData);
43162        
43163    },
43164
43165
43166    /**
43167    * @method configKeyListener
43168    * @description Event handler for when the "keylistener" configuration 
43169    * property of a menu item changes.
43170    * @param {String} p_sType String representing the name of the event that 
43171    * was fired.
43172    * @param {Array} p_aArgs Array of arguments sent when the event was fired.
43173    */
43174    configKeyListener: function (p_sType, p_aArgs) {
43175
43176        var oKeyData = p_aArgs[0],
43177            oMenuItem = this,
43178            oMenu = oMenuItem.parent;
43179
43180        if (oMenuItem._keyData) {
43181
43182            //	Unsubscribe from the "show" event in case the keylistener 
43183            //	config was changed before the Menu was ever made visible.
43184
43185            oMenu.unsubscribe(_SHOW, 
43186                    oMenuItem._createKeyListener, oMenuItem._keyData);
43187
43188            oMenuItem._keyData = null;					
43189                    
43190        }
43191
43192
43193        //	Tear down for the previous value of the "keylistener" property
43194
43195        if (oMenuItem._keyListener) {
43196
43197            oMenu.unsubscribe(_SHOW, oMenuItem._keyListener.enable);
43198            oMenu.unsubscribe(_HIDE, oMenuItem._keyListener.disable);
43199
43200            oMenuItem._keyListener.disable();
43201            oMenuItem._keyListener = null;
43202
43203        }
43204
43205
43206        if (oKeyData) {
43207    
43208            oMenuItem._keyData = oKeyData;
43209
43210            //	Defer the creation of the KeyListener instance until the 
43211            //	parent Menu is visible.  This is necessary since the 
43212            //	KeyListener instance needs to be bound to the document the 
43213            //	Menu has been rendered into.  Deferring creation of the 
43214            //	KeyListener instance also improves performance.
43215
43216            oMenu.subscribe(_SHOW, oMenuItem._createKeyListener, 
43217                oKeyData, oMenuItem);
43218        }
43219    
43220    },
43221
43222
43223    // Public methods
43224
43225
43226    /**
43227    * @method initDefaultConfig
43228    * @description Initializes an item's configurable properties.
43229    */
43230    initDefaultConfig : function () {
43231
43232        var oConfig = this.cfg;
43233
43234
43235        // Define the configuration attributes
43236
43237        /**
43238        * @config text
43239        * @description String or markup specifying the text label for the menu item.  
43240        * When building a menu from existing HTML the value of this property
43241        * will be interpreted from the menu's markup. The text is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
43242        * @default ""
43243        * @type HTML
43244        */
43245        oConfig.addProperty(
43246            TEXT_CONFIG.key, 
43247            { 
43248                handler: this.configText, 
43249                value: TEXT_CONFIG.value, 
43250                validator: TEXT_CONFIG.validator, 
43251                suppressEvent: TEXT_CONFIG.suppressEvent 
43252            }
43253        );
43254        
43255
43256        /**
43257        * @config helptext
43258        * @description String or markup specifying additional instructional text to 
43259        * accompany the text for the menu item. The helptext is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
43260        * @deprecated Use "text" configuration property to add help text markup.  
43261        * For example: <code>oMenuItem.cfg.setProperty("text", "Copy &#60;em 
43262        * class=\"helptext\"&#62;Ctrl + C&#60;/em&#62;");</code>
43263        * @default null
43264        * @type HTML|<a href="http://www.w3.org/TR/
43265        * 2000/WD-DOM-Level-1-20000929/level-one-html.html#ID-58190037">
43266        * HTMLElement</a>
43267        */
43268        oConfig.addProperty(
43269            HELP_TEXT_CONFIG.key,
43270            {
43271                handler: this.configHelpText, 
43272                supercedes: HELP_TEXT_CONFIG.supercedes,
43273                suppressEvent: HELP_TEXT_CONFIG.suppressEvent 
43274            }
43275        );
43276
43277
43278        /**
43279        * @config url
43280        * @description String specifying the URL for the menu item's anchor's 
43281        * "href" attribute.  When building a menu from existing HTML the value 
43282        * of this property will be interpreted from the menu's markup. Markup for the menu item content. The url is inserted into the DOM as an attribute value, and should be escaped by the implementor if coming from an external source.
43283        * @default "#"
43284        * @type String
43285        */        
43286        oConfig.addProperty(
43287            URL_CONFIG.key, 
43288            {
43289                handler: this.configURL, 
43290                value: URL_CONFIG.value, 
43291                suppressEvent: URL_CONFIG.suppressEvent
43292            }
43293        );
43294
43295
43296        /**
43297        * @config target
43298        * @description String specifying the value for the "target" attribute 
43299        * of the menu item's anchor element. <strong>Specifying a target will 
43300        * require the user to click directly on the menu item's anchor node in
43301        * order to cause the browser to navigate to the specified URL.</strong> 
43302        * When building a menu from existing HTML the value of this property 
43303        * will be interpreted from the menu's markup. The target is inserted into the DOM as an attribute value, and should be escaped by the implementor if coming from an external source.
43304        * @default null
43305        * @type String
43306        */        
43307        oConfig.addProperty(
43308            TARGET_CONFIG.key, 
43309            {
43310                handler: this.configTarget, 
43311                suppressEvent: TARGET_CONFIG.suppressEvent
43312            }
43313        );
43314
43315
43316        /**
43317        * @config emphasis
43318        * @description Boolean indicating if the text of the menu item will be 
43319        * rendered with emphasis.
43320        * @deprecated Use the "text" configuration property to add emphasis.  
43321        * For example: <code>oMenuItem.cfg.setProperty("text", "&#60;em&#62;Some 
43322        * Text&#60;/em&#62;");</code>
43323        * @default false
43324        * @type Boolean
43325        */
43326        oConfig.addProperty(
43327            EMPHASIS_CONFIG.key, 
43328            { 
43329                handler: this.configEmphasis, 
43330                value: EMPHASIS_CONFIG.value, 
43331                validator: EMPHASIS_CONFIG.validator, 
43332                suppressEvent: EMPHASIS_CONFIG.suppressEvent,
43333                supercedes: EMPHASIS_CONFIG.supercedes
43334            }
43335        );
43336
43337
43338        /**
43339        * @config strongemphasis
43340        * @description Boolean indicating if the text of the menu item will be 
43341        * rendered with strong emphasis.
43342        * @deprecated Use the "text" configuration property to add strong emphasis.  
43343        * For example: <code>oMenuItem.cfg.setProperty("text", "&#60;strong&#62; 
43344        * Some Text&#60;/strong&#62;");</code>
43345        * @default false
43346        * @type Boolean
43347        */
43348        oConfig.addProperty(
43349            STRONG_EMPHASIS_CONFIG.key,
43350            {
43351                handler: this.configStrongEmphasis,
43352                value: STRONG_EMPHASIS_CONFIG.value,
43353                validator: STRONG_EMPHASIS_CONFIG.validator,
43354                suppressEvent: STRONG_EMPHASIS_CONFIG.suppressEvent,
43355                supercedes: STRONG_EMPHASIS_CONFIG.supercedes
43356            }
43357        );
43358
43359
43360        /**
43361        * @config checked
43362        * @description Boolean indicating if the menu item should be rendered 
43363        * with a checkmark.
43364        * @default false
43365        * @type Boolean
43366        */
43367        oConfig.addProperty(
43368            CHECKED_CONFIG.key, 
43369            {
43370                handler: this.configChecked, 
43371                value: CHECKED_CONFIG.value, 
43372                validator: CHECKED_CONFIG.validator, 
43373                suppressEvent: CHECKED_CONFIG.suppressEvent,
43374                supercedes: CHECKED_CONFIG.supercedes
43375            } 
43376        );
43377
43378
43379        /**
43380        * @config disabled
43381        * @description Boolean indicating if the menu item should be disabled.  
43382        * (Disabled menu items are  dimmed and will not respond to user input 
43383        * or fire events.)
43384        * @default false
43385        * @type Boolean
43386        */
43387        oConfig.addProperty(
43388            DISABLED_CONFIG.key,
43389            {
43390                handler: this.configDisabled,
43391                value: DISABLED_CONFIG.value,
43392                validator: DISABLED_CONFIG.validator,
43393                suppressEvent: DISABLED_CONFIG.suppressEvent
43394            }
43395        );
43396
43397
43398        /**
43399        * @config selected
43400        * @description Boolean indicating if the menu item should 
43401        * be highlighted.
43402        * @default false
43403        * @type Boolean
43404        */
43405        oConfig.addProperty(
43406            SELECTED_CONFIG.key,
43407            {
43408                handler: this.configSelected,
43409                value: SELECTED_CONFIG.value,
43410                validator: SELECTED_CONFIG.validator,
43411                suppressEvent: SELECTED_CONFIG.suppressEvent
43412            }
43413        );
43414
43415
43416        /**
43417        * @config submenu
43418        * @description Object specifying the submenu to be appended to the 
43419        * menu item.  The value can be one of the following: <ul><li>Object 
43420        * specifying a Menu instance.</li><li>Object literal specifying the
43421        * menu to be created.  Format: <code>{ id: [menu id], itemdata: 
43422        * [<a href="YAHOO.widget.Menu.html#itemData">array of values for 
43423        * items</a>] }</code>.</li><li>String specifying the id attribute 
43424        * of the <code>&#60;div&#62;</code> element of the menu.</li><li>
43425        * Object specifying the <code>&#60;div&#62;</code> element of the 
43426        * menu.</li></ul>
43427        * @default null
43428        * @type Menu|String|Object|<a href="http://www.w3.org/TR/2000/
43429        * WD-DOM-Level-1-20000929/level-one-html.html#ID-58190037">
43430        * HTMLElement</a>
43431        */
43432        oConfig.addProperty(
43433            SUBMENU_CONFIG.key, 
43434            {
43435                handler: this.configSubmenu, 
43436                supercedes: SUBMENU_CONFIG.supercedes,
43437                suppressEvent: SUBMENU_CONFIG.suppressEvent
43438            }
43439        );
43440
43441
43442        /**
43443        * @config onclick
43444        * @description Object literal representing the code to be executed when 
43445        * the item is clicked.  Format:<br> <code> {<br> 
43446        * <strong>fn:</strong> Function,   &#47;&#47; The handler to call when 
43447        * the event fires.<br> <strong>obj:</strong> Object, &#47;&#47; An 
43448        * object to  pass back to the handler.<br> <strong>scope:</strong> 
43449        * Object &#47;&#47; The object to use for the scope of the handler.
43450        * <br> } </code>
43451        * @type Object
43452        * @default null
43453        */
43454        oConfig.addProperty(
43455            ONCLICK_CONFIG.key, 
43456            {
43457                handler: this.configOnClick, 
43458                suppressEvent: ONCLICK_CONFIG.suppressEvent 
43459            }
43460        );
43461
43462
43463        /**
43464        * @config classname
43465        * @description CSS class to be applied to the menu item's root 
43466        * <code>&#60;li&#62;</code> element.  The specified class(es) are 
43467        * appended in addition to the default class as specified by the menu 
43468        * item's CSS_CLASS_NAME constant.
43469        * @default null
43470        * @type String
43471        */
43472        oConfig.addProperty(
43473            CLASS_NAME_CONFIG.key, 
43474            { 
43475                handler: this.configClassName,
43476                value: CLASS_NAME_CONFIG.value, 
43477                validator: CLASS_NAME_CONFIG.validator,
43478                suppressEvent: CLASS_NAME_CONFIG.suppressEvent 
43479            }
43480        );
43481
43482
43483        /**
43484        * @config keylistener
43485        * @description Object literal representing the key(s) that can be used 
43486        * to trigger the MenuItem's "click" event.  Possible attributes are 
43487        * shift (boolean), alt (boolean), ctrl (boolean) and keys (either an int 
43488        * or an array of ints representing keycodes).
43489        * @default null
43490        * @type Object
43491        */
43492        oConfig.addProperty(
43493            KEY_LISTENER_CONFIG.key, 
43494            { 
43495                handler: this.configKeyListener,
43496                value: KEY_LISTENER_CONFIG.value, 
43497                suppressEvent: KEY_LISTENER_CONFIG.suppressEvent 
43498            }
43499        );
43500
43501    },
43502
43503    /**
43504    * @method getNextSibling
43505    * @description Finds the menu item's next sibling.
43506    * @return YAHOO.widget.MenuItem
43507    */
43508    getNextSibling: function () {
43509    
43510        var isUL = function (el) {
43511                return (el.nodeName.toLowerCase() === "ul");
43512            },
43513    
43514            menuitemEl = this.element,
43515            next = Dom.getNextSibling(menuitemEl),
43516            parent,
43517            sibling,
43518            list;
43519        
43520        if (!next) {
43521            
43522            parent = menuitemEl.parentNode;
43523            sibling = Dom.getNextSiblingBy(parent, isUL);
43524            
43525            if (sibling) {
43526                list = sibling;
43527            }
43528            else {
43529                list = Dom.getFirstChildBy(parent.parentNode, isUL);
43530            }
43531            
43532            next = Dom.getFirstChild(list);
43533            
43534        }
43535
43536        return YAHOO.widget.MenuManager.getMenuItem(next.id);
43537
43538    },
43539
43540    /**
43541    * @method getNextEnabledSibling
43542    * @description Finds the menu item's next enabled sibling.
43543    * @return YAHOO.widget.MenuItem
43544    */
43545    getNextEnabledSibling: function () {
43546        
43547        var next = this.getNextSibling();
43548        
43549        return (next.cfg.getProperty(_DISABLED) || next.element.style.display == _NONE) ? next.getNextEnabledSibling() : next;
43550        
43551    },
43552
43553
43554    /**
43555    * @method getPreviousSibling
43556    * @description Finds the menu item's previous sibling.
43557    * @return {YAHOO.widget.MenuItem}
43558    */	
43559    getPreviousSibling: function () {
43560
43561        var isUL = function (el) {
43562                return (el.nodeName.toLowerCase() === "ul");
43563            },
43564
43565            menuitemEl = this.element,
43566            next = Dom.getPreviousSibling(menuitemEl),
43567            parent,
43568            sibling,
43569            list;
43570        
43571        if (!next) {
43572            
43573            parent = menuitemEl.parentNode;
43574            sibling = Dom.getPreviousSiblingBy(parent, isUL);
43575            
43576            if (sibling) {
43577                list = sibling;
43578            }
43579            else {
43580                list = Dom.getLastChildBy(parent.parentNode, isUL);
43581            }
43582            
43583            next = Dom.getLastChild(list);
43584            
43585        }
43586
43587        return YAHOO.widget.MenuManager.getMenuItem(next.id);
43588        
43589    },
43590
43591
43592    /**
43593    * @method getPreviousEnabledSibling
43594    * @description Finds the menu item's previous enabled sibling.
43595    * @return {YAHOO.widget.MenuItem}
43596    */
43597    getPreviousEnabledSibling: function () {
43598        
43599        var next = this.getPreviousSibling();
43600        
43601        return (next.cfg.getProperty(_DISABLED) || next.element.style.display == _NONE) ? next.getPreviousEnabledSibling() : next;
43602        
43603    },
43604
43605
43606    /**
43607    * @method focus
43608    * @description Causes the menu item to receive the focus and fires the 
43609    * focus event.
43610    */
43611    focus: function () {
43612
43613        var oParent = this.parent,
43614            oAnchor = this._oAnchor,
43615            oActiveItem = oParent.activeItem;
43616
43617
43618        function setFocus() {
43619
43620            try {
43621
43622                if (!(UA.ie && !document.hasFocus())) {
43623                
43624                    if (oActiveItem) {
43625        
43626                        oActiveItem.blurEvent.fire();
43627        
43628                    }
43629    
43630                    oAnchor.focus();
43631                    
43632                    this.focusEvent.fire();
43633                
43634                }
43635
43636            }
43637            catch(e) {
43638            
43639            }
43640
43641        }
43642
43643
43644        if (!this.cfg.getProperty(_DISABLED) && oParent && oParent.cfg.getProperty(_VISIBLE) && 
43645            this.element.style.display != _NONE) {
43646
43647
43648            /*
43649                Setting focus via a timer fixes a race condition in Firefox, IE 
43650                and Opera where the browser viewport jumps as it trys to 
43651                position and focus the menu.
43652            */
43653
43654            Lang.later(0, this, setFocus);
43655
43656        }
43657
43658    },
43659
43660
43661    /**
43662    * @method blur
43663    * @description Causes the menu item to lose focus and fires the 
43664    * blur event.
43665    */    
43666    blur: function () {
43667
43668        var oParent = this.parent;
43669
43670        if (!this.cfg.getProperty(_DISABLED) && oParent && oParent.cfg.getProperty(_VISIBLE)) {
43671
43672            Lang.later(0, this, function () {
43673
43674                try {
43675    
43676                    this._oAnchor.blur();
43677                    this.blurEvent.fire();    
43678
43679                } 
43680                catch (e) {
43681                
43682                }
43683                
43684            }, 0);
43685
43686        }
43687
43688    },
43689
43690
43691    /**
43692    * @method hasFocus
43693    * @description Returns a boolean indicating whether or not the menu item
43694    * has focus.
43695    * @return {Boolean}
43696    */
43697    hasFocus: function () {
43698    
43699        return (YAHOO.widget.MenuManager.getFocusedMenuItem() == this);
43700    
43701    },
43702
43703
43704    /**
43705    * @method destroy
43706    * @description Removes the menu item's <code>&#60;li&#62;</code> element 
43707    * from its parent <code>&#60;ul&#62;</code> element.
43708    */
43709    destroy: function () {
43710
43711        var oEl = this.element,
43712            oSubmenu,
43713            oParentNode,
43714            aEventData,
43715            i;
43716
43717
43718        if (oEl) {
43719
43720
43721            // If the item has a submenu, destroy it first
43722
43723            oSubmenu = this.cfg.getProperty(_SUBMENU);
43724
43725            if (oSubmenu) {
43726            
43727                oSubmenu.destroy();
43728            
43729            }
43730
43731
43732            // Remove the element from the parent node
43733
43734            oParentNode = oEl.parentNode;
43735
43736            if (oParentNode) {
43737
43738                oParentNode.removeChild(oEl);
43739
43740                this.destroyEvent.fire();
43741
43742            }
43743
43744
43745            // Remove CustomEvent listeners
43746
43747            i = EVENT_TYPES.length - 1;
43748
43749            do {
43750
43751                aEventData = EVENT_TYPES[i];
43752                
43753                this[aEventData[0]].unsubscribeAll();
43754
43755            }
43756            while (i--);
43757            
43758            
43759            this.cfg.configChangedEvent.unsubscribeAll();
43760
43761        }
43762
43763    },
43764
43765
43766    /**
43767    * @method toString
43768    * @description Returns a string representing the menu item.
43769    * @return {String}
43770    */
43771    toString: function () {
43772
43773        var sReturnVal = _MENUITEM,
43774            sId = this.id;
43775
43776        if (sId) {
43777    
43778            sReturnVal += (_SPACE + sId);
43779        
43780        }
43781
43782        return sReturnVal;
43783    
43784    }
43785
43786};
43787
43788Lang.augmentProto(MenuItem, YAHOO.util.EventProvider);
43789
43790})();
43791(function () {
43792
43793    var _XY = "xy",
43794        _MOUSEDOWN = "mousedown",
43795        _CONTEXTMENU = "ContextMenu",
43796        _SPACE = " ";
43797
43798/**
43799* Creates a list of options or commands which are made visible in response to 
43800* an HTML element's "contextmenu" event ("mousedown" for Opera).
43801*
43802* @param {String} p_oElement String specifying the id attribute of the 
43803* <code>&#60;div&#62;</code> element of the context menu.
43804* @param {String} p_oElement String specifying the id attribute of the 
43805* <code>&#60;select&#62;</code> element to be used as the data source for the 
43806* context menu.
43807* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-
43808* html.html#ID-22445964">HTMLDivElement</a>} p_oElement Object specifying the 
43809* <code>&#60;div&#62;</code> element of the context menu.
43810* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-
43811* html.html#ID-94282980">HTMLSelectElement</a>} p_oElement Object specifying 
43812* the <code>&#60;select&#62;</code> element to be used as the data source for 
43813* the context menu.
43814* @param {Object} p_oConfig Optional. Object literal specifying the 
43815* configuration for the context menu. See configuration class documentation 
43816* for more details.
43817* @class ContextMenu
43818* @constructor
43819* @extends YAHOO.widget.Menu
43820* @namespace YAHOO.widget
43821*/
43822YAHOO.widget.ContextMenu = function(p_oElement, p_oConfig) {
43823    YAHOO.widget.ContextMenu.superclass.constructor.call(this, p_oElement, p_oConfig);
43824};
43825
43826
43827var Event = YAHOO.util.Event,
43828    UA = YAHOO.env.ua,
43829    ContextMenu = YAHOO.widget.ContextMenu,
43830
43831
43832
43833    /**
43834    * Constant representing the name of the ContextMenu's events
43835    * @property EVENT_TYPES
43836    * @private
43837    * @final
43838    * @type Object
43839    */
43840    EVENT_TYPES = {
43841
43842        "TRIGGER_CONTEXT_MENU": "triggerContextMenu",
43843        "CONTEXT_MENU": (UA.opera ? _MOUSEDOWN : "contextmenu"),
43844        "CLICK": "click"
43845
43846    },
43847    
43848    
43849    /**
43850    * Constant representing the ContextMenu's configuration properties
43851    * @property DEFAULT_CONFIG
43852    * @private
43853    * @final
43854    * @type Object
43855    */
43856    TRIGGER_CONFIG = { 
43857        key: "trigger",
43858        suppressEvent: true
43859    };
43860
43861
43862/**
43863* @method position
43864* @description "beforeShow" event handler used to position the contextmenu.
43865* @private
43866* @param {String}
43866 p_sType String representing the name of the event that 
43867* was fired.
43868* @param {Array} p_aArgs Array of arguments sent when the event was fired.
43869* @param {Array} p_aPos Array representing the xy position for the context menu.
43870*/
43871function position(p_sType, p_aArgs, p_aPos) {
43872    this.cfg.setProperty(_XY, p_aPos);
43873    this.beforeShowEvent.unsubscribe(position, p_aPos);
43874}
43875
43876
43877YAHOO.lang.extend(ContextMenu, YAHOO.widget.Menu, {
43878
43879
43880
43881// Private properties
43882
43883
43884/**
43885* @property _oTrigger
43886* @description Object reference to the current value of the "trigger" 
43887* configuration property.
43888* @default null
43889* @private
43890* @type String|<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/leve
43891* l-one-html.html#ID-58190037">HTMLElement</a>|Array
43892*/
43893_oTrigger: null,
43894
43895
43896/**
43897* @property _bCancelled
43898* @description Boolean indicating if the display of the context menu should 
43899* be cancelled.
43900* @default false
43901* @private
43902* @type Boolean
43903*/
43904_bCancelled: false,
43905
43906
43907
43908// Public properties
43909
43910
43911/**
43912* @property contextEventTarget
43913* @description Object reference for the HTML element that was the target of the
43914* "contextmenu" DOM event ("mousedown" for Opera) that triggered the display of 
43915* the context menu.
43916* @default null
43917* @type <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-
43918* html.html#ID-58190037">HTMLElement</a>
43919*/
43920contextEventTarget: null,
43921
43922
43923
43924// Events
43925
43926
43927/**
43928* @event triggerContextMenuEvent
43929* @param type {String} The name of the event, "triggerContextMenu"
43930* @param args {Array} The array of event arguments. For this event, the underlying
43931* DOM event is the only argument, available from args[0].
43932* @description Custom Event wrapper for the "contextmenu" DOM event 
43933* ("mousedown" for Opera) fired by the element(s) that trigger the display of 
43934* the context menu.
43935*/
43936triggerContextMenuEvent: null,
43937
43938
43939
43940/**
43941* @method init
43942* @description The ContextMenu class's initialization method. This method is 
43943* automatically called by the constructor, and sets up all DOM references for 
43944* pre-existing markup, and creates required markup if it is not already present.
43945* @param {String} p_oElement String specifying the id attribute of the 
43946* <code>&#60;div&#62;</code> element of the context menu.
43947* @param {String} p_oElement String specifying the id attribute of the 
43948* <code>&#60;select&#62;</code> element to be used as the data source for 
43949* the context menu.
43950* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-
43951* html.html#ID-22445964">HTMLDivElement</a>} p_oElement Object specifying the 
43952* <code>&#60;div&#62;</code> element of the context menu.
43953* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-
43954* html.html#ID-94282980">HTMLSelectElement</a>} p_oElement Object specifying 
43955* the <code>&#60;select&#62;</code> element to be used as the data source for 
43956* the context menu.
43957* @param {Object} p_oConfig Optional. Object literal specifying the 
43958* configuration for the context menu. See configuration class documentation 
43959* for more details.
43960*/
43961init: function(p_oElement, p_oConfig) {
43962
43963
43964    // Call the init of the superclass (YAHOO.widget.Menu)
43965    
43966    ContextMenu.superclass.init.call(this, p_oElement);
43967
43968    this.beforeInitEvent.fire(ContextMenu);
43969
43970    if (p_oConfig) {
43971        this.cfg.applyConfig(p_oConfig, true);
43972    }
43973
43974    this.initEvent.fire(ContextMenu);
43975},
43976
43977
43978/**
43979* @method initEvents
43980* @description Initializes the custom events for the context menu.
43981*/
43982initEvents: function() {
43983    ContextMenu.superclass.initEvents.call(this);
43984
43985    // Create custom events
43986    this.triggerContextMenuEvent = this.createEvent(EVENT_TYPES.TRIGGER_CONTEXT_MENU);
43987    this.triggerContextMenuEvent.signature = YAHOO.util.CustomEvent.LIST;
43988},
43989
43990/**
43991* @method cancel
43992* @description Cancels the display of the context menu.
43993*/
43994cancel: function() {
43995    this._bCancelled = true;
43996},
43997
43998// Private methods
43999
44000
44001/**
44002* @method _removeEventHandlers
44003* @description Removes all of the DOM event handlers from the HTML element(s) 
44004* whose "context menu" event ("click" for Opera) trigger the display of 
44005* the context menu.
44006* @private
44007*/
44008_removeEventHandlers: function() {
44009
44010    var oTrigger = this._oTrigger;
44011
44012    // Remove the event handlers from the trigger(s)
44013    if (oTrigger) {
44014        Event.removeListener(oTrigger, EVENT_TYPES.CONTEXT_MENU, this._onTriggerContextMenu);    
44015
44016        if (UA.opera) {
44017            Event.removeListener(oTrigger, EVENT_TYPES.CLICK, this._onTriggerClick);
44018        }
44019    }
44020
44021},
44022
44023// Private event handlers
44024
44025/**
44026* @method _onTriggerClick
44027* @description "click" event handler for the HTML element(s) identified as the 
44028* "trigger" for the context menu.  Used to cancel default behaviors in Opera.
44029* @private
44030* @param {Event} p_oEvent Object representing the DOM event object passed back 
44031* by the event utility (YAHOO.util.Event).
44032* @param {YAHOO.widget.ContextMenu} p_oMenu Object representing the context 
44033* menu that is handling the event.
44034*/
44035_onTriggerClick: function(p_oEvent, p_oMenu) {
44036
44037    if (p_oEvent.ctrlKey) {
44038        Event.stopEvent(p_oEvent);
44039    }
44040    
44041},
44042
44043
44044/**
44045* @method _onTriggerContextMenu
44046* @description "contextmenu" event handler ("mousedown" for Opera) for the HTML 
44047* element(s) that trigger the display of the context menu.
44048* @private
44049* @param {Event} p_oEvent Object representing the DOM event object passed back 
44050* by the event utility (YAHOO.util.Event).
44051* @param {YAHOO.widget.ContextMenu} p_oMenu Object representing the context 
44052* menu that is handling the event.
44053*/
44054_onTriggerContextMenu: function(p_oEvent, p_oMenu) {
44055
44056    var aXY;
44057
44058    if (!(p_oEvent.type == _MOUSEDOWN && !p_oEvent.ctrlKey)) {
44059    
44060        this.contextEventTarget = Event.getTarget(p_oEvent);
44061    
44062        this.triggerContextMenuEvent.fire(p_oEvent);
44063        
44064    
44065        if (!this._bCancelled) {
44066
44067            /*
44068                Prevent the browser's default context menu from appearing and 
44069                stop the propagation of the "contextmenu" event so that 
44070                other ContextMenu instances are not displayed.
44071            */
44072
44073            Event.stopEvent(p_oEvent);
44074
44075
44076            // Hide any other Menu instances that might be visible
44077
44078            YAHOO.widget.MenuManager.hideVisible();
44079            
44080    
44081
44082            // Position and display the context menu
44083    
44084            aXY = Event.getXY(p_oEvent);
44085    
44086    
44087            if (!YAHOO.util.Dom.inDocument(this.element)) {
44088    
44089                this.beforeShowEvent.subscribe(position, aXY);
44090    
44091            }
44092            else {
44093    
44094                this.cfg.setProperty(_XY, aXY);
44095            
44096            }
44097    
44098    
44099            this.show();
44100    
44101        }
44102    
44103        this._bCancelled = false;
44104
44105    }
44106
44107},
44108
44109
44110
44111// Public methods
44112
44113
44114/**
44115* @method toString
44116* @description Returns a string representing the context menu.
44117* @return {String}
44118*/
44119toString: function() {
44120
44121    var sReturnVal = _CONTEXTMENU,
44122        sId = this.id;
44123
44124    if (sId) {
44125
44126        sReturnVal += (_SPACE + sId);
44127    
44128    }
44129
44130    return sReturnVal;
44131
44132},
44133
44134
44135/**
44136* @method initDefaultConfig
44137* @description Initializes the class's configurable properties which can be 
44138* changed using the context menu's Config object ("cfg").
44139*/
44140initDefaultConfig: function() {
44141
44142    ContextMenu.superclass.initDefaultConfig.call(this);
44143
44144    /**
44145    * @config trigger
44146    * @description The HTML element(s) whose "contextmenu" event ("mousedown" 
44147    * for Opera) trigger the display of the context menu.  Can be a string 
44148    * representing the id attribute of the HTML element, an object reference 
44149    * for the HTML element, or an array of strings or HTML element references.
44150    * @default null
44151    * @type String|<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/
44152    * level-one-html.html#ID-58190037">HTMLElement</a>|Array
44153    */
44154    this.cfg.addProperty(TRIGGER_CONFIG.key, 
44155        {
44156            handler: this.configTrigger, 
44157            suppressEvent: TRIGGER_CONFIG.suppressEvent 
44158        }
44159    );
44160
44161},
44162
44163
44164/**
44165* @method destroy
44166* @description Removes the context menu's <code>&#60;div&#62;</code> element 
44167* (and accompanying child nodes) from the document.
44168* @param {boolean} shallowPurge If true, only the parent element's DOM event listeners are purged. If false, or not provided, all children are also purged of DOM event listeners. 
44169* NOTE: The flag is a "shallowPurge" flag, as opposed to what may be a more intuitive "purgeChildren" flag to maintain backwards compatibility with behavior prior to 2.9.0.
44170*/
44171destroy: function(shallowPurge) {
44172
44173    // Remove the DOM event handlers from the current trigger(s)
44174
44175    this._removeEventHandlers();
44176
44177
44178    // Continue with the superclass implementation of this method
44179
44180    ContextMenu.superclass.destroy.call(this, shallowPurge);
44181
44182},
44183
44184
44185
44186// Public event handlers for configuration properties
44187
44188
44189/**
44190* @method configTrigger
44191* @description Event handler for when the value of the "trigger" configuration 
44192* property changes. 
44193* @param {String} p_sType String representing the name of the event that 
44194* was fired.
44195* @param {Array} p_aArgs Array of arguments sent when the event was fired.
44196* @param {YAHOO.widget.ContextMenu} p_oMenu Object representing the context 
44197* menu that fired the event.
44198*/
44199configTrigger: function(p_sType, p_aArgs, p_oMenu) {
44200    
44201    var oTrigger = p_aArgs[0];
44202
44203    if (oTrigger) {
44204
44205        /*
44206            If there is a current "trigger" - remove the event handlers 
44207            from that element(s) before assigning new ones
44208        */
44209
44210        if (this._oTrigger) {
44211        
44212            this._removeEventHandlers();
44213
44214        }
44215
44216        this._oTrigger = oTrigger;
44217
44218
44219        /*
44220            Listen for the "mousedown" event in Opera b/c it does not 
44221            support the "contextmenu" event
44222        */ 
44223  
44224        Event.on(oTrigger, EVENT_TYPES.CONTEXT_MENU, this._onTriggerContextMenu, this, true);
44225
44226
44227        /*
44228            Assign a "click" event handler to the trigger element(s) for
44229            Opera to prevent default browser behaviors.
44230        */
44231
44232        if (UA.opera) {
44233        
44234            Event.on(oTrigger, EVENT_TYPES.CLICK, this._onTriggerClick, this, true);
44235
44236        }
44237
44238    }
44239    else {
44240   
44241        this._removeEventHandlers();
44242    
44243    }
44244    
44245}
44246
44247}); // END YAHOO.lang.extend
44248
44249}());
44250
44251
44252
44253/**
44254* Creates an item for a context menu.
44255* 
44256* @param {String} p_oObject String specifying the text of the context menu item.
44257* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
44258* one-html.html#ID-74680021">HTMLLIElement</a>} p_oObject Object specifying the 
44259* <code>&#60;li&#62;</code> element of the context menu item.
44260* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
44261* one-html.html#ID-38450247">HTMLOptGroupElement</a>} p_oObject Object 
44262* specifying the <code>&#60;optgroup&#62;</code> element of the context 
44263* menu item.
44264* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
44265* one-html.html#ID-70901257">HTMLOptionElement</a>} p_oObject Object specifying 
44266* the <code>&#60;option&#62;</code> element of the context menu item.
44267* @param {Object} p_oConfig Optional. Object literal specifying the 
44268* configuration for the context menu item. See configuration class 
44269* documentation for more details.
44270* @class ContextMenuItem
44271* @constructor
44272* @extends YAHOO.widget.MenuItem
44273* @deprecated As of version 2.4.0 items for YAHOO.widget.ContextMenu instances
44274* are of type YAHOO.widget.MenuItem.
44275*/
44276YAHOO.widget.ContextMenuItem = YAHOO.widget.MenuItem;
44277(function () {
44278
44279    var Lang = YAHOO.lang,
44280
44281        // String constants
44282    
44283        _STATIC = "static",
44284        _DYNAMIC_STATIC = "dynamic," + _STATIC,
44285        _DISABLED = "disabled",
44286        _SELECTED = "selected",
44287        _AUTO_SUBMENU_DISPLAY = "autosubmenudisplay",
44288        _SUBMENU = "submenu",
44289        _VISIBLE = "visible",
44290        _SPACE = " ",
44291        _SUBMENU_TOGGLE_REGION = "submenutoggleregion",
44292        _MENUBAR = "MenuBar";
44293
44294/**
44295* Horizontal collection of items, each of which can contain a submenu.
44296* 
44297* @param {String} p_oElement String specifying the id attribute of the 
44298* <code>&#60;div&#62;</code> element of the menu bar.
44299* @param {String} p_oElement String specifying the id attribute of the 
44300* <code>&#60;select&#62;</code> element to be used as the data source for the 
44301* menu bar.
44302* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
44303* one-html.html#ID-22445964">HTMLDivElement</a>} p_oElement Object specifying 
44304* the <code>&#60;div&#62;</code> element of the menu bar.
44305* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
44306* one-html.html#ID-94282980">HTMLSelectElement</a>} p_oElement Object 
44307* specifying the <code>&#60;select&#62;</code> element to be used as the data 
44308* source for the menu bar.
44309* @param {Object} p_oConfig Optional. Object literal specifying the 
44310* configuration for the menu bar. See configuration class documentation for
44311* more details.
44312* @class MenuBar
44313* @constructor
44314* @extends YAHOO.widget.Menu
44315* @namespace YAHOO.widget
44316*/
44317YAHOO.widget.MenuBar = function(p_oElement, p_oConfig) {
44318
44319    YAHOO.widget.MenuBar.superclass.constructor.call(this, p_oElement, p_oConfig);
44320
44321};
44322
44323
44324/**
44325* @method checkPosition
44326* @description Checks to make sure that the value of the "position" property 
44327* is one of the supported strings. Returns true if the position is supported.
44328* @private
44329* @param {Object} p_sPosition String specifying the position of the menu.
44330* @return {Boolean}
44331*/
44332function checkPosition(p_sPosition) {
44333
44334    var returnVal = false;
44335
44336    if (Lang.isString(p_sPosition)) {
44337
44338        returnVal = (_DYNAMIC_STATIC.indexOf((p_sPosition.toLowerCase())) != -1);
44339
44340    }
44341    
44342    return returnVal;
44343
44344}
44345
44346
44347var Event = YAHOO.util.Event,
44348    MenuBar = YAHOO.widget.MenuBar,
44349
44350    POSITION_CONFIG =  { 
44351        key: "position", 
44352        value: _STATIC, 
44353        validator: checkPosition, 
44354        supercedes: [_VISIBLE] 
44355    }, 
44356
44357    SUBMENU_ALIGNMENT_CONFIG =  { 
44358        key: "submenualignment", 
44359        value: ["tl","bl"]
44360    },
44361
44362    AUTO_SUBMENU_DISPLAY_CONFIG =  { 
44363        key: _AUTO_SUBMENU_DISPLAY, 
44364        value: false, 
44365        validator: Lang.isBoolean,
44366        suppressEvent: true
44367    },
44368    
44369    SUBMENU_TOGGLE_REGION_CONFIG = {
44370        key: _SUBMENU_TOGGLE_REGION, 
44371        value: false, 
44372        validator: Lang.isBoolean
44373    };
44374
44375
44376
44377Lang.extend(MenuBar, YAHOO.widget.Menu, {
44378
44379/**
44380* @method init
44381* @description The MenuBar class's initialization method. This method is 
44382* automatically called by the constructor, and sets up all DOM references for 
44383* pre-existing markup, and creates required markup if it is not already present.
44384* @param {String} p_oElement String specifying the id attribute of the 
44385* <code>&#60;div&#62;</code> element of the menu bar.
44386* @param {String} p_oElement String specifying the id attribute of the 
44387* <code>&#60;select&#62;</code> element to be used as the data source for the 
44388* menu bar.
44389* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
44390* one-html.html#ID-22445964">HTMLDivElement</a>} p_oElement Object specifying 
44391* the <code>&#60;div&#62;</code> element of the menu bar.
44392* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
44393* one-html.html#ID-94282980">HTMLSelectElement</a>} p_oElement Object 
44394* specifying the <code>&#60;select&#62;</code> element to be used as the data 
44395* source for the menu bar.
44396* @param {Object} p_oConfig Optional. Object literal specifying the 
44397* configuration for the menu bar. See configuration class documentation for
44398* more details.
44399*/
44400init: function(p_oElement, p_oConfig) {
44401
44402    if(!this.ITEM_TYPE) {
44403
44404        this.ITEM_TYPE = YAHOO.widget.MenuBarItem;
44405
44406    }
44407
44408
44409    // Call the init of the superclass (YAHOO.widget.Menu)
44410
44411    MenuBar.superclass.init.call(this, p_oElement);
44412
44413
44414    this.beforeInitEvent.fire(MenuBar);
44415
44416
44417    if(p_oConfig) {
44418
44419        this.cfg.applyConfig(p_oConfig, true);
44420
44421    }
44422
44423    this.initEvent.fire(MenuBar);
44424
44425},
44426
44427
44428
44429// Constants
44430
44431
44432/**
44433* @property CSS_CLASS_NAME
44434* @description String representing the CSS class(es) to be applied to the menu 
44435* bar's <code>&#60;div&#62;</code> element.
44436* @default "yuimenubar"
44437* @final
44438* @type String
44439*/
44440CSS_CLASS_NAME: "yuimenubar",
44441
44442
44443/**
44444* @property SUBMENU_TOGGLE_REGION_WIDTH
44445* @description Width (in pixels) of the area of a MenuBarItem that, when pressed, will toggle the
44446* display of the MenuBarItem's submenu.
44447* @default 20
44448* @final
44449* @type Number
44450*/
44451SUBMENU_TOGGLE_REGION_WIDTH: 20,
44452
44453
44454// Protected event handlers
44455
44456
44457/**
44458* @method _onKeyDown
44459* @description "keydown" Custom Event handler for the menu bar.
44460* @private
44461* @param {String} p_sType String representing the name of the event that 
44462* was fired.
44463* @param {Array} p_aArgs Array of arguments sent when the event was fired.
44464* @param {YAHOO.widget.MenuBar} p_oMenuBar Object representing the menu bar 
44465* that fired the event.
44466*/
44467_onKeyDown: function(p_sType, p_aArgs, p_oMenuBar) {
44468
44469    var oEvent = p_aArgs[0],
44470        oItem = p_aArgs[1],
44471        oSubmenu,
44472        oItemCfg,
44473        oNextItem;
44474
44475
44476    if(oItem && !oItem.cfg.getProperty(_DISABLED)) {
44477
44478        oItemCfg = oItem.cfg;
44479
44480        switch(oEvent.keyCode) {
44481    
44482            case 37:    // Left arrow
44483            case 39:    // Right arrow
44484    
44485                if(oItem == this.activeItem && !oItemCfg.getProperty(_SELECTED)) {
44486    
44487                    oItemCfg.setProperty(_SELECTED, true);
44488    
44489                }
44490                else {
44491    
44492                    oNextItem = (oEvent.keyCode == 37) ? 
44493                        oItem.getPreviousEnabledSibling() : 
44494                        oItem.getNextEnabledSibling();
44495            
44496                    if(oNextItem) {
44497    
44498                        this.clearActiveItem();
44499    
44500                        oNextItem.cfg.setProperty(_SELECTED, true);
44501                        
44502                        oSubmenu = oNextItem.cfg.getProperty(_SUBMENU);
44503                        
44504                        if(oSubmenu) {
44505                    
44506                            oSubmenu.show();
44507                            oSubmenu.setInitialFocus();
44508                        
44509                        }
44510                        else {
44511                            oNextItem.focus();  
44512                        }
44513    
44514                    }
44515    
44516                }
44517    
44518                Event.preventDefault(oEvent);
44519    
44520            break;
44521    
44522            case 40:    // Down arrow
44523    
44524                if(this.activeItem != oItem) {
44525    
44526                    this.clearActiveItem();
44527    
44528                    oItemCfg.setProperty(_SELECTED, true);
44529                    oItem.focus();
44530                
44531                }
44532    
44533                oSubmenu = oItemCfg.getProperty(_SUBMENU);
44534    
44535                if(oSubmenu) {
44536    
44537                    if(oSubmenu.cfg.getProperty(_VISIBLE)) {
44538    
44539                        oSubmenu.setInitialSelection();
44540                        oSubmenu.setInitialFocus();
44541                    
44542                    }
44543                    else {
44544    
44545                        oSubmenu.show();
44546                        oSubmenu.setInitialFocus();
44547                    
44548                    }
44549    
44550                }
44551    
44552                Event.preventDefault(oEvent);
44553    
44554            break;
44555    
44556        }
44557
44558    }
44559
44560
44561    if(oEvent.keyCode == 27 && this.activeItem) { // Esc key
44562
44563        oSubmenu = this.activeItem.cfg.getProperty(_SUBMENU);
44564
44565        if(oSubmenu && oSubmenu.cfg.getProperty(_VISIBLE)) {
44566        
44567            oSubmenu.hide();
44568            this.activeItem.focus();
44569        
44570        }
44571        else {
44572
44573            this.activeItem.cfg.setProperty(_SELECTED, false);
44574            this.activeItem.blur();
44575    
44576        }
44577
44578        Event.preventDefault(oEvent);
44579    
44580    }
44581
44582},
44583
44584
44585/**
44586* @method _onClick
44587* @description "click" event handler for the menu bar.
44588* @protected
44589* @param {String}
44589 p_sType String representing the name of the event that 
44590* was fired.
44591* @param {Array} p_aArgs Array of arguments sent when the event was fired.
44592* @param {YAHOO.widget.MenuBar} p_oMenuBar Object representing the menu bar 
44593* that fired the event.
44594*/
44595_onClick: function(p_sType, p_aArgs, p_oMenuBar) {
44596
44597    MenuBar.superclass._onClick.call(this, p_sType, p_aArgs, p_oMenuBar);
44598
44599    var oItem = p_aArgs[1],
44600        bReturnVal = true,
44601        oItemEl,
44602        oEvent,
44603        oTarget,
44604        oActiveItem,
44605        oConfig,
44606        oSubmenu,
44607        nMenuItemX,
44608        nToggleRegion;
44609
44610
44611    var toggleSubmenuDisplay = function () {
44612
44613        if(oSubmenu.cfg.getProperty(_VISIBLE)) {
44614        
44615            oSubmenu.hide();
44616        
44617        }
44618        else {
44619        
44620            oSubmenu.show();                    
44621        
44622        }
44623    
44624    };
44625    
44626
44627    if(oItem && !oItem.cfg.getProperty(_DISABLED)) {
44628
44629        oEvent = p_aArgs[0];
44630        oTarget = Event.getTarget(oEvent);
44631        oActiveItem = this.activeItem;
44632        oConfig = this.cfg;
44633
44634
44635        // Hide any other submenus that might be visible
44636    
44637        if(oActiveItem && oActiveItem != oItem) {
44638    
44639            this.clearActiveItem();
44640    
44641        }
44642
44643    
44644        oItem.cfg.setProperty(_SELECTED, true);
44645    
44646
44647        // Show the submenu for the item
44648    
44649        oSubmenu = oItem.cfg.getProperty(_SUBMENU);
44650
44651
44652        if(oSubmenu) {
44653
44654            oItemEl = oItem.element;
44655            nMenuItemX = YAHOO.util.Dom.getX(oItemEl);
44656            nToggleRegion = nMenuItemX + (oItemEl.offsetWidth - this.SUBMENU_TOGGLE_REGION_WIDTH);
44657
44658            if (oConfig.getProperty(_SUBMENU_TOGGLE_REGION)) {
44659
44660                if (Event.getPageX(oEvent) > nToggleRegion) {
44661
44662                    toggleSubmenuDisplay();
44663
44664                    Event.preventDefault(oEvent);
44665
44666                    /*
44667                         Return false so that other click event handlers are not called when the 
44668                         user clicks inside the toggle region.
44669                    */
44670                    bReturnVal = false;
44671                
44672                }
44673        
44674            }
44675            else {
44676
44677                toggleSubmenuDisplay();
44678            
44679            }
44680        
44681        }
44682    
44683    }
44684
44685
44686    return bReturnVal;
44687
44688},
44689
44690
44691
44692// Public methods
44693
44694/**
44695* @method configSubmenuToggle
44696* @description Event handler for when the "submenutoggleregion" configuration property of 
44697* a MenuBar changes.
44698* @param {String} p_sType The name of the event that was fired.
44699* @param {Array} p_aArgs Collection of arguments sent when the event was fired.
44700*/
44701configSubmenuToggle: function (p_sType, p_aArgs) {
44702
44703    var bSubmenuToggle = p_aArgs[0];
44704    
44705    if (bSubmenuToggle) {
44706    
44707        this.cfg.setProperty(_AUTO_SUBMENU_DISPLAY, false);
44708    
44709    }
44710
44711},
44712
44713
44714/**
44715* @method toString
44716* @description Returns a string representing the menu bar.
44717* @return {String}
44718*/
44719toString: function() {
44720
44721    var sReturnVal = _MENUBAR,
44722        sId = this.id;
44723
44724    if(sId) {
44725
44726        sReturnVal += (_SPACE + sId);
44727    
44728    }
44729
44730    return sReturnVal;
44731
44732},
44733
44734
44735/**
44736* @description Initializes the class's configurable properties which can be
44737* changed using the menu bar's Config object ("cfg").
44738* @method initDefaultConfig
44739*/
44740initDefaultConfig: function() {
44741
44742    MenuBar.superclass.initDefaultConfig.call(this);
44743
44744    var oConfig = this.cfg;
44745
44746    // Add configuration properties
44747
44748
44749    /*
44750        Set the default value for the "position" configuration property
44751        to "static" by re-adding the property.
44752    */
44753
44754
44755    /**
44756    * @config position
44757    * @description String indicating how a menu bar should be positioned on the 
44758    * screen.  Possible values are "static" and "dynamic."  Static menu bars 
44759    * are visible by default and reside in the normal flow of the document 
44760    * (CSS position: static).  Dynamic menu bars are hidden by default, reside
44761    * out of the normal flow of the document (CSS position: absolute), and can 
44762    * overlay other elements on the screen.
44763    * @default static
44764    * @type String
44765    */
44766    oConfig.addProperty(
44767        POSITION_CONFIG.key, 
44768        {
44769            handler: this.configPosition, 
44770            value: POSITION_CONFIG.value, 
44771            validator: POSITION_CONFIG.validator,
44772            supercedes: POSITION_CONFIG.supercedes
44773        }
44774    );
44775
44776
44777    /*
44778        Set the default value for the "submenualignment" configuration property
44779        to ["tl","bl"] by re-adding the property.
44780    */
44781
44782    /**
44783    * @config submenualignment
44784    * @description Array defining how submenus should be aligned to their 
44785    * parent menu bar item. The format is: [itemCorner, submenuCorner].
44786    * @default ["tl","bl"]
44787    * @type Array
44788    */
44789    oConfig.addProperty(
44790        SUBMENU_ALIGNMENT_CONFIG.key, 
44791        {
44792            value: SUBMENU_ALIGNMENT_CONFIG.value,
44793            suppressEvent: SUBMENU_ALIGNMENT_CONFIG.suppressEvent
44794        }
44795    );
44796
44797
44798    /*
44799        Change the default value for the "autosubmenudisplay" configuration 
44800        property to "false" by re-adding the property.
44801    */
44802
44803    /**
44804    * @config autosubmenudisplay
44805    * @description Boolean indicating if submenus are automatically made 
44806    * visible when the user mouses over the menu bar's items.
44807    * @default false
44808    * @type Boolean
44809    */
44810    oConfig.addProperty(
44811       AUTO_SUBMENU_DISPLAY_CONFIG.key, 
44812       {
44813           value: AUTO_SUBMENU_DISPLAY_CONFIG.value, 
44814           validator: AUTO_SUBMENU_DISPLAY_CONFIG.validator,
44815           suppressEvent: AUTO_SUBMENU_DISPLAY_CONFIG.suppressEvent
44816       } 
44817    );
44818
44819
44820    /**
44821    * @config submenutoggleregion
44822    * @description Boolean indicating if only a specific region of a MenuBarItem should toggle the 
44823    * display of a submenu.  The default width of the region is determined by the value of the
44824    * SUBMENU_TOGGLE_REGION_WIDTH property.  If set to true, the autosubmenudisplay 
44825    * configuration property will be set to false, and any click event listeners will not be 
44826    * called when the user clicks inside the submenu toggle region of a MenuBarItem.  If the 
44827    * user clicks outside of the submenu toggle region, the MenuBarItem will maintain its 
44828    * standard behavior.
44829    * @default false
44830    * @type Boolean
44831    */
44832    oConfig.addProperty(
44833       SUBMENU_TOGGLE_REGION_CONFIG.key, 
44834       {
44835           value: SUBMENU_TOGGLE_REGION_CONFIG.value, 
44836           validator: SUBMENU_TOGGLE_REGION_CONFIG.validator,
44837           handler: this.configSubmenuToggle
44838       } 
44839    );
44840
44841}
44842 
44843}); // END YAHOO.lang.extend
44844
44845}());
44846
44847
44848
44849/**
44850* Creates an item for a menu bar.
44851* 
44852* @param {HTML} p_oObject Markup for the menu item content. The markup is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
44853* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
44854* one-html.html#ID-74680021">HTMLLIElement</a>} p_oObject Object specifying the 
44855* <code>&#60;li&#62;</code> element of the menu bar item.
44856* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
44857* one-html.html#ID-38450247">HTMLOptGroupElement</a>} p_oObject Object 
44858* specifying the <code>&#60;optgroup&#62;</code> element of the menu bar item.
44859* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
44860* one-html.html#ID-70901257">HTMLOptionElement</a>} p_oObject Object specifying 
44861* the <code>&#60;option&#62;</code> element of the menu bar item.
44862* @param {Object} p_oConfig Optional. Object literal specifying the 
44863* configuration for the menu bar item. See configuration class documentation 
44864* for more details.
44865* @class MenuBarItem
44866* @constructor
44867* @extends YAHOO.widget.MenuItem
44868*/
44869YAHOO.widget.MenuBarItem = function(p_oObject, p_oConfig) {
44870
44871    YAHOO.widget.MenuBarItem.superclass.constructor.call(this, p_oObject, p_oConfig);
44872
44873};
44874
44875YAHOO.lang.extend(YAHOO.widget.MenuBarItem, YAHOO.widget.MenuItem, {
44876
44877
44878
44879/**
44880* @method init
44881* @description The MenuBarItem class's initialization method. This method is 
44882* automatically called by the constructor, and sets up all DOM references for 
44883* pre-existing markup, and creates required markup if it is not already present.
44884* @param {HTML} p_oObject Markup for the menu item content. The markup is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
44885* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
44886* one-html.html#ID-74680021">HTMLLIElement</a>} p_oObject Object specifying the 
44887* <code>&#60;li&#62;</code> element of the menu bar item.
44888* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
44889* one-html.html#ID-38450247">HTMLOptGroupElement</a>} p_oObject Object 
44890* specifying the <code>&#60;optgroup&#62;</code> element of the menu bar item.
44891* @param {<a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-
44892* one-html.html#ID-70901257">HTMLOptionElement</a>} p_oObject Object specifying 
44893* the <code>&#60;option&#62;</code> element of the menu bar item.
44894* @param {Object} p_oConfig Optional. Object literal specifying the 
44895* configuration for the menu bar item. See configuration class documentation 
44896* for more details.
44897*/
44898init: function(p_oObject, p_oConfig) {
44899
44900    if(!this.SUBMENU_TYPE) {
44901
44902        this.SUBMENU_TYPE = YAHOO.widget.Menu;
44903
44904    }
44905
44906
44907    /* 
44908        Call the init of the superclass (YAHOO.widget.MenuItem)
44909        Note: We don't pass the user config in here yet 
44910        because we only want it executed once, at the lowest 
44911        subclass level.
44912    */ 
44913
44914    YAHOO.widget.MenuBarItem.superclass.init.call(this, p_oObject);  
44915
44916
44917    var oConfig = this.cfg;
44918
44919    if(p_oConfig) {
44920
44921        oConfig.applyConfig(p_oConfig, true);
44922
44923    }
44924
44925    oConfig.fireQueue();
44926
44927},
44928
44929
44930
44931// Constants
44932
44933
44934/**
44935* @property CSS_CLASS_NAME
44936* @description String representing the CSS class(es) to be applied to the 
44937* <code>&#60;li&#62;</code> element of the menu bar item.
44938* @default "yuimenubaritem"
44939* @final
44940* @type String
44941*/
44942CSS_CLASS_NAME: "yuimenubaritem",
44943
44944
44945/**
44946* @property CSS_LABEL_CLASS_NAME
44947* @description String representing the CSS class(es) to be applied to the 
44948* menu bar item's <code>&#60;a&#62;</code> element.
44949* @default "yuimenubaritemlabel"
44950* @final
44951* @type String
44952*/
44953CSS_LABEL_CLASS_NAME: "yuimenubaritemlabel",
44954
44955
44956
44957// Public methods
44958
44959
44960/**
44961* @method toString
44962* @description Returns a string representing the menu bar item.
44963* @return {String}
44964*/
44965toString: function() {
44966
44967    var sReturnVal = "MenuBarItem";
44968
44969    if(this.cfg && this.cfg.getProperty("text")) {
44970
44971        sReturnVal += (": " + this.cfg.getProperty("text"));
44972
44973    }
44974
44975    return sReturnVal;
44976
44977}
44978    
44979}); // END YAHOO.lang.extend
44980YAHOO.register("menu", YAHOO.widget.Menu, {version: "2.9.0", build: "2800"});
44981/*
44982Copyright (c) 2011, Yahoo! Inc. All rights reserved.
44983Code licensed under the BSD License:
44984http://developer.yahoo.com/yui/license.html
44985version: 2.9.0
44986*/
44987/**
44988 * Provides methods to parse JSON strings and convert objects to JSON strings.
44989 *
44990 * @module json
44991 * @class JSON
44992 * @namespace YAHOO.lang
44993 * @static
44994 */
44995(function () {
44996
44997var l = YAHOO.lang,
44998    isFunction = l.isFunction,
44999    isObject   = l.isObject,
45000    isArray    = l.isArray,
45001    _toStr     = Object.prototype.toString,
45002                 // 'this' is the global object.  window in browser env.  Keep
45003                 // the code env agnostic.  Caja requies window, unfortunately.
45004    Native     = (YAHOO.env.ua.caja ? window : this).JSON,
45005
45006/* Variables used by parse */
45007
45008    /**
45009     * Replace certain Unicode characters that JavaScript may handle incorrectly
45010     * during eval--either by deleting them or treating them as line
45011     * endings--with escape sequences.
45012     * IMPORTANT NOTE: This regex will be used to modify the input if a match is
45013     * found.
45014     *
45015     * @property _UNICODE_EXCEPTIONS
45016     * @type {RegExp}
45017     * @private
45018     */
45019    _UNICODE_EXCEPTIONS = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
45020
45021    /**
45022     * First step in the safety evaluation.  Regex used to replace all escape
45023     * sequences (i.e. "\\", etc) with '@' characters (a non-JSON character).
45024     *
45025     * @property _ESCAPES
45026     * @type {RegExp}
45027     * @static
45028     * @private
45029     */
45030    _ESCAPES = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
45031
45032    /**
45033     * Second step in the safety evaluation.  Regex used to replace all simple
45034     * values with ']' characters.
45035     *
45036     * @property _VALUES
45037     * @type {RegExp}
45038     * @static
45039     * @private
45040     */
45041    _VALUES  = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
45042
45043    /**
45044     * Third step in the safety evaluation.  Regex used to remove all open
45045     * square brackets following a colon, comma, or at the beginning of the
45046     * string.
45047     *
45048     * @property _BRACKETS
45049     * @type {RegExp}
45050     * @static
45051     * @private
45052     */
45053    _BRACKETS = /(?:^|:|,)(?:\s*\[)+/g,
45054
45055    /**
45056     * Final step in the safety evaluation.  Regex used to test the string left
45057     * after all previous replacements for invalid characters.
45058     *
45059     * @property _UNSAFE
45060     * @type {RegExp}
45061     * @static
45062     * @private
45063     */
45064    _UNSAFE  = /[^\],:{}\s]/,
45065
45066
45067/* Variables used by stringify */
45068
45069    /**
45070     * Regex used to replace special characters in strings for JSON
45071     * stringification.
45072     *
45073     * @property _SPECIAL_CHARS
45074     * @type {RegExp}
45075     * @static
45076     * @private
45077     */
45078    _SPECIAL_CHARS = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
45079
45080    /**
45081     * Character substitution map for common escapes and special characters.
45082     *
45083     * @property _CHARS
45084     * @type {Object}
45085     * @static
45086     * @private
45087     */
45088    _CHARS = {
45089        '\b': '\\b',
45090        '\t': '\\t',
45091        '\n': '\\n',
45092        '\f': '\\f',
45093        '\r': '\\r',
45094        '"' : '\\"',
45095        '\\': '\\\\'
45096    },
45097    
45098    UNDEFINED = 'undefined',
45099    OBJECT    = 'object',
45100    NULL      = 'null',
45101    STRING    = 'string',
45102    NUMBER    = 'number',
45103    BOOLEAN   = 'boolean',
45104    DATE      = 'date',
45105    _allowable = {
45106        'undefined'        : UNDEFINED,
45107        'string'           : STRING,
45108        '[object String]'  : STRING,
45109        'number'           : NUMBER,
45110        '[object Number]'  : NUMBER,
45111        'boolean'          : BOOLEAN,
45112        '[object Boolean]' : BOOLEAN,
45113        '[object Date]'    : DATE,
45114        '[object RegExp]'  : OBJECT
45115    },
45116    EMPTY     = '',
45117    OPEN_O    = '{',
45118    CLOSE_O   = '}',
45119    OPEN_A    = '[',
45120    CLOSE_A   = ']',
45121    COMMA     = ',',
45122    COMMA_CR  = ",\n",
45123    CR        = "\n",
45124    COLON     = ':',
45125    COLON_SP  = ': ',
45126    QUOTE     = '"';
45127
45128// Only accept JSON objects that report a [[Class]] of JSON
45129Native = _toStr.call(Native) === '[object JSON]' && Native;
45130
45131// Escapes a special character to a safe Unicode representation
45132function _char(c) {
45133    if (!_CHARS[c]) {
45134        _CHARS[c] =  '\\u'+('0000'+(+(c.charCodeAt(0))).toString(16)).slice(-4);
45135    }
45136    return _CHARS[c];
45137}
45138
45139
45140/* functions used by parse */
45141
45142/**
45143 * Traverses nested objects, applying a filter or reviver function to
45144 * each value.  The value returned from the function will replace the
45145 * original value in the key:value pair.  If the value returned is
45146 * undefined, the key will be omitted from the returned object.
45147 *
45148 * @method _revive
45149 * @param data {MIXED} Any JavaScript data
45150 * @param reviver {Function} filter or mutation function
45151 * @return {MIXED} The results of the filtered/mutated data structure
45152 * @private
45153 */
45154function _revive(data, reviver) {
45155    var walk = function (o,key) {
45156        var k,v,value = o[key];
45157        if (value && typeof value === 'object') {
45158            for (k in value) {
45159                if (l.hasOwnProperty(value,k)) {
45160                    v = walk(value, k);
45161                    if (v === undefined) {
45162                        delete value[k];
45163                    } else {
45164                        value[k] = v;
45165                    }
45166                }
45167            }
45168        }
45169        return reviver.call(o,key,value);
45170    };
45171
45172    return typeof reviver === 'function' ? walk({'':data},'') : data;
45173}
45174
45175/**
45176 * Replace certain Unicode characters that may be handled incorrectly by
45177 * some browser implementations.
45178 *
45179 * @method _prepare
45180 * @param s {String} parse input
45181 * @return {String} sanitized JSON string ready to be validated/parsed
45182 * @private
45183 */
45184function _prepare(s) {
45185    return s.replace(_UNICODE_EXCEPTIONS, _char);
45186}
45187
45188function _isSafe(str) {
45189    return l.isString(str) &&
45190            !_UNSAFE.test(str.replace(_ESCAPES,'@').
45191                             replace(_VALUES,']').
45192                             replace(_BRACKETS,''));
45193}
45194
45195function _parse(s,reviver) {
45196    // sanitize
45197    s = _prepare(s);
45198
45199    // Ensure valid JSON
45200    if (_isSafe(s)) {
45201        // Eval the text into a JavaScript data structure, apply the
45202        // reviver function if provided, and return
45203        return _revive( eval('(' + s + ')'), reviver );
45204    }
45205
45206    // The text is not valid JSON
45207    throw new SyntaxError('JSON.parse');
45208}
45209
45210
45211
45212/* functions used by stringify */
45213
45214// Utility function used to determine how to serialize a variable.
45215function _type(o) {
45216    var t = typeof o;
45217    return  _allowable[t] ||              // number, string, boolean, undefined
45218            _allowable[_toStr.call(o)] || // Number, String, Boolean, Date
45219            (t === OBJECT ?
45220                (o ? OBJECT : NULL) :     // object, array, null, misc natives
45221                UNDEFINED);               // function, unknown
45222}
45223
45224// Enclose escaped strings in quotes
45225function _string(s) {
45226    return QUOTE + s.replace(_SPECIAL_CHARS, _char) + QUOTE;
45227}
45228
45229// Adds the provided space to the beginning of every line in the input string
45230function _indent(s,space) {
45231    return s.replace(/^/gm, space);
45232}
45233
45234// JavaScript implementation of stringify (see API declaration of stringify)
45235function _stringify(o,w,space) {
45236    if (o === undefined) {
45237        return undefined;
45238    }
45239
45240    var replacer = isFunction(w) ? w : null,
45241        format   = _toStr.call(space).match(/String|Number/) || [],
45242        _date    = YAHOO.lang.JSON.dateToString,
45243        stack    = [],
45244        tmp,i,len;
45245
45246    if (replacer || !isArray(w)) {
45247        w = undefined;
45248    }
45249
45250    // Ensure whitelist keys are unique (bug 2110391)
45251    if (w) {
45252        tmp = {};
45253        for (i = 0, len = w.length; i < len; ++i) {
45254            tmp[w[i]] = true;
45255        }
45256        w = tmp;
45257    }
45258
45259    // Per the spec, strings are truncated to 10 characters and numbers
45260    // are converted to that number of spaces (max 10)
45261    space = format[0] === 'Number' ?
45262                new Array(Math.min(Math.max(0,space),10)+1).join(" ") :
45263                (space || EMPTY).slice(0,10);
45264
45265    function _serialize(h,key) {
45266        var value = h[key],
45267            t     = _type(value),
45268            a     = [],
45269            colon = space ? COLON_SP : COLON,
45270            arr, i, keys, k, v;
45271
45272        // Per the ECMA 5 spec, toJSON is applied before the replacer is
45273        // called.  Also per the spec, Date.prototype.toJSON has been added, so
45274        // Date instances should be serialized prior to exposure to the
45275        // replacer.  I disagree with this decision, but the spec is the spec.
45276        if (isObject(value) && isFunction(value.toJSON)) {
45277            value = value.toJSON(key);
45278        } else if (t === DATE) {
45279            value = _date(value);
45280        }
45281
45282        if (isFunction(replacer)) {
45283            value = replacer.call(h,key,value);
45284        }
45285
45286        if (value !== h[key]) {
45287            t = _type(value);
45288        }
45289
45290        switch (t) {
45291            case DATE    : // intentional fallthrough.  Pre-replacer Dates are
45292                           // serialized in the toJSON stage.  Dates here would
45293                           // have been produced by the replacer.
45294            case OBJECT  : break;
45295            case STRING  : return _string(value);
45296            case NUMBER  : return isFinite(value) ? value+EMPTY : NULL;
45297            case BOOLEAN : return value+EMPTY;
45298            case NULL    : return NULL;
45299            default      : return undefined;
45300        }
45301
45302        // Check for cyclical references in nested objects
45303        for (i = stack.length - 1; i >= 0; --i) {
45304            if (stack[i] === value) {
45305                throw new Error("JSON.stringify. Cyclical reference");
45306            }
45307        }
45308
45309        arr = isArray(value);
45310
45311        // Add the object to the processing stack
45312        stack.push(value);
45313
45314        if (arr) { // Array
45315            for (i = value.length - 1; i >= 0; --i) {
45316                a[i] = _serialize(value, i) || NULL;
45317            }
45318        } else {   // Object
45319            // If whitelist provided, take only those keys
45320            keys = w || value;
45321            i = 0;
45322
45323            for (k in keys) {
45324                if (l.hasOwnProperty(keys, k)) {
45325                    v = _serialize(value, k);
45326                    if (v) {
45327                        a[i++] = _string(k) + colon + v;
45328                    }
45329                }
45330            }
45331        }
45332
45333        // remove the array from the stack
45334        stack.pop();
45335
45336        if (space && a.length) {
45337            return arr ?
45338                OPEN_A + CR + _indent(a.join(COMMA_CR), space) + CR + CLOSE_A :
45339                OPEN_O + CR + _indent(a.join(COMMA_CR), space) + CR + CLOSE_O;
45340        } else {
45341            return arr ?
45342                OPEN_A + a.join(COMMA) + CLOSE_A :
45343                OPEN_O + a.join(COMMA) + CLOSE_O;
45344        }
45345    }
45346
45347    // process the input
45348    return _serialize({'':o},'');
45349}
45350
45351
45352/* Public API */
45353YAHOO.lang.JSON = {
45354    /**
45355     * Leverage native JSON parse if the browser has a native implementation.
45356     * In general, this is a good idea.  See the Known Issues section in the
45357     * JSON user guide for caveats.  The default value is true for browsers with
45358     * native JSON support.
45359     *
45360     * @property useNativeParse
45361     * @type Boolean
45362     * @default true
45363     * @static
45364     */
45365    useNativeParse : !!Native,
45366
45367    /**
45368     * Leverage native JSON stringify if the browser has a native
45369     * implementation.  In general, this is a good idea.  See the Known Issues
45370     * section in the JSON user guide for caveats.  The default value is true
45371     * for browsers with native JSON support.
45372     *
45373     * @property useNativeStringify
45374     * @type Boolean
45375     * @default true
45376     * @static
45377     */
45378    useNativeStringify : !!Native,
45379
45380    /**
45381     * Four step determination whether a string is safe to eval. In three steps,
45382     * escape sequences, safe values, and properly placed open square brackets
45383     * are replaced with placeholders or removed.  Then in the final step, the
45384     * result of all these replacements is checked for invalid characters.
45385     *
45386     * @method isSafe
45387     * @param str {String} JSON string to be tested
45388     * @return {boolean} is the string safe for eval?
45389     * @static
45390     */
45391    isSafe : function (s) {
45392        return _isSafe(_prepare(s));
45393    },
45394
45395    /**
45396     * <p>Parse a JSON string, returning the native JavaScript
45397     * representation.</p>
45398     *
45399     * <p>When lang.JSON.useNativeParse is true, this will defer to the native
45400     * JSON.parse if the browser has a native implementation.  Otherwise, a
45401     * JavaScript implementation based on http://www.json.org/json2.js
45402     * is used.</p>
45403     *
45404     * @method parse
45405     * @param s {string} JSON string data
45406     * @param reviver {function} (optional) function(k,v) passed each key:value
45407     *          pair of object literals, allowing pruning or altering values
45408     * @return {MIXED} the native JavaScript representation of the JSON string
45409     * @throws SyntaxError
45410     * @static
45411     */
45412    parse : function (s,reviver) {
45413        if (typeof s !== 'string') {
45414            s += '';
45415        }
45416
45417        return Native && YAHOO.lang.JSON.useNativeParse ?
45418            Native.parse(s,reviver) : _parse(s,reviver);
45419    },
45420
45421    /**
45422     * <p>Converts an arbitrary value to a JSON string representation.</p>
45423     *
45424     * <p>Objects with cyclical references will trigger an exception.</p>
45425     *
45426     * <p>If a whitelist is provided, only matching object keys will be
45427     * included.  Alternately, a replacer function may be passed as the
45428     * second parameter.  This function is executed on every value in the
45429     * input, and its return value will be used in place of the original value.
45430     * This is useful to serialize specialized objects or class instances.</p>
45431     *
45432     * <p>If a positive integer or non-empty string is passed as the third
45433     * parameter, the output will be formatted with carriage returns and
45434     * indentation for readability.  If a String is passed (such as "\t") it
45435     * will be used once for each indentation level.  If a number is passed,
45436     * that number of spaces will be used.</p>
45437     *
45438     * <p>When lang.JSON.useNativeStringify is true, this will defer to the
45439     * native JSON.stringify if the browser has a native implementation.
45440     * Otherwise, a JavaScript implementation is used.</p>
45441     *
45442     * @method stringify
45443     * @param o {MIXED} any arbitrary object to convert to JSON string
45444     * @param w {Array|Function} (optional) whitelist of acceptable object keys
45445     *                  to include OR a function(value,key) to alter values
45446     *                  before serialization
45447     * @param space {Number|String} (optional) indentation character(s) or
45448     *                  depthy of spaces to format the output 
45449     * @return {string} JSON string representation of the input
45450     * @throws Error
45451     * @static
45452     */
45453    stringify : function (o,w,space) {
45454        return Native && YAHOO.lang.JSON.useNativeStringify ?
45455            Native.stringify(o,w,space) : _stringify(o,w,space);
45456    },
45457
45458    /**
45459     * Serializes a Date instance as a UTC date string.  Used internally by
45460     * the JavaScript implementation of stringify.  If you need a different
45461     * Date serialization format, override this method.  If you change this,
45462     * you should also set useNativeStringify to false, since native JSON
45463     * implementations serialize Dates per the ECMAScript 5 spec.  You've been
45464     * warned.
45465     *
45466     * @method dateToString
45467     * @param d {Date} The Date to serialize
45468     * @return {String} stringified Date in UTC format YYYY-MM-DDTHH:mm:SSZ
45469     * @static
45470     */
45471    dateToString : function (d) {
45472        function _zeroPad(v) {
45473            return v < 10 ? '0' + v : v;
45474        }
45475
45476        return d.getUTCFullYear()         + '-' +
45477            _zeroPad(d.getUTCMonth() + 1) + '-' +
45478            _zeroPad(d.getUTCDate())      + 'T' +
45479            _zeroPad(d.getUTCHours())     + COLON +
45480            _zeroPad(d.getUTCMinutes())   + COLON +
45481            _zeroPad(d.getUTCSeconds())   + 'Z';
45482    },
45483
45484    /**
45485     * Reconstitute Date instances from the default JSON UTC serialization.
45486     * Reference this from a reviver function to rebuild Dates during the
45487     * parse operation.
45488     *
45489     * @method stringToDate
45490     * @param str {String} String serialization of a Date
45491     * @return {Date}
45492     */
45493    stringToDate : function (str) {
45494        var m = str.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{3}))?Z$/);
45495        if (m) {
45496            var d = new Date();
45497            d.setUTCFullYear(m[1], m[2]-1, m[3]);
45498            d.setUTCHours(m[4], m[5], m[6], (m[7] || 0));
45499            return d;
45500        }
45501        return str;
45502    }
45503};
45504
45505/**
45506 * <p>Four step determination whether a string is safe to eval. In three steps,
45507 * escape sequences, safe values, and properly placed open square brackets
45508 * are replaced with placeholders or removed.  Then in the final step, the
45509 * result of all these replacements is checked for invalid characters.</p>
45510 *
45511 * <p>This is an alias for isSafe.</p>
45512 *
45513 * @method isValid
45514 * @param str {String} JSON string to be tested
45515 * @return {boolean} is the string safe for eval?
45516 * @static
45517 * @deprecated use isSafe
45518 */
45519YAHOO.lang.JSON.isValid = YAHOO.lang.JSON.isSafe;
45520
45521})();
45522YAHOO.register("json", YAHOO.lang.JSON, {version: "2.9.0", build: "2800"});
45523/*
45524Copyright (c) 2011, Yahoo! Inc. All rights reserved.
45525Code licensed under the BSD License:
45526http://developer.yahoo.com/yui/license.html
45527version: 2.9.0
45528*/
45529var Y = YAHOO,
45530    Y_DOM = YAHOO.util.Dom, 
45531    EMPTY_ARRAY = [],
45532    Y_UA = Y.env.ua,
45533    Y_Lang = Y.lang,
45534    Y_DOC = document,
45535    Y_DOCUMENT_ELEMENT = Y_DOC.documentElement,
45536
45537    Y_DOM_inDoc = Y_DOM.inDocument, 
45538    Y_mix = Y_Lang.augmentObject,
45539    Y_guid = Y_DOM.generateId,
45540
45541    Y_getDoc = function(element) {
45542        var doc = Y_DOC;
45543        if (element) {
45544            doc = (element.nodeType === 9) ? element : // element === document
45545                element.ownerDocument || // element === DOM node
45546                element.document || // element === window
45547                Y_DOC; // default
45548        }
45549
45550        return doc;
45551    },
45552    
45553    Y_Array = function(o, startIdx) {
45554        var l, a, start = startIdx || 0;
45555
45556        // IE errors when trying to slice HTMLElement collections
45557        try {
45558            return Array.prototype.slice.call(o, start);
45559        } catch (e) {
45560            a = [];
45561            l = o.length;
45562            for (; start < l; start++) {
45563                a.push(o[start]);
45564            }
45565            return a;
45566        }
45567    },
45568
45569    Y_DOM_allById = function(id, root) {
45570        root = root || Y_DOC;
45571        var nodes = [],
45572            ret = [],
45573            i,
45574            node;
45575
45576        if (root.querySelectorAll) {
45577            ret = root.querySelectorAll('[id="' + id + '"]');
45578        } else if (root.all) {
45579            nodes = root.all(id);
45580
45581            if (nodes) {
45582                // root.all may return HTMLElement or HTMLCollection.
45583                // some elements are also HTMLCollection (FORM, SELECT).
45584                if (nodes.nodeName) {
45585                    if (nodes.id === id) { // avoid false positive on name
45586                        ret.push(nodes);
45587                        nodes = EMPTY_ARRAY; // done, no need to filter
45588                    } else { //  prep for filtering
45589                        nodes = [nodes];
45590                    }
45591                }
45592
45593                if (nodes.length) {
45594                    // filter out matches on node.name
45595                    // and element.id as reference to element with id === 'id'
45596                    for (i = 0; node = nodes[i++];) {
45597                        if (node.id === id  || 
45598                                (node.attributes && node.attributes.id &&
45599                                node.attributes.id.value === id)) { 
45600                            ret.push(node);
45601                        }
45602                    }
45603                }
45604            }
45605        } else {
45606            ret = [Y_getDoc(root).getElementById(id)];
45607        }
45608
45609        return ret;
45610    };
45611
45612/**
45613 * The selector-native module provides support for native querySelector
45614 * @module dom
45615 * @submodule selector-native
45616 * @for Selector
45617 */
45618
45619/**
45620 * Provides support for using CSS selectors to query the DOM 
45621 * @class Selector 
45622 * @static
45623 * @for Selector
45624 */
45625
45626var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
45627    OWNER_DOCUMENT = 'ownerDocument',
45628
45629Selector = {
45630    _foundCache: [],
45631
45632    useNative: true,
45633
45634    _compare: ('sourceIndex' in Y_DOCUMENT_ELEMENT) ?
45635        function(nodeA, nodeB) {
45636            var a = nodeA.sourceIndex,
45637                b = nodeB.sourceIndex;
45638
45639            if (a === b) {
45640                return 0;
45641            } else if (a > b) {
45642                return 1;
45643            }
45644
45645            return -1;
45646
45647        } : (Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION] ?
45648        function(nodeA, nodeB) {
45649            if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
45650                return -1;
45651            } else {
45652                return 1;
45653            }
45654        } :
45655        function(nodeA, nodeB) {
45656            var rangeA, rangeB, compare;
45657            if (nodeA && nodeB) {
45658                rangeA = nodeA[OWNER_DOCUMENT].createRange();
45659                rangeA.setStart(nodeA, 0);
45660                rangeB = nodeB[OWNER_DOCUMENT].createRange();
45661                rangeB.setStart(nodeB, 0);
45662                compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
45663            }
45664
45665            return compare;
45666        
45667    }),
45668
45669    _sort: function(nodes) {
45670        if (nodes) {
45671            nodes = Y_Array(nodes, 0, true);
45672            if (nodes.sort) {
45673                nodes.sort(Selector._compare);
45674            }
45675        }
45676
45677        return nodes;
45678    },
45679
45680    _deDupe: function(nodes) {
45681        var ret = [],
45682            i, node;
45683
45684        for (i = 0; (node = nodes[i++]);) {
45685            if (!node._found) {
45686                ret[ret.length] = node;
45687                node._found = true;
45688            }
45689        }
45690
45691        for (i = 0; (node = ret[i++]);) {
45692            node._found = null;
45693            node.removeAttribute('_found');
45694        }
45695
45696        return ret;
45697    },
45698
45699    /**
45700     * Retrieves a set of nodes based on a given CSS selector. 
45701     * @method query
45702     *
45703     * @param {string} selector The CSS Selector to test the node against.
45704     * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
45705     * @param {Boolean} firstOnly optional Whether or not to return only the first match.
45706     * @return {Array} An array of nodes that match the given selector.
45707     * @static
45708     */
45709    query: function(selector, root, firstOnly, skipNative) {
45710        if (root && typeof root == 'string') {
45711            root = Y_DOM.get(root);
45712            if (!root) {
45713                return (firstOnly) ? null : [];
45714            }
45715        } else {
45716            root = root || Y_DOC;
45717        }
45718
45719        var ret = [],
45720            useNative = (Selector.useNative && Y_DOC.querySelector && !skipNative),
45721            queries = [[selector, root]],
45722            query,
45723            result,
45724            i,
45725            fn = (useNative) ? Selector._nativeQuery : Selector._bruteQuery;
45726
45727        if (selector && fn) {
45728            // split group into seperate queries
45729            if (!skipNative && // already done if skipping
45730                    (!useNative || root.tagName)) { // split native when element scoping is needed
45731                queries = Selector._splitQueries(selector, root);
45732            }
45733
45734            for (i = 0; (query = queries[i++]);) {
45735                result = fn(query[0], query[1], firstOnly);
45736                if (!firstOnly) { // coerce DOM Collection to Array
45737                    result = Y_Array(result, 0, true);
45738                }
45739                if (result) {
45740                    ret = ret.concat(result);
45741                }
45742            }
45743
45744            if (queries.length > 1) { // remove dupes and sort by doc order 
45745                ret = Selector._sort(Selector._deDupe(ret));
45746            }
45747        }
45748
45749        YAHOO.log('query: ' + selector + ' returning: ' + ret.length, 'info', 'Selector');
45750        return (firstOnly) ? (ret[0] || null) : ret;
45751
45752    },
45753
45754    // allows element scoped queries to begin with combinator
45755    // e.g. query('> p', document.body) === query('body > p')
45756    _splitQueries: function(selector, node) {
45757        var groups = selector.split(','),
45758            queries = [],
45759            prefix = '',
45760            i, len;
45761
45762        if (node) {
45763            // enforce for element scoping
45764            if (node.tagName) {
45765                node.id = node.id || Y_guid();
45766                prefix = '[id="' + node.id + '"] ';
45767            }
45768
45769            for (i = 0, len = groups.length; i < len; ++i) {
45770                selector =  prefix + groups[i];
45771                queries.push([selector, node]);
45772            }
45773        }
45774
45775        return queries;
45776    },
45777
45778    _nativeQuery: function(selector, root, one) {
45779        if (Y_UA.webkit && selector.indexOf(':checked') > -1 &&
45780                (Selector.pseudos && Selector.pseudos.checked)) { // webkit (chrome, safari) fails to find "selected"
45781            return Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
45782        }
45783        try {
45784            //YAHOO.log('trying native query with: ' + selector, 'info', 'selector-native');
45785            return root['querySelector' + (one ? '' : 'All')](selector);
45786        } catch(e) { // fallback to brute if available
45787            //YAHOO.log('native query error; reverting to brute query with: ' + selector, 'info', 'selector-native');
45788            return Selector.query(selector, root, one, true); // redo with skipNative true
45789        }
45790    },
45791
45792    filter: function(nodes, selector) {
45793        var ret = [],
45794            i, node;
45795
45796        if (nodes && selector) {
45797            for (i = 0; (node = nodes[i++]);) {
45798                if (Selector.test(node, selector)) {
45799                    ret[ret.length] = node;
45800                }
45801            }
45802        } else {
45803            YAHOO.log('invalid filter input (nodes: ' + nodes +
45804                    ', selector: ' + selector + ')', 'warn', 'Selector');
45805        }
45806
45807        return ret;
45808    },
45809
45810    test: function(node, selector, root) {
45811        var ret = false,
45812            groups = selector.split(','),
45813            useFrag = false,
45814            parent,
45815            item,
45816            items,
45817            frag,
45818            i, j, group;
45819
45820        if (node && node.tagName) { // only test HTMLElements
45821
45822            // we need a root if off-doc
45823            if (!root && !Y_DOM_inDoc(node)) {
45824                parent = node.parentNode;
45825                if (parent) { 
45826                    root = parent;
45827                } else { // only use frag when no parent to query
45828                    frag = node[OWNER_DOCUMENT].createDocumentFragment();
45829                    frag.appendChild(node);
45830                    root = frag;
45831                    useFrag = true;
45832                }
45833            }
45834            root = root || node[OWNER_DOCUMENT];
45835
45836            if (!node.id) {
45837                node.id = Y_guid();
45838            }
45839            for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
45840                group += '[id="' + node.id + '"]';
45841                items = Selector.query(group, root);
45842
45843                for (j = 0; item = items[j++];) {
45844                    if (item === node) {
45845                        ret = true;
45846                        break;
45847                    }
45848                }
45849                if (ret) {
45850                    break;
45851                }
45852            }
45853
45854            if (useFrag) { // cleanup
45855                frag.removeChild(node);
45856            }
45857        }
45858
45859        return ret;
45860    }
45861
45862};
45863
45864YAHOO.util.Selector = Selector;
45865/**
45866 * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
45867 * @module dom
45868 * @submodule selector-css2
45869 * @for Selector
45870 */
45871
45872/**
45873 * Provides helper methods for collecting and filtering DOM elements.
45874 */
45875
45876var PARENT_NODE = 'parentNode',
45877    TAG_NAME = 'tagName',
45878    ATTRIBUTES = 'attributes',
45879    COMBINATOR = 'combinator',
45880    PSEUDOS = 'pseudos',
45881
45882    SelectorCSS2 = {
45883        _reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/, // TODO: move?
45884        SORT_RESULTS: true,
45885        _children: function(node, tag) {
45886            var ret = node.children,
45887                i,
45888                children = [],
45889                childNodes,
45890                child;
45891
45892            if (node.children && tag && node.children.tags) {
45893                children = node.children.tags(tag);
45894            } else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children
45895                childNodes = ret || node.childNodes;
45896                ret = [];
45897                for (i = 0; (child = childNodes[i++]);) {
45898                    if (child.tagName) {
45899                        if (!tag || tag === child.tagName) {
45900                            ret.push(child);
45901                        }
45902                    }
45903                }
45904            }
45905
45906            return ret || [];
45907        },
45908
45909        _re: {
45910            //attr: /(\[.*\])/g,
45911            attr: /(\[[^\]]*\])/g,
45912            //esc: /\\[:\[][\w\d\]]*/gi,
45913            esc: /\\[:\[\]\(\)#\.\'\>+~"]/gi,
45914            //pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\))*)/i
45915            pseudos: /(\([^\)]*\))/g
45916        },
45917
45918        /**
45919         * Mapping of shorthand tokens to corresponding attribute selector 
45920         * @property shorthand
45921         * @type object
45922         */
45923        shorthand: {
45924            //'\\#([^\\s\\\\(\\[:]*)': '[id=$1]',
45925            '\\#(-?[_a-z]+[-\\w\\uE000]*)': '[id=$1]',
45926            //'\\#([^\\s\\\.:\\[\\]]*)': '[id=$1]',
45927            //'\\.([^\\s\\\\(\\[:]*)': '[className=$1]'
45928            '\\.(-?[_a-z]+[-\\w\\uE000]*)': '[className~=$1]'
45929        },
45930
45931        /**
45932         * List of operators and corresponding boolean functions. 
45933         * These functions are passed the attribute and the current node's value of the attribute.
45934         * @property operators
45935         * @type object
45936         */
45937        operators: {
45938            '': function(node, attr) { return !!node.getAttribute(attr); }, // Just test for existence of attribute
45939            //'': '.+',
45940            //'=': '^{val}$', // equality
45941            '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
45942            '|=': '^{val}(?:-|$)' // optional hyphen-delimited
45943        },
45944
45945        pseudos: {
45946           'first-child': function(node) { 
45947                return Selector._children(node[PARENT_NODE])[0] === node; 
45948            } 
45949        },
45950
45951        _bruteQuery: function(selector, root, firstOnly) {
45952            var ret = [],
45953                nodes = [],
45954                tokens = Selector._tokenize(selector),
45955                token = tokens[tokens.length - 1],
45956                rootDoc = Y_getDoc(root),
45957                child,
45958                id,
45959                className,
45960                tagName;
45961
45962
45963            // if we have an initial ID, set to root when in document
45964            /*
45965            if (tokens[0] && rootDoc === root &&  
45966                    (id = tokens[0].id) &&
45967                    rootDoc.getElementById(id)) {
45968                root = rootDoc.getElementById(id);
45969            }
45970            */
45971
45972            if (token) {
45973                // prefilter nodes
45974                id = token.id;
45975                className = token.className;
45976                tagName = token.tagName || '*';
45977
45978                if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags
45979                    // try ID first, unless no root.all && root not in document
45980                    // (root.all works off document, but not getElementById)
45981                    // TODO: move to allById?
45982                    if (id && (root.all || (root.nodeType === 9 || Y_DOM_inDoc(root)))) {
45983                        nodes = Y_DOM_allById(id, root);
45984                    // try className
45985                    } else if (className) {
45986                        nodes = root.getElementsByClassName(className);
45987                    } else { // default to tagName
45988                        nodes = root.getElementsByTagName(tagName);
45989                    }
45990
45991                } else { // brute getElementsByTagName('*')
45992                    child = root.firstChild;
45993                    while (child) {
45994                        if (child.tagName) { // only collect HTMLElements
45995                            nodes.push(child);
45996                        }
45997                        child = child.nextSilbing || child.firstChild;
45998                    }
45999                }
46000                if (nodes.length) {
46001                    ret = Selector._filterNodes(nodes, tokens, firstOnly);
46002                }
46003            }
46004
46005            return ret;
46006        },
46007        
46008        _filterNodes: function(nodes, tokens, firstOnly) {
46009            var i = 0,
46010                j,
46011                len = tokens.length,
46012                n = len - 1,
46013                result = [],
46014                node = nodes[0],
46015                tmpNode = node,
46016                getters = Selector.getters,
46017                operator,
46018                combinator,
46019                token,
46020                path,
46021                pass,
46022                //FUNCTION = 'function',
46023                value,
46024                tests,
46025                test;
46026
46027            //do {
46028            for (i = 0; (tmpNode = node = nodes[i++]);) {
46029                n = len - 1;
46030                path = null;
46031                
46032                testLoop:
46033                while (tmpNode && tmpNode.tagName) {
46034                    token = tokens[n];
46035                    tests = token.tests;
46036                    j = tests.length;
46037                    if (j && !pass) {
46038                        while ((test = tests[--j])) {
46039                            operator = test[1];
46040                            if (getters[test[0]]) {
46041                                value = getters[test[0]](tmpNode, test[0]);
46042                            } else {
46043                                value = tmpNode[test[0]];
46044                                // use getAttribute for non-standard attributes
46045                                if (value === undefined && tmpNode.getAttribute) {
46046                                    value = tmpNode.getAttribute(test[0]);
46047                                }
46048                            }
46049
46050                            if ((operator === '=' && value !== test[2]) ||  // fast path for equality
46051                                (typeof operator !== 'string' && // protect against String.test monkey-patch (Moo)
46052                                operator.test && !operator.test(value)) ||  // regex test
46053                                (!operator.test && // protect against RegExp as function (webkit)
46054                                        typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test
46055
46056                                // skip non element nodes or non-matching tags
46057                                if ((tmpNode = tmpNode[path])) {
46058                                    while (tmpNode &&
46059                                        (!tmpNode.tagName ||
46060                                            (token.tagName && token.tagName !== tmpNode.tagName))
46061                                    ) {
46062                                        tmpNode = tmpNode[path]; 
46063                                    }
46064                                }
46065                                continue testLoop;
46066                            }
46067                        }
46068                    }
46069
46070                    n--; // move to next token
46071                    // now that we've passed the test, move up the tree by combinator
46072                    if (!pass && (combinator = token.combinator)) {
46073                        path = combinator.axis;
46074                        tmpNode = tmpNode[path];
46075
46076                        // skip non element nodes
46077                        while (tmpNode && !tmpNode.tagName) {
46078                            tmpNode = tmpNode[path]; 
46079                        }
46080
46081                        if (combinator.direct) { // one pass only
46082                            path = null; 
46083                        }
46084
46085                    } else { // success if we made it this far
46086                        result.push(node);
46087                        if (firstOnly) {
46088                            return result;
46089                        }
46090                        break;
46091                    }
46092                }
46093            }// while (tmpNode = node = nodes[++i]);
46094            node = tmpNode = null;
46095            return result;
46096        },
46097
46098        combinators: {
46099            ' ': {
46100                axis: 'parentNode'
46101            },
46102
46103            '>': {
46104                axis: 'parentNode',
46105                direct: true
46106            },
46107
46108
46109            '+': {
46110                axis: 'previousSibling',
46111                direct: true
46112            }
46113        },
46114
46115        _parsers: [
46116            {
46117                name: ATTRIBUTES,
46118                //re: /^\[(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
46119                re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,
46120                fn: function(match, token) {
46121                    var operator = match[2] || '',
46122                        operators = Selector.operators,
46123                        escVal = (match[3]) ? match[3].replace(/\\/g, '') : '',
46124                        test;
46125
46126                    // add prefiltering for ID and CLASS
46127                    if ((match[1] === 'id' && operator === '=') ||
46128                            (match[1] === 'className' &&
46129                            Y_DOCUMENT_ELEMENT.getElementsByClassName &&
46130                            (operator === '~=' || operator === '='))) {
46131                        token.prefilter = match[1];
46132
46133
46134                        match[3] = escVal; 
46135
46136                        // escape all but ID for prefilter, which may run through QSA (via Dom.allById)
46137                        token[match[1]] = (match[1] === 'id') ? match[3] : escVal;
46138
46139                    }
46140
46141                    // add tests
46142                    if (operator in operators) {
46143                        test = operators[operator];
46144                        if (typeof test === 'string') {
46145                            match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1');
46146                            test = new RegExp(test.replace('{val}', match[3]));
46147                        }
46148                        match[2] = test;
46149                    }
46150                    if (!token.last || token.prefilter !== match[1]) {
46151                        return match.slice(1);
46152                    }
46153                }
46154
46155            },
46156            {
46157                name: TAG_NAME,
46158                re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
46159                fn: function(match, token) {
46160                    var tag = match[1].toUpperCase();
46161                    token.tagName = tag;
46162
46163                    if (tag !== '*' && (!token.last || token.prefilter)) {
46164                        return [TAG_NAME, '=', tag];
46165                    }
46166                    if (!token.prefilter) {
46167                        token.prefilter = 'tagName';
46168                    }
46169                }
46170            },
46171            {
46172                name: COMBINATOR,
46173                re: /^\s*([>+~]|\s)\s*/,
46174                fn: function(match, token) {
46175                }
46176            },
46177            {
46178                name: PSEUDOS,
46179                re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,
46180                fn: function(match, token) {
46181                    var test = Selector[PSEUDOS][match[1]];
46182                    if (test) { // reorder match array and unescape special chars for tests
46183                        if (match[2]) {
46184                            match[2] = match[2].replace(/\\/g, '');
46185                        }
46186                        return [match[2], test]; 
46187                    } else { // selector token not supported (possibly missing CSS3 module)
46188                        return false;
46189                    }
46190                }
46191            }
46192            ],
46193
46194        _getToken: function(token) {
46195            return {
46196                tagName: null,
46197                id: null,
46198                className: null,
46199                attributes: {},
46200                combinator: null,
46201                tests: []
46202            };
46203        },
46204
46205        /**
46206            Break selector into token units per simple selector.
46207            Combinator is attached to the previous token.
46208         */
46209        _tokenize: function(selector) {
46210            selector = selector || '';
46211            selector = Selector._replaceShorthand(Y_Lang.trim(selector)); 
46212            var token = Selector._getToken(),     // one token per simple selector (left selector holds combinator)
46213                query = selector, // original query for debug report
46214                tokens = [],    // array of tokens
46215                found = false,  // whether or not any matches were found this pass
46216                match,         // the regex match
46217                test,
46218                i, parser;
46219
46220            /*
46221                Search for selector patterns, store, and strip them from the selector string
46222                until no patterns match (invalid selector) or we run out of chars.
46223
46224                Multiple attributes and pseudos are allowed, in any order.
46225                for example:
46226                    'form:first-child[type=button]:not(button)[lang|=en]'
46227            */
46228
46229            outer:
46230            do {
46231                found = false; // reset after full pass
46232
46233                for (i = 0; (parser = Selector._parsers[i++]);) {
46234                    if ( (match = parser.re.exec(selector)) ) { // note assignment
46235                        if (parser.name !== COMBINATOR ) {
46236                            token.selector = selector;
46237                        }
46238                        selector = selector.replace(match[0], ''); // strip current match from selector
46239                        if (!selector.length) {
46240                            token.last = true;
46241                        }
46242
46243                        if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
46244                            match[1] = Selector._attrFilters[match[1]];
46245                        }
46246
46247                        test = parser.fn(match, token);
46248                        if (test === false) { // selector not supported
46249                            found = false;
46250                            break outer;
46251                        } else if (test) {
46252                            token.tests.push(test);
46253                        }
46254
46255                        if (!selector.length || parser.name === COMBINATOR) {
46256                            tokens.push(token);
46257                            token = Selector._getToken(token);
46258                            if (parser.name === COMBINATOR) {
46259                                token.combinator = Selector.combinators[match[1]];
46260                            }
46261                        }
46262                        found = true;
46263
46264
46265                    }
46266                }
46267            } while (found && selector.length);
46268
46269            if (!found || selector.length) { // not fully parsed
46270                YAHOO.log('query: ' + query + ' contains unsupported token in: ' + selector, 'warn', 'Selector');
46271                tokens = [];
46272            }
46273            return tokens;
46274        },
46275
46276        _replaceShorthand: function(selector) {
46277            var shorthand = Selector.shorthand,
46278                esc = selector.match(Selector._re.esc), // pull escaped colon, brackets, etc. 
46279                attrs,
46280                pseudos,
46281                re, i, len;
46282
46283            if (esc) {
46284                selector = selector.replace(Selector._re.esc, '\uE000');
46285            }
46286
46287            attrs = selector.match(Selector._re.attr);
46288            pseudos = selector.match(Selector._re.pseudos);
46289
46290            if (attrs) {
46291                selector = selector.replace(Selector._re.attr, '\uE001');
46292            }
46293
46294            if (pseudos) {
46295                selector = selector.replace(Selector._re.pseudos, '\uE002');
46296            }
46297
46298
46299            for (re in shorthand) {
46300                if (shorthand.hasOwnProperty(re)) {
46301                    selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]);
46302                }
46303            }
46304
46305            if (attrs) {
46306                for (i = 0, len = attrs.length; i < len; ++i) {
46307                    selector = selector.replace(/\uE001/, attrs[i]);
46308                }
46309            }
46310
46311            if (pseudos) {
46312                for (i = 0, len = pseudos.length; i < len; ++i) {
46313                    selector = selector.replace(/\uE002/, pseudos[i]);
46314                }
46315            }
46316
46317            selector = selector.replace(/\[/g, '\uE003');
46318            selector = selector.replace(/\]/g, '\uE004');
46319
46320            selector = selector.replace(/\(/g, '\uE005');
46321            selector = selector.replace(/\)/g, '\uE006');
46322
46323            if (esc) {
46324                for (i = 0, len = esc.length; i < len; ++i) {
46325                    selector = selector.replace('\uE000', esc[i]);
46326                }
46327            }
46328
46329            return selector;
46330        },
46331
46332        _attrFilters: {
46333            'class': 'className',
46334            'for': 'htmlFor'
46335        },
46336
46337        getters: {
46338            href: function(node, attr) {
46339                return Y_DOM.getAttribute(node, attr);
46340            }
46341        }
46342    };
46343
46344Y_mix(Selector, SelectorCSS2, true);
46345Selector.getters.src = Selector.getters.rel = Selector.getters.href;
46346
46347// IE wants class with native queries
46348if (Selector.useNative && Y_DOC.querySelector) {
46349    Selector.shorthand['\\.([^\\s\\\\(\\[:]*)'] = '[class~=$1]';
46350}
46351
46352/**
46353 * The selector css3 module provides support for css3 selectors.
46354 * @module dom
46355 * @submodule selector-css3
46356 * @for Selector
46357 */
46358
46359/*
46360    an+b = get every _a_th node starting at the _b_th
46361    0n+b = no repeat ("0" and "n" may both be omitted (together) , e.g. "0n+1" or "1", not "0+1"), return only the _b_th element
46362    1n+b =  get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n")
46363    an+0 = get every _a_th element, "0" may be omitted 
46364*/
46365
46366Selector._reNth = /^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;
46367
46368Selector._getNth = function(node, expr, tag, reverse) {
46369    Selector._reNth.test(expr);
46370    var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_)
46371        n = RegExp.$2, // "n"
46372        oddeven = RegExp.$3, // "odd" or "even"
46373        b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_
46374        result = [],
46375        siblings = Selector._children(node.parentNode, tag),
46376        op;
46377
46378    if (oddeven) {
46379        a = 2; // always every other
46380        op = '+';
46381        n = 'n';
46382        b = (oddeven === 'odd') ? 1 : 0;
46383    } else if ( isNaN(a) ) {
46384        a = (n) ? 1 : 0; // start from the first or no repeat
46385    }
46386
46387    if (a === 0) { // just the first
46388        if (reverse) {
46389            b = siblings.length - b + 1; 
46390        }
46391
46392        if (siblings[b - 1] === node) {
46393            return true;
46394        } else {
46395            return false;
46396        }
46397
46398    } else if (a < 0) {
46399        reverse = !!reverse;
46400        a = Math.abs(a);
46401    }
46402
46403    if (!reverse) {
46404        for (var i = b - 1, len = siblings.length; i < len; i += a) {
46405            if ( i >= 0 && siblings[i] === node ) {
46406                return true;
46407            }
46408        }
46409    } else {
46410        for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) {
46411            if ( i < len && siblings[i] === node ) {
46412                return true;
46413            }
46414        }
46415    }
46416    return false;
46417};
46418
46419Y_mix(Selector.pseudos, {
46420    'root': function(node) {
46421        return node === node.ownerDocument.documentElement;
46422    },
46423
46424    'nth-child': function(node, expr) {
46425        return Selector._getNth(node, expr);
46426    },
46427
46428    'nth-last-child': function(node, expr) {
46429        return Selector._getNth(node, expr, null, true);
46430    },
46431
46432    'nth-of-type': function(node, expr) {
46433        return Selector._getNth(node, expr, node.tagName);
46434    },
46435     
46436    'nth-last-of-type': function(node, expr) {
46437        return Selector._getNth(node, expr, node.tagName, true);
46438    },
46439     
46440    'last-child': function(node) {
46441        var children = Selector._children(node.parentNode);
46442        return children[children.length - 1] === node;
46443    },
46444
46445    'first-of-type': function(node) {
46446        return Selector._children(node.parentNode, node.tagName)[0] === node;
46447    },
46448     
46449    'last-of-type': function(node) {
46450        var children = Selector._children(node.parentNode, node.tagName);
46451        return children[children.length - 1] === node;
46452    },
46453     
46454    'only-child': function(node) {
46455        var children = Selector._children(node.parentNode);
46456        return children.length === 1 && children[0] === node;
46457    },
46458
46459    'only-of-type': function(node) {
46460        var children = Selector._children(node.parentNode, node.tagName);
46461        return children.length === 1 && children[0] === node;
46462    },
46463
46464    'empty': function(node) {
46465        return node.childNodes.length === 0;
46466    },
46467
46468    'not': function(node, expr) {
46469        return !Selector.test(node, expr);
46470    },
46471
46472    'contains': function(node, expr) {
46473        var text = node.innerText || node.textContent || '';
46474        return text.indexOf(expr) > -1;
46475    },
46476
46477    'checked': function(node) {
46478        return (node.checked === true || node.selected === true);
46479    },
46480
46481    enabled: function(node) {
46482        return (node.disabled !== undefined && !node.disabled);
46483    },
46484
46485    disabled: function(node) {
46486        return (node.disabled);
46487    }
46488});
46489
46490Y_mix(Selector.operators, {
46491    '^=': '^{val}', // Match starts with value
46492    '!=': function(node, attr, val) { return node[attr] !== val; }, // Match starts with value
46493    '$=': '{val}$', // Match ends with value
46494    '*=': '{val}' // Match contains value as substring 
46495});
46496
46497Selector.combinators['~'] = {
46498    axis: 'previousSibling'
46499};
46500YAHOO.register("selector", YAHOO.util.Selector, {version: "2.9.0", build: "2800"});
46501/*
46502Copyright (c) 2011, Yahoo! Inc. All rights reserved.
46503Code licensed under the BSD License:
46504http://developer.yahoo.com/yui/license.html
46505version: 2.9.0
46506*/
46507(function () {
46508
46509var lang   = YAHOO.lang,
46510    util   = YAHOO.util,
46511    Ev     = util.Event;
46512
46513/**
46514 * The DataSource utility provides a common configurable interface for widgets to
46515 * access a variety of data, from JavaScript arrays to online database servers.
46516 *
46517 * @module datasource
46518 * @requires yahoo, event
46519 * @optional json, get, connection 
46520 * @title DataSource Utility
46521 */
46522
46523/****************************************************************************/
46524/****************************************************************************/
46525/****************************************************************************/
46526
46527/**
46528 * Base class for the YUI DataSource utility.
46529 *
46530 * @namespace YAHOO.util
46531 * @class YAHOO.util.DataSourceBase
46532 * @constructor
46533 * @param oLiveData {HTMLElement}  Pointer to live data.
46534 * @param oConfigs {object} (optional) Object literal of configuration values.
46535 */
46536util.DataSourceBase = function(oLiveData, oConfigs) {
46537    if(oLiveData === null || oLiveData === undefined) {
46538        YAHOO.log("Could not instantiate DataSource due to invalid live database",
46539                "error", this.toString());
46540        return;
46541    }
46542    
46543    this.liveData = oLiveData;
46544    this._oQueue = {interval:null, conn:null, requests:[]};
46545    this.responseSchema = {};   
46546
46547    // Set any config params passed in to override defaults
46548    if(oConfigs && (oConfigs.constructor == Object)) {
46549        for(var sConfig in oConfigs) {
46550            if(sConfig) {
46551                this[sConfig] = oConfigs[sConfig];
46552            }
46553        }
46554    }
46555    
46556    // Validate and initialize public configs
46557    var maxCacheEntries = this.maxCacheEntries;
46558    if(!lang.isNumber(maxCacheEntries) || (maxCacheEntries < 0)) {
46559        maxCacheEntries = 0;
46560    }
46561
46562    // Initialize interval tracker
46563    this._aIntervals = [];
46564
46565    /////////////////////////////////////////////////////////////////////////////
46566    //
46567    // Custom Events
46568    //
46569    /////////////////////////////////////////////////////////////////////////////
46570
46571    /**
46572     * Fired when a request is made to the local cache.
46573     *
46574     * @event cacheRequestEvent
46575     * @param oArgs.request {Object} The request object.
46576     * @param oArgs.callback {Object} The callback object.
46577     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
46578     */
46579    this.createEvent("cacheRequestEvent");
46580
46581    /**
46582     * Fired when data is retrieved from the local cache.
46583     *
46584     * @event cacheResponseEvent
46585     * @param oArgs.request {Object} The request object.
46586     * @param oArgs.response {Object} The response object.
46587     * @param oArgs.callback {Object} The callback object.
46588     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
46589     */
46590    this.createEvent("cacheResponseEvent");
46591
46592    /**
46593     * Fired when a request is sent to the live data source.
46594     *
46595     * @event requestEvent
46596     * @param oArgs.request {Object} The request object.
46597     * @param oArgs.callback {Object} The callback object.
46598     * @param oArgs.tId {Number} Transaction ID.     
46599     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
46600     */
46601    this.createEvent("requestEvent");
46602
46603    /**
46604     * Fired when live data source sends response.
46605     *
46606     * @event responseEvent
46607     * @param oArgs.request {Object} The request object.
46608     * @param oArgs.response {Object} The raw response object.
46609     * @param oArgs.callback {Object} The callback object.
46610     * @param oArgs.tId {Number} Transaction ID.     
46611     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
46612     */
46613    this.createEvent("responseEvent");
46614
46615    /**
46616     * Fired when response is parsed.
46617     *
46618     * @event responseParseEvent
46619     * @param oArgs.request {Object} The request object.
46620     * @param oArgs.response {Object} The parsed response object.
46621     * @param oArgs.callback {Object} The callback object.
46622     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
46623     */
46624    this.createEvent("responseParseEvent");
46625
46626    /**
46627     * Fired when response is cached.
46628     *
46629     * @event responseCacheEvent
46630     * @param oArgs.request {Object} The request object.
46631     * @param oArgs.response {Object} The parsed response object.
46632     * @param oArgs.callback {Object} The callback object.
46633     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
46634     */
46635    this.createEvent("responseCacheEvent");
46636    /**
46637     * Fired when an error is encountered with the live data source.
46638     *
46639     * @event dataErrorEvent
46640     * @param oArgs.request {Object} The request object.
46641     * @param oArgs.response {String} The response object (if available).
46642     * @param oArgs.callback {Object} The callback object.
46643     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
46644     * @param oArgs.message {String} The error message.
46645     */
46646    this.createEvent("dataErrorEvent");
46647
46648    /**
46649     * Fired when the local cache is flushed.
46650     *
46651     * @event cacheFlushEvent
46652     */
46653    this.createEvent("cacheFlushEvent");
46654
46655    var DS = util.DataSourceBase;
46656    this._sName = "DataSource instance" + DS._nIndex;
46657    DS._nIndex++;
46658    YAHOO.log("DataSource initialized", "info", this.toString());
46659};
46660
46661var DS = util.DataSourceBase;
46662
46663lang.augmentObject(DS, {
46664
46665/////////////////////////////////////////////////////////////////////////////
46666//
46667// DataSourceBase public constants
46668//
46669/////////////////////////////////////////////////////////////////////////////
46670
46671/**
46672 * Type is unknown.
46673 *
46674 * @property TYPE_UNKNOWN
46675 * @type Number
46676 * @final
46677 * @default -1
46678 */
46679TYPE_UNKNOWN : -1,
46680
46681/**
46682 * Type is a JavaScript Array.
46683 *
46684 * @property TYPE_JSARRAY
46685 * @type Number
46686 * @final
46687 * @default 0
46688 */
46689TYPE_JSARRAY : 0,
46690
46691/**
46692 * Type is a JavaScript Function.
46693 *
46694 * @property TYPE_JSFUNCTION
46695 * @type Number
46696 * @final
46697 * @default 1
46698 */
46699TYPE_JSFUNCTION : 1,
46700
46701/**
46702 * Type is hosted on a server via an XHR connection.
46703 *
46704 * @property TYPE_XHR
46705 * @type Number
46706 * @final
46707 * @default 2
46708 */
46709TYPE_XHR : 2,
46710
46711/**
46712 * Type is JSON.
46713 *
46714 * @property TYPE_JSON
46715 * @type Number
46716 * @final
46717 * @default 3
46718 */
46719TYPE_JSON : 3,
46720
46721/**
46722 * Type is XML.
46723 *
46724 * @property TYPE_XML
46725 * @type Number
46726 * @final
46727 * @default 4
46728 */
46729TYPE_XML : 4,
46730
46731/**
46732 * Type is plain text.
46733 *
46734 * @property TYPE_TEXT
46735 * @type Number
46736 * @final
46737 * @default 5
46738 */
46739TYPE_TEXT : 5,
46740
46741/**
46742 * Type is an HTML TABLE element. Data is parsed out of TR elements from all TBODY elements.
46743 *
46744 * @property TYPE_HTMLTABLE
46745 * @type Number
46746 * @final
46747 * @default 6
46748 */
46749TYPE_HTMLTABLE : 6,
46750
46751/**
46752 * Type is hosted on a server via a dynamic script node.
46753 *
46754 * @property TYPE_SCRIPTNODE
46755 * @type Number
46756 * @final
46757 * @default 7
46758 */
46759TYPE_SCRIPTNODE : 7,
46760
46761/**
46762 * Type is local.
46763 *
46764 * @property TYPE_LOCAL
46765 * @type Number
46766 * @final
46767 * @default 8
46768 */
46769TYPE_LOCAL : 8,
46770
46771/**
46772 * Error message for invalid dataresponses.
46773 *
46774 * @property ERROR_DATAINVALID
46775 * @type String
46776 * @final
46777 * @default "Invalid data"
46778 */
46779ERROR_DATAINVALID : "Invalid data",
46780
46781/**
46782 * Error message for null data responses.
46783 *
46784 * @property ERROR_DATANULL
46785 * @type String
46786 * @final
46787 * @default "Null data"
46788 */
46789ERROR_DATANULL : "Null data",
46790
46791/////////////////////////////////////////////////////////////////////////////
46792//
46793// DataSourceBase private static properties
46794//
46795/////////////////////////////////////////////////////////////////////////////
46796
46797/**
46798 * Internal class variable to index multiple DataSource instances.
46799 *
46800 * @property DataSourceBase._nIndex
46801 * @type Number
46802 * @private
46803 * @static
46804 */
46805_nIndex : 0,
46806
46807/**
46808 * Internal class variable to assign unique transaction IDs.
46809 *
46810 * @property DataSourceBase._nTransactionId
46811 * @type Number
46812 * @private
46813 * @static
46814 */
46815_nTransactionId : 0,
46816
46817/////////////////////////////////////////////////////////////////////////////
46818//
46819// DataSourceBase private static methods
46820//
46821/////////////////////////////////////////////////////////////////////////////
46822/**
46823 * Clones object literal or array of object literals.
46824 *
46825 * @method DataSourceBase._cloneObject
46826 * @param o {Object} Object.
46827 * @private
46828 * @static
46829 */
46830_cloneObject: function(o) {
46831    if(!lang.isValue(o)) {
46832        return o;
46833    }
46834
46835    var copy = {};
46836
46837    if(Object.prototype.toString.apply(o) === "[object RegExp]") {
46838        copy = o;
46839    }
46840    else if(lang.isFunction(o)) {
46841        copy = o;
46842    }
46843    else if(lang.isArray(o)) {
46844        var array = [];
46845        for(var i=0,len=o.length;i<len;i++) {
46846            array[i] = DS._cloneObject(o[i]);
46847        }
46848        copy = array;
46849    }
46850    else if(lang.isObject(o)) {
46851        for (var x in o){
46852            if(lang.hasOwnProperty(o, x)) {
46853                if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) {
46854                    copy[x] = DS._cloneObject(o[x]);
46855                }
46856                else {
46857                    copy[x] = o[x];
46858                }
46859            }
46860        }
46861    }
46862    else {
46863        copy = o;
46864    }
46865
46866    return copy;
46867},
46868    
46869/**
46870 * Get an XPath-specified value for a given field from an XML node or document.
46871 *
46872 * @method _getLocationValue
46873 * @param field {String | Object} Field definition.
46874 * @param context {Object} XML node or document to search within.
46875 * @return {Object} Data value or null.
46876 * @static
46877 * @private
46878 */
46879_getLocationValue: function(field, context) {
46880    var locator = field.locator || field.key || field,
46881        xmldoc = context.ownerDocument || context,
46882        result, res, value = null;
46883
46884    try {
46885        // Standards mode
46886        if(!lang.isUndefined(xmldoc.evaluate)) {
46887            result = xmldoc.evaluate(locator, context, xmldoc.createNSResolver(!context.ownerDocument ? context.documentElement : context.ownerDocument.documentElement), 0, null);
46888            while(res = result.iterateNext()) {
46889                value = res.textContent;
46890            }
46891        }
46892        // IE mode
46893        else {
46894            xmldoc.setProperty("SelectionLanguage", "XPath");
46895            result = context.selectNodes(locator)[0];
46896            value = result.value || result.text || null;
46897        }
46898        return value;
46899
46900    }
46901    catch(e) {
46902    }
46903},
46904
46905/////////////////////////////////////////////////////////////////////////////
46906//
46907// DataSourceBase public static methods
46908//
46909/////////////////////////////////////////////////////////////////////////////
46910
46911/**
46912 * Executes a configured callback.  For object literal callbacks, the third
46913 * param determines whether to execute the success handler or failure handler.
46914 *  
46915 * @method issueCallback
46916 * @param callback {Function|Object} the callback to execute
46917 * @param params {Array} params to be passed to the callback method
46918 * @param error {Boolean} whether an error occurred
46919 * @param scope {Object} the scope from which to execute the callback
46920 * (deprecated - use an object literal callback)
46921 * @static     
46922 */
46923issueCallback : function (callback,params,error,scope) {
46924    if (lang.isFunction(callback)) {
46925        callback.apply(scope, params);
46926    } else if (lang.isObject(callback)) {
46927        scope = callback.scope || scope || window;
46928        var callbackFunc = callback.success;
46929        if (error) {
46930            callbackFunc = callback.failure;
46931        }
46932        if (callbackFunc) {
46933            callbackFunc.apply(scope, params.concat([callback.argument]));
46934        }
46935    }
46936},
46937
46938/**
46939 * Converts data to type String.
46940 *
46941 * @method DataSourceBase.parseString
46942 * @param oData {String | Number | Boolean | Date | Array | Object} Data to parse.
46943 * The special values null and undefined will return null.
46944 * @return {String} A string, or null.
46945 * @static
46946 */
46947parseString : function(oData) {
46948    // Special case null and undefined
46949    if(!lang.isValue(oData)) {
46950        return null;
46951    }
46952    
46953    //Convert to string
46954    var string = oData + "";
46955
46956    // Validate
46957    if(lang.isString(string)) {
46958        return string;
46959    }
46960    else {
46961        YAHOO.log("Could not convert data " + lang.dump(oData) + " to type String", "warn", this.toString());
46962        return null;
46963    }
46964},
46965
46966/**
46967 * Converts data to type Number.
46968 *
46969 * @method DataSourceBase.parseNumber
46970 * @param oData {String | Number | Boolean} Data to convert. Note, the following
46971 * values return as null: null, undefined, NaN, "". 
46972 * @return {Number} A number, or null.
46973 * @static
46974 */
46975parseNumber : function(oData) {
46976    if(!lang.isValue(oData) || (oData === "")) {
46977        return null;
46978    }
46979
46980    //Convert to number
46981    var number = oData * 1;
46982    
46983    // Validate
46984    if(lang.isNumber(number)) {
46985        return number;
46986    }
46987    else {
46988        YAHOO.log("Could not convert data " + lang.dump(oData) + " to type Number", "warn", this.toString());
46989        return null;
46990    }
46991},
46992// Backward compatibility
46993convertNumber : function(oData) {
46994    YAHOO.log("The method YAHOO.util.DataSourceBase.convertNumber() has been" +
46995    " deprecated in favor of YAHOO.util.DataSourceBase.parseNumber()", "warn",
46996    this.toString());
46997    return DS.parseNumber(oData);
46998},
46999
47000/**
47001 * Converts data to type Date.
47002 *
47003 * @method DataSourceBase.parseDate
47004 * @param oData {Date | String | Number} Data to convert.
47005 * @return {Date} A Date instance.
47006 * @static
47007 */
47008parseDate : function(oData) {
47009    var date = null;
47010    
47011    //Convert to date
47012    if(lang.isValue(oData) && !(oData instanceof Date)) {
47013        date = new Date(oData);
47014    }
47015    else {
47016        return oData;
47017    }
47018    
47019    // Validate
47020    if(date instanceof Date) {
47021        return date;
47022    }
47023    else {
47024        YAHOO.log("Could not convert data " + lang.dump(oData) + " to type Date", "warn", this.toString());
47025        return null;
47026    }
47027},
47028// Backward compatibility
47029convertDate : function(oData) {
47030    YAHOO.log("The method YAHOO.util.DataSourceBase.convertDate() has been" +
47031    " deprecated in favor of YAHOO.util.DataSourceBase.parseDate()", "warn",
47032    this.toString());
47033    return DS.parseDate(oData);
47034}
47035
47036});
47037
47038// Done in separate step so referenced functions are defined.
47039/**
47040 * Data parsing functions.
47041 * @property DataSource.Parser
47042 * @type Object
47043 * @static
47044 */
47045DS.Parser = {
47046    string   : DS.parseString,
47047    number   : DS.parseNumber,
47048    date     : DS.parseDate
47049};
47050
47051// Prototype properties and methods
47052DS.prototype = {
47053
47054/////////////////////////////////////////////////////////////////////////////
47055//
47056// DataSourceBase private properties
47057//
47058/////////////////////////////////////////////////////////////////////////////
47059
47060/**
47061 * Name of DataSource instance.
47062 *
47063 * @property _sName
47064 * @type String
47065 * @private
47066 */
47067_sName : null,
47068
47069/**
47070 * Local cache of data result object literals indexed chronologically.
47071 *
47072 * @property _aCache
47073 * @type Object[]
47074 * @private
47075 */
47076_aCache : null,
47077
47078/**
47079 * Local queue of request connections, enabled if queue needs to be managed.
47080 *
47081 * @property _oQueue
47082 * @type Object
47083 * @private
47084 */
47085_oQueue : null,
47086
47087/**
47088 * Array of polling interval IDs that have been enabled, needed to clear all intervals.
47089 *
47090 * @property _aIntervals
47091 * @type Array
47092 * @private
47093 */
47094_aIntervals : null,
47095
47096/////////////////////////////////////////////////////////////////////////////
47097//
47098// DataSourceBase public properties
47099//
47100/////////////////////////////////////////////////////////////////////////////
47101
47102/**
47103 * Max size of the local cache.  Set to 0 to turn off caching.  Caching is
47104 * useful to reduce the number of server connections.  Recommended only for data
47105 * sources that return comprehensive results for queries or when stale data is
47106 * not an issue.
47107 *
47108 * @property maxCacheEntries
47109 * @type Number
47110 * @default 0
47111 */
47112maxCacheEntries : 0,
47113
47114 /**
47115 * Pointer to live database.
47116 *
47117 * @property liveData
47118 * @type Object
47119 */
47120liveData : null,
47121
47122/**
47123 * Where the live data is held:
47124 * 
47125 * <dl>  
47126 *    <dt>TYPE_UNKNOWN</dt>
47127 *    <dt>TYPE_LOCAL</dt>
47128 *    <dt>TYPE_XHR</dt>
47129 *    <dt>TYPE_SCRIPTNODE</dt>
47130 *    <dt>TYPE_JSFUNCTION</dt>
47131 * </dl> 
47132 *  
47133 * @property dataType
47134 * @type Number
47135 * @default YAHOO.util.DataSourceBase.TYPE_UNKNOWN
47136 *
47137 */
47138dataType : DS.TYPE_UNKNOWN,
47139
47140/**
47141 * Format of response:
47142 *  
47143 * <dl>  
47144 *    <dt>TYPE_UNKNOWN</dt>
47145 *    <dt>TYPE_JSARRAY</dt>
47146 *    <dt>TYPE_JSON</dt>
47147 *    <dt>TYPE_XML</dt>
47148 *    <dt>TYPE_TEXT</dt>
47149 *    <dt>TYPE_HTMLTABLE</dt> 
47150 * </dl> 
47151 *
47152 * @property responseType
47153 * @type Number
47154 * @default YAHOO.util.DataSourceBase.TYPE_UNKNOWN
47155 */
47156responseType : DS.TYPE_UNKNOWN,
47157
47158/**
47159 * Response schema object literal takes a combination of the following properties:
47160 *
47161 * <dl>
47162 * <dt>resultsList</dt> <dd>Pointer to array of tabular data</dd>
47163 * <dt>resultNode</dt> <dd>Pointer to node name of row data (XML data only)</dd>
47164 * <dt>recordDelim</dt> <dd>Record delimiter (text data only)</dd>
47165 * <dt>fieldDelim</dt> <dd>Field delimiter (text data only)</dd>
47166 * <dt>fields</dt> <dd>Array of field names (aka keys), or array of object literals
47167 * such as: {key:"fieldname",parser:YAHOO.util.DataSourceBase.parseDate}</dd>
47168 * <dt>metaFields</dt> <dd>Object literal of keys to include in the oParsedResponse.meta collection</dd>
47169 * <dt>metaNode</dt> <dd>Name of the node under which to search for meta information in XML response data</dd>
47170 * </dl>
47171 *
47172 * @property responseSchema
47173 * @type Object
47174 */
47175responseSchema : null,
47176
47177/**
47178 * Additional arguments passed to the JSON parse routine.  The JSON string
47179 * is the assumed first argument (where applicable).  This property is not
47180 * set by default, but the parse methods will use it if present.
47181 *
47182 * @property parseJSONArgs
47183 * @type {MIXED|Array} If an Array, contents are used as individual arguments.
47184 *                     Otherwise, value is used as an additional argument.
47185 */
47186// property intentionally undefined
47187 
47188/**
47189 * When working with XML data, setting this property to true enables support for
47190 * XPath-syntaxed locators in schema definitions.
47191 *
47192 * @property useXPath
47193 * @type Boolean
47194 * @default false
47195 */
47196useXPath : false,
47197
47198/**
47199 * Clones entries before adding to cache.
47200 *
47201 * @property cloneBeforeCaching
47202 * @type Boolean
47203 * @default false
47204 */
47205cloneBeforeCaching : false,
47206
47207/////////////////////////////////////////////////////////////////////////////
47208//
47209// DataSourceBase public methods
47210//
47211/////////////////////////////////////////////////////////////////////////////
47212
47213/**
47214 * Public accessor to the unique name of the DataSource instance.
47215 *
47216 * @method toString
47217 * @return {String} Unique name of the DataSource instance.
47218 */
47219toString : function() {
47220    return this._sName;
47221},
47222
47223/**
47224 * Overridable method passes request to cache and returns cached response if any,
47225 * refreshing the hit in the cache as the newest item. Returns null if there is
47226 * no cache hit.
47227 *
47228 * @method getCachedResponse
47229 * @param oRequest {Object} Request object.
47230 * @param oCallback {Object} Callback object.
47231 * @param oCaller {Object} (deprecated) Use callback object.
47232 * @return {Object} Cached response object or null.
47233 */
47234getCachedResponse : function(oRequest, oCallback, oCaller) {
47235    var aCache = this._aCache;
47236
47237    // If cache is enabled...
47238    if(this.maxCacheEntries > 0) {        
47239        // Initialize local cache
47240        if(!aCache) {
47241            this._aCache = [];
47242            YAHOO.log("Cache initialized", "info", this.toString());
47243        }
47244        // Look in local cache
47245        else {
47246            var nCacheLength = aCache.length;
47247            if(nCacheLength > 0) {
47248                var oResponse = null;
47249                this.fireEvent("cacheRequestEvent", {request:oRequest,callback:oCallback,caller:oCaller});
47250        
47251                // Loop through each cached element
47252                for(var i = nCacheLength-1; i >= 0; i--) {
47253                    var oCacheElem = aCache[i];
47254        
47255                    // Defer cache hit logic to a public overridable method
47256                    if(this.isCacheHit(oRequest,oCacheElem.request)) {
47257                        // The cache returned a hit!
47258                        // Grab the cached response
47259                        oResponse = oCacheElem.response;
47260                        this.fireEvent("cacheResponseEvent", {request:oRequest,response:oResponse,callback:oCallback,caller:oCaller});
47261                        
47262                        // Refresh the position of the cache hit
47263                        if(i < nCacheLength-1) {
47264                            // Remove element from its original location
47265                            aCache.splice(i,1);
47266                            // Add as newest
47267                            this.addToCache(oRequest, oResponse);
47268                            YAHOO.log("Refreshed cache position of the response for \"" +  oRequest + "\"", "info", this.toString());
47269                        }
47270                        
47271                        // Add a cache flag
47272                        oResponse.cached = true;
47273                        break;
47274                    }
47275                }
47276                YAHOO.log("The cached response for \"" + lang.dump(oRequest) +
47277                        "\" is " + lang.dump(oResponse), "info", this.toString());
47278                return oResponse;
47279            }
47280        }
47281    }
47282    else if(aCache) {
47283        this._aCache = null;
47284        YAHOO.log("Cache destroyed", "info", this.toString());
47285    }
47286    return null;
47287},
47288
47289/**
47290 * Default overridable method matches given request to given cached request.
47291 * Returns true if is a hit, returns false otherwise.  Implementers should
47292 * override this method to customize the cache-matching algorithm.
47293 *
47294 * @method isCacheHit
47295 * @param oRequest {Object} Request object.
47296 * @param oCachedRequest {Object} Cached request object.
47297 * @return {Boolean} True if given request matches cached request, false otherwise.
47298 */
47299isCacheHit : function(oRequest, oCachedRequest) {
47300    return (oRequest === oCachedRequest);
47301},
47302
47303/**
47304 * Adds a new item to the cache. If cache is full, evicts the stalest item
47305 * before adding the new item.
47306 *
47307 * @method addToCache
47308 * @param oRequest {Object} Request object.
47309 * @param oResponse {Object} Response object to cache.
47310 */
47311addToCache : function(oRequest, oResponse) {
47312    var aCache = this._aCache;
47313    if(!aCache) {
47314        return;
47315    }
47316
47317    // If the cache is full, make room by removing stalest element (index=0)
47318    while(aCache.length >= this.maxCacheEntries) {
47319        aCache.shift();
47320    }
47321
47322    // Add to cache in the newest position, at the end of the array
47323    oResponse = (this.cloneBeforeCaching) ? DS._cloneObject(oResponse) : oResponse;
47324    var oCacheElem = {request:oRequest,response:oResponse};
47325    aCache[aCache.length] = oCacheElem;
47326    this.fireEvent("responseCacheEvent", {request:oRequest,response:oResponse});
47327    YAHOO.log("Cached the response for \"" +  oRequest + "\"", "info", this.toString());
47328},
47329
47330/**
47331 * Flushes cache.
47332 *
47333 * @method flushCache
47334 */
47335flushCache : function() {
47336    if(this._aCache) {
47337        this._aCache = [];
47338        this.fireEvent("cacheFlushEvent");
47339        YAHOO.log("Flushed the cache", "info", this.toString());
47340    }
47341},
47342
47343/**
47344 * Sets up a polling mechanism to send requests at set intervals and forward
47345 * responses to given callback.
47346 *
47347 * @method setInterval
47348 * @param nMsec {Number} Length of interval in milliseconds.
47349 * @param oRequest {Object} Request object.
47350 * @param oCallback {Function} Handler function to receive the response.
47351 * @param oCaller {Object} (deprecated) Use oCallback.scope.
47352 * @return {Number} Interval ID.
47353 */
47354setInterval : function(nMsec, oRequest, oCallback, oCaller) {
47355    if(lang.isNumber(nMsec) && (nMsec >= 0)) {
47356        YAHOO.log("Enabling polling to live data for \"" + oRequest + "\" at interval " + nMsec, "info", this.toString());
47357        var oSelf = this;
47358        var nId = setInterval(function() {
47359            oSelf.makeConnection(oRequest, oCallback, oCaller);
47360        }, nMsec);
47361        this._aIntervals.push(nId);
47362        return nId;
47363    }
47364    else {
47365        YAHOO.log("Could not enable polling to live data for \"" + oRequest + "\" at interval " + nMsec, "info", this.toString());
47366    }
47367},
47368
47369/**
47370 * Disables polling mechanism associated with the given interval ID. Does not
47371 * affect transactions that are in progress.
47372 *
47373 * @method clearInterval
47374 * @param nId {Number} Interval ID.
47375 */
47376clearInterval : function(nId) {
47377    // Remove from tracker if there
47378    var tracker = this._aIntervals || [];
47379    for(var i=tracker.length-1; i>-1; i--) {
47380        if(tracker[i] === nId) {
47381            tracker.splice(i,1);
47382            clearInterval(nId);
47383        }
47384    }
47385},
47386
47387/**
47388 * Disables all known polling intervals. Does not affect transactions that are
47389 * in progress.
47390 *
47391 * @method clearAllIntervals
47392 */
47393clearAllIntervals : function() {
47394    var tracker = this._aIntervals || [];
47395    for(var i=tracker.length-1; i>-1; i--) {
47396        clearInterval(tracker[i]);
47397    }
47398    tracker = [];
47399},
47400
47401/**
47402 * First looks for cached response, then sends request to live data. The
47403 * following arguments are passed to the callback function:
47404 *     <dl>
47405 *     <dt><code>oRequest</code></dt>
47406 *     <dd>The same value that was passed in as the first argument to sendRequest.</dd>
47407 *     <dt><code>oParsedResponse</code></dt>
47408 *     <dd>An object literal containing the following properties:
47409 *         <dl>
47410 *         <dt><code>tId</code></dt>
47411 *         <dd>Unique transaction ID number.</dd>
47412 *         <dt><code>results</code></dt>
47413 *         <dd>Schema-parsed data results.</dd>
47414 *         <dt><code>error</code></dt>
47415 *         <dd>True in cases of data error.</dd>
47416 *         <dt><code>cached</code></dt>
47417 *         <dd>True when response is returned from DataSource cache.</dd> 
47418 *         <dt><code>meta</code></dt>
47419 *         <dd>Schema-parsed meta data.</dd>
47420 *         </dl>
47421 *     <dt><code>oPayload</code></dt>
47422 *     <dd>The same value as was passed in as <code>argument</code> in the oCallback object literal.</dd>
47423 *     </dl> 
47424 *
47425 * @method sendRequest
47426 * @param oRequest {Object} Request object.
47427 * @param oCallback {Object} An object literal with the following properties:
47428 *     <dl>
47429 *     <dt><code>success</code></dt>
47430 *     <dd>The function to call when the data is ready.</dd>
47431 *     <dt><code>failure</code></dt>
47432 *     <dd>The function to call upon a response failure condition.</dd>
47433 *     <dt><code>scope</code></dt>
47434 *     <dd>The object to serve as the scope for the success and failure handlers.</dd>
47435 *     <dt><code>argument</code></dt>
47436 *     <dd>Arbitrary data that will be passed back to the success and failure handlers.</dd>
47437 *     </dl> 
47438 * @param oCaller {Object} (deprecated) Use oCallback.scope.
47439 * @return {Number} Transaction ID, or null if response found in cache.
47440 */
47441sendRequest : function(oRequest, oCallback, oCaller) {
47442    // First look in cache
47443    var oCachedResponse = this.getCachedResponse(oRequest, oCallback, oCaller);
47444    if(oCachedResponse) {
47445        DS.issueCallback(oCallback,[oRequest,oCachedResponse],false,oCaller);
47446        return null;
47447    }
47448
47449
47450    // Not in cache, so forward request to live data
47451    YAHOO.log("Making connection to live data for \"" + oRequest + "\"", "info", this.toString());
47452    return this.makeConnection(oRequest, oCallback, oCaller);
47453},
47454
47455/**
47456 * Overridable default method generates a unique transaction ID and passes 
47457 * the live data reference directly to the  handleResponse function. This
47458 * method should be implemented by subclasses to achieve more complex behavior
47459 * or to access remote data.          
47460 *
47461 * @method makeConnection
47462 * @param oRequest {Object} Request object.
47463 * @param oCallback {Object} Callback object literal.
47464 * @param oCaller {Object} (deprecated) Use oCallback.scope.
47465 * @return {Number} Transaction ID.
47466 */
47467makeConnection : function(oRequest, oCallback, oCaller) {
47468    var tId = DS._nTransactionId++;
47469    this.fireEvent("requestEvent", {tId:tId, request:oRequest,callback:oCallback,caller:oCaller});
47470
47471    /* accounts for the following cases:
47472    YAHOO.util.DataSourceBase.TYPE_UNKNOWN
47473    YAHOO.util.DataSourceBase.TYPE_JSARRAY
47474    YAHOO.util.DataSourceBase.TYPE_JSON
47475    YAHOO.util.DataSourceBase.TYPE_HTMLTABLE
47476    YAHOO.util.DataSourceBase.TYPE_XML
47477    YAHOO.util.DataSourceBase.TYPE_TEXT
47478    */
47479    var oRawResponse = this.liveData;
47480    
47481    this.handleResponse(oRequest, oRawResponse, oCallback, oCaller, tId);
47482    return tId;
47483},
47484
47485/**
47486 * Receives raw data response and type converts to XML, JSON, etc as necessary.
47487 * Forwards oFullResponse to appropriate parsing function to get turned into
47488 * oParsedResponse. Calls doBeforeCallback() and adds oParsedResponse to 
47489 * the cache when appropriate before calling issueCallback().
47490 * 
47491 * The oParsedResponse object literal has the following properties:
47492 * <dl>
47493 *     <dd><dt>tId {Number}</dt> Unique transaction ID</dd>
47494 *     <dd><dt>results {Array}</dt> Array of parsed data results</dd>
47495 *     <dd><dt>meta {Object}</dt> Object literal of meta values</dd> 
47496 *     <dd><dt>error {Boolean}</dt> (optional) True if there was an error</dd>
47497 *     <dd><dt>cached {Boolean}</dt> (optional) True if response was cached</dd>
47498 * </dl>
47499 *
47500 * @method handleResponse
47501 * @param oRequest {Object} Request object
47502 * @param oRawResponse {Object} The raw response from the live database.
47503 * @param oCallback {Object} Callback object literal.
47504 * @param oCaller {Object} (deprecated) Use oCallback.scope.
47505 * @param tId {Number} Transaction ID.
47506 */
47507handleResponse : function(oRequest, oRawResponse, oCallback, oCaller, tId) {
47508    this.fireEvent("responseEvent", {tId:tId, request:oRequest, response:oRawResponse,
47509            callback:oCallback, caller:oCaller});
47510    YAHOO.log("Received live data response for \"" + oRequest + "\"", "info", this.toString());
47511    var xhr = (this.dataType == DS.TYPE_XHR) ? true : false;
47512    var oParsedResponse = null;
47513    var oFullResponse = oRawResponse;
47514    
47515    // Try to sniff data type if it has not been defined
47516    if(this.responseType === DS.TYPE_UNKNOWN) {
47517        var ctype = (oRawResponse && oRawResponse.getResponseHeader) ? oRawResponse.getResponseHeader["Content-Type"] : null;
47518        if(ctype) {
47519             // xml
47520            if(ctype.indexOf("text/xml") > -1) {
47521                this.responseType = DS.TYPE_XML;
47522            }
47523            else if(ctype.indexOf("application/json") > -1) { // json
47524                this.responseType = DS.TYPE_JSON;
47525            }
47526            else if(ctype.indexOf("text/plain") > -1) { // text
47527                this.responseType = DS.TYPE_TEXT;
47528            }
47529        }
47530        else {
47531            if(YAHOO.lang.isArray(oRawResponse)) { // array
47532                this.responseType = DS.TYPE_JSARRAY;
47533            }
47534             // xml
47535            else if(oRawResponse && oRawResponse.nodeType && (oRawResponse.nodeType === 9 || oRawResponse.nodeType === 1 || oRawResponse.nodeType === 11)) {
47536                this.responseType = DS.TYPE_XML;
47537            }
47538            else if(oRawResponse && oRawResponse.nodeName && (oRawResponse.nodeName.toLowerCase() == "table")) { // table
47539                this.responseType = DS.TYPE_HTMLTABLE;
47540            }    
47541            else if(YAHOO.lang.isObject(oRawResponse)) { // json
47542                this.responseType = DS.TYPE_JSON;
47543            }
47544            else if(YAHOO.lang.isString(oRawResponse)) { // text
47545                this.responseType = DS.TYPE_TEXT;
47546            }
47547        }
47548    }
47549
47550    switch(this.responseType) {
47551        case DS.TYPE_JSARRAY:
47552            if(xhr && oRawResponse && oRawResponse.responseText) {
47553                oFullResponse = oRawResponse.responseText; 
47554            }
47555            try {
47556                // Convert to JS array if it's a string
47557                if(lang.isString(oFullResponse)) {
47558                    var parseArgs = [oFullResponse].concat(this.parseJSONArgs);
47559                    // Check for YUI JSON Util
47560                    if(lang.JSON) {
47561                        oFullResponse = lang.JSON.parse.apply(lang.JSON,parseArgs);
47562                    }
47563                    // Look for JSON parsers using an API similar to json2.js
47564                    else if(window.JSON && JSON.parse) {
47565                        oFullResponse = JSON.parse.apply(JSON,parseArgs);
47566                    }
47567                    // Look for JSON parsers using an API similar to json.js
47568                    else if(oFullResponse.parseJSON) {
47569                        oFullResponse = oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));
47570                    }
47571                    // No JSON lib found so parse the string
47572                    else {
47573                        // Trim leading spaces
47574                        while (oFullResponse.length > 0 &&
47575                                (oFullResponse.charAt(0) != "{") &&
47576                                (oFullResponse.charAt(0) != "[")) {
47577                            oFullResponse = oFullResponse.substring(1, oFullResponse.length);
47578                        }
47579
47580                        if(oFullResponse.length > 0) {
47581                            // Strip extraneous stuff at the end
47582                            var arrayEnd =
47583Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));
47584                            oFullResponse = oFullResponse.substring(0,arrayEnd+1);
47585
47586                            // Turn the string into an object literal...
47587                            // ...eval is necessary here
47588                            oFullResponse = eval("(" + oFullResponse + ")");
47589
47590                        }
47591                    }
47592                }
47593            }
47594            catch(e1) {
47595            }
47596            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
47597            oParsedResponse = this.parseArrayData(oRequest, oFullResponse);
47598            break;
47599        case DS.TYPE_JSON:
47600            if(xhr && oRawResponse && oRawResponse.responseText) {
47601                oFullResponse = oRawResponse.responseText;
47602            }
47603            try {
47604                // Convert to JSON object if it's a string
47605                if(lang.isString(oFullResponse)) {
47606                    var parseArgs = [oFullResponse].concat(this.parseJSONArgs);
47607                    // Check for YUI JSON Util
47608                    if(lang.JSON) {
47609                        oFullResponse = lang.JSON.parse.apply(lang.JSON,parseArgs);
47610                    }
47611                    // Look for JSON parsers using an API similar to json2.js
47612                    else if(window.JSON && JSON.parse) {
47613                        oFullResponse = JSON.parse.apply(JSON,parseArgs);
47614                    }
47615                    // Look for JSON parsers using an API similar to json.js
47616                    else if(oFullResponse.parseJSON) {
47617                        oFullResponse = oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));
47618                    }
47619                    // No JSON lib found so parse the string
47620                    else {
47621                        // Trim leading spaces
47622                        while (oFullResponse.length > 0 &&
47623                                (oFullResponse.charAt(0) != "{") &&
47624                                (oFullResponse.charAt(0) != "[")) {
47625                            oFullResponse = oFullResponse.substring(1, oFullResponse.length);
47626                        }
47627    
47628                        if(oFullResponse.length > 0) {
47629                            // Strip extraneous stuff at the end
47630                            var objEnd = Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));
47631                            oFullResponse = oFullResponse.substring(0,objEnd+1);
47632    
47633                            // Turn the string into an object literal...
47634                            // ...eval is necessary here
47635                            oFullResponse = eval("(" + oFullResponse + ")");
47636    
47637                        }
47638                    }
47639                }
47640            }
47641            catch(e) {
47642            }
47643
47644            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
47645            oParsedResponse = this.parseJSONData(oRequest, oFullResponse);
47646            break;
47647        case DS.TYPE_HTMLTABLE:
47648            if(xhr && oRawResponse.responseText) {
47649                var el = document.createElement('div');
47650                el.innerHTML = oRawResponse.responseText;
47651                oFullResponse = el.getElementsByTagName('table')[0];
47652            }
47653            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
47654            oParsedResponse = this.parseHTMLTableData(oRequest, oFullResponse);
47655            break;
47656        case DS.TYPE_XML:
47657            if(xhr && oRawResponse.responseXML) {
47658                oFullResponse = oRawResponse.responseXML;
47659            }
47660            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
47661            oParsedResponse = this.parseXMLData(oRequest, oFullResponse);
47662            break;
47663        case DS.TYPE_TEXT:
47664            if(xhr && lang.isString(oRawResponse.responseText)) {
47665                oFullResponse = oRawResponse.responseText;
47666            }
47667            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
47668            oParsedResponse = this.parseTextData(oRequest, oFullResponse);
47669            break;
47670        default:
47671            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
47672            oParsedResponse = this.parseData(oRequest, oFullResponse);
47673            break;
47674    }
47675
47676
47677    // Clean up for consistent signature
47678    oParsedResponse = oParsedResponse || {};
47679    if(!oParsedResponse.results) {
47680        oParsedResponse.results = [];
47681    }
47682    if(!oParsedResponse.meta) {
47683        oParsedResponse.meta = {};
47684    }
47685
47686    // Success
47687    if(!oParsedResponse.error) {
47688        // Last chance to touch the raw response or the parsed response
47689        oParsedResponse = this.doBeforeCallback(oRequest, oFullResponse, oParsedResponse, oCallback);
47690        this.fireEvent("responseParseEvent", {request:oRequest,
47691                response:oParsedResponse, callback:oCallback, caller:oCaller});
47692        // Cache the response
47693        this.addToCache(oRequest, oParsedResponse);
47694    }
47695    // Error
47696    else {
47697        // Be sure the error flag is on
47698        oParsedResponse.error = true;
47699        this.fireEvent("dataErrorEvent", {request:oRequest, response: oRawResponse, callback:oCallback, 
47700                caller:oCaller, message:DS.ERROR_DATANULL});
47701        YAHOO.log(DS.ERROR_DATANULL, "error", this.toString());
47702    }
47703
47704    // Send the response back to the caller
47705    oParsedResponse.tId = tId;
47706    DS.issueCallback(oCallback,[oRequest,oParsedResponse],oParsedResponse.error,oCaller);
47707},
47708
47709/**
47710 * Overridable method gives implementers access to the original full response
47711 * before the data gets parsed. Implementers should take care not to return an
47712 * unparsable or otherwise invalid response.
47713 *
47714 * @method doBeforeParseData
47715 * @param oRequest {Object} Request object.
47716 * @param oFullResponse {Object} The full response from the live database.
47717 * @param oCallback {Object} The callback object.  
47718 * @return {Object} Full response for parsing.
47719  
47720 */
47721doBeforeParseData : function(oRequest, oFullResponse, oCallback) {
47722    return oFullResponse;
47723},
47724
47725/**
47726 * Overridable method gives implementers access to the original full response and
47727 * the parsed response (parsed against the given schema) before the data
47728 * is added to the cache (if applicable) and then sent back to callback function.
47729 * This is your chance to access the raw response and/or populate the parsed
47730 * response with any custom data.
47731 *
47732 * @method doBeforeCallback
47733 * @param oRequest {Object} Request object.
47734 * @param oFullResponse {Object} The full response from the live database.
47735 * @param oParsedResponse {Object} The parsed response to return to calling object.
47736 * @param oCallback {Object} The callback object. 
47737 * @return {Object} Parsed response object.
47738 */
47739doBeforeCallback : function(oRequest, oFullResponse, oParsedResponse, oCallback) {
47740    return oParsedResponse;
47741},
47742
47743/**
47744 * Overridable method parses data of generic RESPONSE_TYPE into a response object.
47745 *
47746 * @method parseData
47747 * @param oRequest {Object} Request object.
47748 * @param oFullResponse {Object} The full Array from the live database.
47749 * @return {Object} Parsed response object with the following properties:<br>
47750 *     - results {Array} Array of parsed data results<br>
47751 *     - meta {Object} Object literal of meta values<br>
47752 *     - error {Boolean} (optional) True if there was an error<br>
47753 */
47754parseData : function(oRequest, oFullResponse) {
47755    if(lang.isValue(oFullResponse)) {
47756        var oParsedResponse = {results:oFullResponse,meta:{}};
47757        YAHOO.log("Parsed generic data is " +
47758                lang.dump(oParsedResponse), "info", this.toString());
47759        return oParsedResponse;
47760
47761    }
47762    YAHOO.log("Generic data could not be parsed: " + lang.dump(oFullResponse), 
47763            "error", this.toString());
47764    return null;
47765},
47766
47767/**
47768 * Overridable method parses Array data into a response object.
47769 *
47770 * @method parseArrayData
47771 * @param oRequest {Object} Request object.
47772 * @param oFullResponse {Object} The full Array from the live database.
47773 * @return {Object} Parsed response object with the following properties:<br>
47774 *     - results (Array) Array of parsed data results<br>
47775 *     - error (Boolean) True if there was an error
47776 */
47777parseArrayData : function(oRequest, oFullResponse) {
47778    if(lang.isArray(oFullResponse)) {
47779        var results = [],
47780            i, j,
47781            rec, field, data;
47782        
47783        // Parse for fields
47784        if(lang.isArray(this.responseSchema.fields)) {
47785            var fields = this.responseSchema.fields;
47786            for (i = fields.length - 1; i >= 0; --i) {
47787                if (typeof fields[i] !== 'object') {
47788                    fields[i] = { key : fields[i] };
47789                }
47790            }
47791
47792            var parsers = {}, p;
47793            for (i = fields.length - 1; i >= 0; --i) {
47794                p = (typeof fields[i].parser === 'function' ?
47795                          fields[i].parser :
47796                          DS.Parser[fields[i].parser+'']) || fields[i].converter;
47797                if (p) {
47798                    parsers[fields[i].key] = p;
47799                }
47800            }
47801
47802            var arrType = lang.isArray(oFullResponse[0]);
47803            for(i=oFullResponse.length-1; i>-1; i--) {
47804                var oResult = {};
47805                rec = oFullResponse[i];
47806                if (typeof rec === 'object') {
47807                    for(j=fields.length-1; j>-1; j--) {
47808                        field = fields[j];
47809                        data = arrType ? rec[j] : rec[field.key];
47810
47811                        if (parsers[field.key]) {
47812                            data = parsers[field.key].call(this,data);
47813                        }
47814
47815                        // Safety measure
47816                        if(data === undefined) {
47817                            data = null;
47818                        }
47819
47820                        oResult[field.key] = data;
47821                    }
47822                }
47823                else if (lang.isString(rec)) {
47824                    for(j=fields.length-1; j>-1; j--) {
47825                        field = fields[j];
47826                        data = rec;
47827
47828                        if (parsers[field.key]) {
47829                            data = parsers[field.key].call(this,data);
47830                        }
47831
47832                        // Safety measure
47833                        if(data === undefined) {
47834                            data = null;
47835                        }
47836
47837                        oResult[field.key] = data;
47838                    }                
47839                }
47840                results[i] = oResult;
47841            }    
47842        }
47843        // Return entire data set
47844        else {
47845            results = oFullResponse;
47846        }
47847        var oParsedResponse = {results:results};
47848        YAHOO.log("Parsed array data is " +
47849                lang.dump(oParsedResponse), "info", this.toString());
47850        return oParsedResponse;
47851
47852    }
47853    YAHOO.log("Array data could not be parsed: " + lang.dump(oFullResponse), 
47854            "error", this.toString());
47855    return null;
47856},
47857
47858/**
47859 * Overridable method parses plain text data into a response object.
47860 *
47861 * @method parseTextData
47862 * @param oRequest {Object} Request object.
47863 * @param oFullResponse {Object} The full text response from the live database.
47864 * @return {Object} Parsed response object with the following properties:<br>
47865 *     - results (Array) Array of parsed data results<br>
47866 *     - error (Boolean) True if there was an error
47867 */
47868parseTextData : function(oRequest, oFullResponse) {
47869    if(lang.isString(oFullResponse)) {
47870        if(lang.isString(this.responseSchema.recordDelim) &&
47871                lang.isString(this.responseSchema.fieldDelim)) {
47872            var oParsedResponse = {results:[]};
47873            var recDelim = this.responseSchema.recordDelim;
47874            var fieldDelim = this.responseSchema.fieldDelim;
47875            if(oFullResponse.length > 0) {
47876                // Delete the last line delimiter at the end of the data if it exists
47877                var newLength = oFullResponse.length-recDelim.length;
47878                if(oFullResponse.substr(newLength) == recDelim) {
47879                    oFullResponse = oFullResponse.substr(0, newLength);
47880                }
47881                if(oFullResponse.length > 0) {
47882                    // Split along record delimiter to get an array of str
47882ings
47883                    var recordsarray = oFullResponse.split(recDelim);
47884                    // Cycle through each record
47885                    for(var i = 0, len = recordsarray.length, recIdx = 0; i < len; ++i) {
47886                        var bError = false,
47887                            sRecord = recordsarray[i];
47888                        if (lang.isString(sRecord) && (sRecord.length > 0)) {
47889                            // Split each record along field delimiter to get data
47890                            var fielddataarray = recordsarray[i].split(fieldDelim);
47891                            var oResult = {};
47892                            
47893                            // Filter for fields data
47894                            if(lang.isArray(this.responseSchema.fields)) {
47895                                var fields = this.responseSchema.fields;
47896                                for(var j=fields.length-1; j>-1; j--) {
47897                                    try {
47898                                        // Remove quotation marks from edges, if applicable
47899                                        var data = fielddataarray[j];
47900                                        if (lang.isString(data)) {
47901                                            if(data.charAt(0) == "\"") {
47902                                                data = data.substr(1);
47903                                            }
47904                                            if(data.charAt(data.length-1) == "\"") {
47905                                                data = data.substr(0,data.length-1);
47906                                            }
47907                                            var field = fields[j];
47908                                            var key = (lang.isValue(field.key)) ? field.key : field;
47909                                            // Backward compatibility
47910                                            if(!field.parser && field.converter) {
47911                                                field.parser = field.converter;
47912                                                YAHOO.log("The field property converter has been deprecated" +
47913                                                        " in favor of parser", "warn", this.toString());
47914                                            }
47915                                            var parser = (typeof field.parser === 'function') ?
47916                                                field.parser :
47917                                                DS.Parser[field.parser+''];
47918                                            if(parser) {
47919                                                data = parser.call(this, data);
47920                                            }
47921                                            // Safety measure
47922                                            if(data === undefined) {
47923                                                data = null;
47924                                            }
47925                                            oResult[key] = data;
47926                                        }
47927                                        else {
47928                                            bError = true;
47929                                        }
47930                                    }
47931                                    catch(e) {
47932                                        bError = true;
47933                                    }
47934                                }
47935                            }            
47936                            // No fields defined so pass along all data as an array
47937                            else {
47938                                oResult = fielddataarray;
47939                            }
47940                            if(!bError) {
47941                                oParsedResponse.results[recIdx++] = oResult;
47942                            }
47943                        }
47944                    }
47945                }
47946            }
47947            YAHOO.log("Parsed text data is " +
47948                    lang.dump(oParsedResponse), "info", this.toString());
47949            return oParsedResponse;
47950        }
47951    }
47952    YAHOO.log("Text data could not be parsed: " + lang.dump(oFullResponse), 
47953            "error", this.toString());
47954    return null;
47955            
47956},
47957
47958/**
47959 * Overridable method parses XML data for one result into an object literal.
47960 *
47961 * @method parseXMLResult
47962 * @param result {XML} XML for one result.
47963 * @return {Object} Object literal of data for one result.
47964 */
47965parseXMLResult : function(result) {
47966    var oResult = {},
47967        schema = this.responseSchema;
47968        
47969    try {
47970        // Loop through each data field in each result using the schema
47971        for(var m = schema.fields.length-1; m >= 0 ; m--) {
47972            var field = schema.fields[m];
47973            var key = (lang.isValue(field.key)) ? field.key : field;
47974            var data = null;
47975
47976            if(this.useXPath) {
47977                data = YAHOO.util.DataSource._getLocationValue(field, result);
47978            }
47979            else {
47980                // Values may be held in an attribute...
47981                var xmlAttr = result.attributes.getNamedItem(key);
47982                if(xmlAttr) {
47983                    data = xmlAttr.value;
47984                }
47985                // ...or in a node
47986                else {
47987                    var xmlNode = result.getElementsByTagName(key);
47988                    if(xmlNode && xmlNode.item(0)) {
47989                        var item = xmlNode.item(0);
47990                        // For IE, then DOM...
47991                        data = (item) ? ((item.text) ? item.text : (item.textContent) ? item.textContent : null) : null;
47992                        // ...then fallback, but check for multiple child nodes
47993                        if(!data) {
47994                            var datapieces = [];
47995                            for(var j=0, len=item.childNodes.length; j<len; j++) {
47996                                if(item.childNodes[j].nodeValue) {
47997                                    datapieces[datapieces.length] = item.childNodes[j].nodeValue;
47998                                }
47999                            }
48000                            if(datapieces.length > 0) {
48001                                data = datapieces.join("");
48002                            }
48003                        }
48004                    }
48005                }
48006            }
48007            
48008            
48009            // Safety net
48010            if(data === null) {
48011                   data = "";
48012            }
48013            // Backward compatibility
48014            if(!field.parser && field.converter) {
48015                field.parser = field.converter;
48016                YAHOO.log("The field property converter has been deprecated" +
48017                        " in favor of parser", "warn", this.toString());
48018            }
48019            var parser = (typeof field.parser === 'function') ?
48020                field.parser :
48021                DS.Parser[field.parser+''];
48022            if(parser) {
48023                data = parser.call(this, data);
48024            }
48025            // Safety measure
48026            if(data === undefined) {
48027                data = null;
48028            }
48029            oResult[key] = data;
48030        }
48031    }
48032    catch(e) {
48033        YAHOO.log("Error while parsing XML result: " + e.message);
48034    }
48035
48036    return oResult;
48037},
48038
48039
48040
48041/**
48042 * Overridable method parses XML data into a response object.
48043 *
48044 * @method parseXMLData
48045 * @param oRequest {Object} Request object.
48046 * @param oFullResponse {Object} The full XML response from the live database.
48047 * @return {Object} Parsed response object with the following properties<br>
48048 *     - results (Array) Array of parsed data results<br>
48049 *     - error (Boolean) True if there was an error
48050 */
48051parseXMLData : function(oRequest, oFullResponse) {
48052    var bError = false,
48053        schema = this.responseSchema,
48054        oParsedResponse = {meta:{}},
48055        xmlList = null,
48056        metaNode      = schema.metaNode,
48057        metaLocators  = schema.metaFields || {},
48058        i,k,loc,v;
48059
48060    // In case oFullResponse is something funky
48061    try {
48062        // Pull any meta identified
48063        if(this.useXPath) {
48064            for (k in metaLocators) {
48065                oParsedResponse.meta[k] = YAHOO.util.DataSource._getLocationValue(metaLocators[k], oFullResponse);
48066            }
48067        }
48068        else {
48069            metaNode = metaNode ? oFullResponse.getElementsByTagName(metaNode)[0] :
48070                       oFullResponse;
48071
48072            if (metaNode) {
48073                for (k in metaLocators) {
48074                    if (lang.hasOwnProperty(metaLocators, k)) {
48075                        loc = metaLocators[k];
48076                        // Look for a node
48077                        v = metaNode.getElementsByTagName(loc)[0];
48078
48079                        if (v) {
48080                            v = v.firstChild.nodeValue;
48081                        } else {
48082                            // Look for an attribute
48083                            v = metaNode.attributes.getNamedItem(loc);
48084                            if (v) {
48085                                v = v.value;
48086                            }
48087                        }
48088
48089                        if (lang.isValue(v)) {
48090                            oParsedResponse.meta[k] = v;
48091                        }
48092                    }
48093                }
48094            }
48095        }
48096        
48097        // For result data
48098        xmlList = (schema.resultNode) ?
48099            oFullResponse.getElementsByTagName(schema.resultNode) :
48100            null;
48101    }
48102    catch(e) {
48103        YAHOO.log("Error while parsing XML data: " + e.message);
48104    }
48105    if(!xmlList || !lang.isArray(schema.fields)) {
48106        bError = true;
48107    }
48108    // Loop through each result
48109    else {
48110        oParsedResponse.results = [];
48111        for(i = xmlList.length-1; i >= 0 ; --i) {
48112            var oResult = this.parseXMLResult(xmlList.item(i));
48113            // Capture each array of values into an array of results
48114            oParsedResponse.results[i] = oResult;
48115        }
48116    }
48117    if(bError) {
48118        YAHOO.log("XML data could not be parsed: " +
48119                lang.dump(oFullResponse), "error", this.toString());
48120        oParsedResponse.error = true;
48121    }
48122    else {
48123        YAHOO.log("Parsed XML data is " +
48124                lang.dump(oParsedResponse), "info", this.toString());
48125    }
48126    return oParsedResponse;
48127},
48128
48129/**
48130 * Overridable method parses JSON data into a response object.
48131 *
48132 * @method parseJSONData
48133 * @param oRequest {Object} Request object.
48134 * @param oFullResponse {Object} The full JSON from the live database.
48135 * @return {Object} Parsed response object with the following properties<br>
48136 *     - results (Array) Array of parsed data results<br>
48137 *     - error (Boolean) True if there was an error
48138 */
48139parseJSONData : function(oRequest, oFullResponse) {
48140    var oParsedResponse = {results:[],meta:{}};
48141    
48142    if(lang.isObject(oFullResponse) && this.responseSchema.resultsList) {
48143        var schema = this.responseSchema,
48144            fields          = schema.fields,
48145            resultsList     = oFullResponse,
48146            results         = [],
48147            metaFields      = schema.metaFields || {},
48148            fieldParsers    = [],
48149            fieldPaths      = [],
48150            simpleFields    = [],
48151            bError          = false,
48152            i,len,j,v,key,parser,path;
48153
48154        // Function to convert the schema's fields into walk paths
48155        var buildPath = function (needle) {
48156            var path = null, keys = [], i = 0;
48157            if (needle) {
48158                // Strip the ["string keys"] and [1] array indexes
48159                needle = needle.
48160                    replace(/\[(['"])(.*?)\1\]/g,
48161                    function (x,$1,$2) {keys[i]=$2;return '.@'+(i++);}).
48162                    replace(/\[(\d+)\]/g,
48163                    function (x,$1) {keys[i]=parseInt($1,10)|0;return '.@'+(i++);}).
48164                    replace(/^\./,''); // remove leading dot
48165
48166                // If the cleaned needle contains invalid characters, the
48167                // path is invalid
48168                if (!/[^\w\.\$@]/.test(needle)) {
48169                    path = needle.split('.');
48170                    for (i=path.length-1; i >= 0; --i) {
48171                        if (path[i].charAt(0) === '@') {
48172                            path[i] = keys[parseInt(path[i].substr(1),10)];
48173                        }
48174                    }
48175                }
48176                else {
48177                    YAHOO.log("Invalid locator: " + needle, "error", this.toString());
48178                }
48179            }
48180            return path;
48181        };
48182
48183
48184        // Function to walk a path and return the pot of gold
48185        var walkPath = function (path, origin) {
48186            var v=origin,i=0,len=path.length;
48187            for (;i<len && v;++i) {
48188                v = v[path[i]];
48189            }
48190            return v;
48191        };
48192
48193        // Parse the response
48194        // Step 1. Pull the resultsList from oFullResponse (default assumes
48195        // oFullResponse IS the resultsList)
48196        path = buildPath(schema.resultsList);
48197        if (path) {
48198            resultsList = walkPath(path, oFullResponse);
48199            if (resultsList === undefined) {
48200                bError = true;
48201            }
48202        } else {
48203            bError = true;
48204        }
48205        
48206        if (!resultsList) {
48207            resultsList = [];
48208        }
48209
48210        if (!lang.isArray(resultsList)) {
48211            resultsList = [resultsList];
48212        }
48213
48214        if (!bError) {
48215            // Step 2. Parse out field data if identified
48216            if(schema.fields) {
48217                var field;
48218                // Build the field parser map and location paths
48219                for (i=0, len=fields.length; i<len; i++) {
48220                    field = fields[i];
48221                    key    = field.key || field;
48222                    parser = ((typeof field.parser === 'function') ?
48223                        field.parser :
48224                        DS.Parser[field.parser+'']) || field.converter;
48225                    path   = buildPath(key);
48226    
48227                    if (parser) {
48228                        fieldParsers[fieldParsers.length] = {key:key,parser:parser};
48229                    }
48230    
48231                    if (path) {
48232                        if (path.length > 1) {
48233                            fieldPaths[fieldPaths.length] = {key:key,path:path};
48234                        } else {
48235                            simpleFields[simpleFields.length] = {key:key,path:path[0]};
48236                        }
48237                    } else {
48238                        YAHOO.log("Invalid key syntax: " + key,"warn",this.toString());
48239                    }
48240                }
48241
48242                // Process the results, flattening the records and/or applying parsers if needed
48243                for (i = resultsList.length - 1; i >= 0; --i) {
48244                    var r = resultsList[i], rec = {};
48245                    if(r) {
48246                        for (j = simpleFields.length - 1; j >= 0; --j) {
48247                            // Bug 1777850: data might be held in an array
48248                            rec[simpleFields[j].key] =
48249                                    (r[simpleFields[j].path] !== undefined) ?
48250                                    r[simpleFields[j].path] : r[j];
48251                        }
48252
48253                        for (j = fieldPaths.length - 1; j >= 0; --j) {
48254                            rec[fieldPaths[j].key] = walkPath(fieldPaths[j].path,r);
48255                        }
48256
48257                        for (j = fieldParsers.length - 1; j >= 0; --j) {
48258                            var p = fieldParsers[j].key;
48259                            rec[p] = fieldParsers[j].parser.call(this, rec[p]);
48260                            if (rec[p] === undefined) {
48261                                rec[p] = null;
48262                            }
48263                        }
48264                    }
48265                    results[i] = rec;
48266                }
48267            }
48268            else {
48269                results = resultsList;
48270            }
48271
48272            for (key in metaFields) {
48273                if (lang.hasOwnProperty(metaFields,key)) {
48274                    path = buildPath(metaFields[key]);
48275                    if (path) {
48276                        v = walkPath(path, oFullResponse);
48277                        oParsedResponse.meta[key] = v;
48278                    }
48279                }
48280            }
48281
48282        } else {
48283            YAHOO.log("JSON data could not be parsed due to invali
48283d responseSchema.resultsList or invalid response: " +
48284                    lang.dump(oFullResponse), "error", this.toString());
48285
48286            oParsedResponse.error = true;
48287        }
48288
48289        oParsedResponse.results = results;
48290    }
48291    else {
48292        YAHOO.log("JSON data could not be parsed: " +
48293                lang.dump(oFullResponse), "error", this.toString());
48294        oParsedResponse.error = true;
48295    }
48296
48297    return oParsedResponse;
48298},
48299
48300/**
48301 * Overridable method parses an HTML TABLE element reference into a response object.
48302 * Data is parsed out of TR elements from all TBODY elements. 
48303 *
48304 * @method parseHTMLTableData
48305 * @param oRequest {Object} Request object.
48306 * @param oFullResponse {Object} The full HTML element reference from the live database.
48307 * @return {Object} Parsed response object with the following properties<br>
48308 *     - results (Array) Array of parsed data results<br>
48309 *     - error (Boolean) True if there was an error
48310 */
48311parseHTMLTableData : function(oRequest, oFullResponse) {
48312    var bError = false;
48313    var elTable = oFullResponse;
48314    var fields = this.responseSchema.fields;
48315    var oParsedResponse = {results:[]};
48316
48317    if(lang.isArray(fields)) {
48318        // Iterate through each TBODY
48319        for(var i=0; i<elTable.tBodies.length; i++) {
48320            var elTbody = elTable.tBodies[i];
48321    
48322            // Iterate through each TR
48323            for(var j=elTbody.rows.length-1; j>-1; j--) {
48324                var elRow = elTbody.rows[j];
48325                var oResult = {};
48326                
48327                for(var k=fields.length-1; k>-1; k--) {
48328                    var field = fields[k];
48329                    var key = (lang.isValue(field.key)) ? field.key : field;
48330                    var data = elRow.cells[k].innerHTML;
48331    
48332                    // Backward compatibility
48333                    if(!field.parser && field.converter) {
48334                        field.parser = field.converter;
48335                        YAHOO.log("The field property converter has been deprecated" +
48336                                " in favor of parser", "warn", this.toString());
48337                    }
48338                    var parser = (typeof field.parser === 'function') ?
48339                        field.parser :
48340                        DS.Parser[field.parser+''];
48341                    if(parser) {
48342                        data = parser.call(this, data);
48343                    }
48344                    // Safety measure
48345                    if(data === undefined) {
48346                        data = null;
48347                    }
48348                    oResult[key] = data;
48349                }
48350                oParsedResponse.results[j] = oResult;
48351            }
48352        }
48353    }
48354    else {
48355        bError = true;
48356        YAHOO.log("Invalid responseSchema.fields", "error", this.toString());
48357    }
48358
48359    if(bError) {
48360        YAHOO.log("HTML TABLE data could not be parsed: " +
48361                lang.dump(oFullResponse), "error", this.toString());
48362        oParsedResponse.error = true;
48363    }
48364    else {
48365        YAHOO.log("Parsed HTML TABLE data is " +
48366                lang.dump(oParsedResponse), "info", this.toString());
48367    }
48368    return oParsedResponse;
48369}
48370
48371};
48372
48373// DataSourceBase uses EventProvider
48374lang.augmentProto(DS, util.EventProvider);
48375
48376
48377
48378/****************************************************************************/
48379/****************************************************************************/
48380/****************************************************************************/
48381
48382/**
48383 * LocalDataSource class for in-memory data structs including JavaScript arrays,
48384 * JavaScript object literals (JSON), XML documents, and HTML tables.
48385 *
48386 * @namespace YAHOO.util
48387 * @class YAHOO.util.LocalDataSource
48388 * @extends YAHOO.util.DataSourceBase 
48389 * @constructor
48390 * @param oLiveData {HTMLElement}  Pointer to live data.
48391 * @param oConfigs {object} (optional) Object literal of configuration values.
48392 */
48393util.LocalDataSource = function(oLiveData, oConfigs) {
48394    this.dataType = DS.TYPE_LOCAL;
48395    
48396    if(oLiveData) {
48397        if(YAHOO.lang.isArray(oLiveData)) { // array
48398            this.responseType = DS.TYPE_JSARRAY;
48399        }
48400         // xml
48401        else if(oLiveData.nodeType && oLiveData.nodeType == 9) {
48402            this.responseType = DS.TYPE_XML;
48403        }
48404        else if(oLiveData.nodeName && (oLiveData.nodeName.toLowerCase() == "table")) { // table
48405            this.responseType = DS.TYPE_HTMLTABLE;
48406            oLiveData = oLiveData.cloneNode(true);
48407        }    
48408        else if(YAHOO.lang.isString(oLiveData)) { // text
48409            this.responseType = DS.TYPE_TEXT;
48410        }
48411        else if(YAHOO.lang.isObject(oLiveData)) { // json
48412            this.responseType = DS.TYPE_JSON;
48413        }
48414    }
48415    else {
48416        oLiveData = [];
48417        this.responseType = DS.TYPE_JSARRAY;
48418    }
48419    
48420    util.LocalDataSource.superclass.constructor.call(this, oLiveData, oConfigs); 
48421};
48422
48423// LocalDataSource extends DataSourceBase
48424lang.extend(util.LocalDataSource, DS);
48425
48426// Copy static members to LocalDataSource class
48427lang.augmentObject(util.LocalDataSource, DS);
48428
48429
48430
48431
48432
48433
48434
48435
48436
48437
48438
48439
48440
48441/****************************************************************************/
48442/****************************************************************************/
48443/****************************************************************************/
48444
48445/**
48446 * FunctionDataSource class for JavaScript functions.
48447 *
48448 * @namespace YAHOO.util
48449 * @class YAHOO.util.FunctionDataSource
48450 * @extends YAHOO.util.DataSourceBase  
48451 * @constructor
48452 * @param oLiveData {HTMLElement}  Pointer to live data.
48453 * @param oConfigs {object} (optional) Object literal of configuration values.
48454 */
48455util.FunctionDataSource = function(oLiveData, oConfigs) {
48456    this.dataType = DS.TYPE_JSFUNCTION;
48457    oLiveData = oLiveData || function() {};
48458    
48459    util.FunctionDataSource.superclass.constructor.call(this, oLiveData, oConfigs); 
48460};
48461
48462// FunctionDataSource extends DataSourceBase
48463lang.extend(util.FunctionDataSource, DS, {
48464
48465/////////////////////////////////////////////////////////////////////////////
48466//
48467// FunctionDataSource public properties
48468//
48469/////////////////////////////////////////////////////////////////////////////
48470
48471/**
48472 * Context in which to execute the function. By default, is the DataSource
48473 * instance itself. If set, the function will receive the DataSource instance
48474 * as an additional argument. 
48475 *
48476 * @property scope
48477 * @type Object
48478 * @default null
48479 */
48480scope : null,
48481
48482
48483/////////////////////////////////////////////////////////////////////////////
48484//
48485// FunctionDataSource public methods
48486//
48487/////////////////////////////////////////////////////////////////////////////
48488
48489/**
48490 * Overriding method passes query to a function. The returned response is then
48491 * forwarded to the handleResponse function.
48492 *
48493 * @method makeConnection
48494 * @param oRequest {Object} Request object.
48495 * @param oCallback {Object} Callback object literal.
48496 * @param oCaller {Object} (deprecated) Use oCallback.scope.
48497 * @return {Number} Transaction ID.
48498 */
48499makeConnection : function(oRequest, oCallback, oCaller) {
48500    var tId = DS._nTransactionId++;
48501    this.fireEvent("requestEvent", {tId:tId,request:oRequest,callback:oCallback,caller:oCaller});
48502
48503    // Pass the request in as a parameter and
48504    // forward the return value to the handler
48505    
48506    
48507    var oRawResponse = (this.scope) ? this.liveData.call(this.scope, oRequest, this, oCallback) : this.liveData(oRequest, oCallback);
48508    
48509    // Try to sniff data type if it has not been defined
48510    if(this.responseType === DS.TYPE_UNKNOWN) {
48511        if(YAHOO.lang.isArray(oRawResponse)) { // array
48512            this.responseType = DS.TYPE_JSARRAY;
48513        }
48514         // xml
48515        else if(oRawResponse && oRawResponse.nodeType && oRawResponse.nodeType == 9) {
48516            this.responseType = DS.TYPE_XML;
48517        }
48518        else if(oRawResponse && oRawResponse.nodeName && (oRawResponse.nodeName.toLowerCase() == "table")) { // table
48519            this.responseType = DS.TYPE_HTMLTABLE;
48520        }    
48521        else if(YAHOO.lang.isObject(oRawResponse)) { // json
48522            this.responseType = DS.TYPE_JSON;
48523        }
48524        else if(YAHOO.lang.isString(oRawResponse)) { // text
48525            this.responseType = DS.TYPE_TEXT;
48526        }
48527    }
48528
48529    this.handleResponse(oRequest, oRawResponse, oCallback, oCaller, tId);
48530    return tId;
48531}
48532
48533});
48534
48535// Copy static members to FunctionDataSource class
48536lang.augmentObject(util.FunctionDataSource, DS);
48537
48538
48539
48540
48541
48542
48543
48544
48545
48546
48547
48548
48549
48550/****************************************************************************/
48551/****************************************************************************/
48552/****************************************************************************/
48553
48554/**
48555 * ScriptNodeDataSource class for accessing remote data via the YUI Get Utility. 
48556 *
48557 * @namespace YAHOO.util
48558 * @class YAHOO.util.ScriptNodeDataSource
48559 * @extends YAHOO.util.DataSourceBase  
48560 * @constructor
48561 * @param oLiveData {HTMLElement}  Pointer to live data.
48562 * @param oConfigs {object} (optional) Object literal of configuration values.
48563 */
48564util.ScriptNodeDataSource = function(oLiveData, oConfigs) {
48565    this.dataType = DS.TYPE_SCRIPTNODE;
48566    oLiveData = oLiveData || "";
48567    
48568    util.ScriptNodeDataSource.superclass.constructor.call(this, oLiveData, oConfigs); 
48569};
48570
48571// ScriptNodeDataSource extends DataSourceBase
48572lang.extend(util.ScriptNodeDataSource, DS, {
48573
48574/////////////////////////////////////////////////////////////////////////////
48575//
48576// ScriptNodeDataSource public properties
48577//
48578/////////////////////////////////////////////////////////////////////////////
48579
48580/**
48581 * Alias to YUI Get Utility, to allow implementers to use a custom class.
48582 *
48583 * @property getUtility
48584 * @type Object
48585 * @default YAHOO.util.Get
48586 */
48587getUtility : util.Get,
48588
48589/**
48590 * Defines request/response management in the following manner:
48591 * <dl>
48592 *     <!--<dt>queueRequests</dt>
48593 *     <dd>If a request is already in progress, wait until response is returned before sending the next request.</dd>
48594 *     <dt>cancelStaleRequests</dt>
48595 *     <dd>If a request is already in progress, cancel it before sending the next request.</dd>-->
48596 *     <dt>ignoreStaleResponses</dt>
48597 *     <dd>Send all requests, but handle only the response for the most recently sent request.</dd>
48598 *     <dt>allowAll</dt>
48599 *     <dd>Send all requests and handle all responses.</dd>
48600 * </dl>
48601 *
48602 * @property asyncMode
48603 * @type String
48604 * @default "allowAll"
48605 */
48606asyncMode : "allowAll",
48607
48608/**
48609 * Callback string parameter name sent to the remote script. By default,
48610 * requests are sent to
48611 * &#60;URI&#62;?&#60;scriptCallbackParam&#62;=callback
48612 *
48613 * @property scriptCallbackParam
48614 * @type String
48615 * @default "callback"
48616 */
48617scriptCallbackParam : "callback",
48618
48619
48620/////////////////////////////////////////////////////////////////////////////
48621//
48622// ScriptNodeDataSource public methods
48623//
48624/////////////////////////////////////////////////////////////////////////////
48625
48626/**
48627 * Creates a request callback that gets appended to the script URI. Implementers
48628 * can customize this string to match their server's query syntax.
48629 *
48630 * @method generateRequestCallback
48631 * @return {String} String fragment that gets appended to script URI that 
48632 * specifies the callback function 
48633 */
48634generateRequestCallback : function(id) {
48635    return "&" + this.scriptCallbackParam + "=YAHOO.util.ScriptNodeDataSource.callbacks["+id+"]" ;
48636},
48637
48638/**
48639 * Overridable method gives implementers access to modify the URI before the dynamic
48640 * script node gets inserted. Implementers should take care not to return an
48641 * invalid URI.
48642 *
48643 * @method doBeforeGetScriptNode
48644 * @param {String} URI to the script 
48645 * @return {String} URI to the script
48646 */
48647doBeforeGetScriptNode : function(sUri) {
48648    return sUri;
48649},
48650
48651/**
48652 * Overriding method passes query to Get Utility. The returned
48653 * response is then forwarded to the handleResponse function.
48654 *
48655 * @method makeConnection
48656 * @param oRequest {Object} Request object.
48657 * @param oCallback {Object} Callback object literal.
48658 * @param oCaller {Object} (deprecated) Use oCallback.scope.
48659 * @return {Number} Transaction ID.
48660 */
48661makeConnection : function(oRequest, oCallback, oCaller) {
48662    var tId = DS._nTransactionId++;
48663    this.fireEvent("requestEvent", {tId:tId,request:oRequest,callback:oCallback,caller:oCaller});
48664    
48665    // If there are no global pending requests, it is safe to purge global callback stack and global counter
48666    if(util.ScriptNodeDataSource._nPending === 0) {
48667        util.ScriptNodeDataSource.callbacks = [];
48668        util.ScriptNodeDataSource._nId = 0;
48669    }
48670    
48671    // ID for this request
48672    var id = util.ScriptNodeDataSource._nId;
48673    util.ScriptNodeDataSource._nId++;
48674    
48675    // Dynamically add handler function with a closure to the callback stack
48676    var oSelf = this;
48677    util.ScriptNodeDataSource.callbacks[id] = function(oRawResponse) {
48678        if((oSelf.asyncMode !== "ignoreStaleResponses")||
48679                (id === util.ScriptNodeDataSource.callbacks.length-1)) { // Must ignore stale responses
48680                
48681            // Try to sniff data type if it has not been defined
48682            if(oSelf.responseType === DS.TYPE_UNKNOWN) {
48683                if(YAHOO.lang.isArray(oRawResponse)) { // array
48684                    oSelf.responseType = DS.TYPE_JSARRAY;
48685                }
48686                 // xml
48687                else if(oRawResponse.nodeType && oRawResponse.nodeType == 9) {
48688                    oSelf.responseType = DS.TYPE_XML;
48689                }
48690                else if(oRawResponse.nodeName && (oRawResponse.nodeName.toLowerCase() == "table")) { // table
48691                    oSelf.responseType = DS.TYPE_HTMLTABLE;
48692                }    
48693                else if(YAHOO.lang.isObject(oRawResponse)) { // json
48694                    oSelf.responseType = DS.TYPE_JSON;
48695                }
48696                else if(YAHOO.lang.isString(oRawResponse)) { // text
48697                    oSelf.responseType = DS.TYPE_TEXT;
48698                }
48699            }
48700
48701            oSelf.handleResponse(oRequest, oRawResponse, oCallback, oCaller, tId);
48702        }
48703        else {
48704            YAHOO.log("DataSource ignored stale response for tId " + tId + "(" + oRequest + ")", "info", oSelf.toString());
48705        }
48706    
48707        delete util.ScriptNodeDataSource.callbacks[id];
48708    };
48709    
48710    // We are now creating a request
48711    util.ScriptNodeDataSource._nPending++;
48712    var sUri = this.liveData + oRequest + this.generateRequestCallback(id);
48713    sUri = this.doBeforeGetScriptNode(sUri);
48714    YAHOO.log("DataSource is querying URL " + sUri, "info", this.toString());
48715    this.getUtility.script(sUri,
48716            {autopurge: true,
48717            onsuccess: util.ScriptNodeDataSource._bumpPendingDown,
48718            onfail: util.ScriptNodeDataSource._bumpPendingDown});
48719
48720    return tId;
48721}
48722
48723});
48724
48725// Copy static members to ScriptNodeDataSource class
48726lang.augmentObject(util.ScriptNodeDataSource, DS);
48727
48728// Copy static members to ScriptNodeDataSource class
48729lang.augmentObject(util.ScriptNodeDataSource,  {
48730
48731/////////////////////////////////////////////////////////////////////////////
48732//
48733// ScriptNodeDataSource private static properties
48734//
48735/////////////////////////////////////////////////////////////////////////////
48736
48737/**
48738 * Unique ID to track requests.
48739 *
48740 * @property _nId
48741 * @type Number
48742 * @private
48743 * @static
48744 */
48745_nId : 0,
48746
48747/**
48748 * Counter for pending requests. When this is 0, it is safe to purge callbacks
48749 * array.
48750 *
48751 * @property _nPending
48752 * @type Number
48753 * @private
48754 * @static
48755 */
48756_nPending : 0,
48757
48758/**
48759 * Global array of callback functions, one for each request sent.
48760 *
48761 * @property callbacks
48762 * @type Function[]
48763 * @static
48764 */
48765callbacks : []
48766
48767});
48768
48769
48770
48771
48772
48773
48774
48775
48776
48777
48778
48779
48780
48781
48782/****************************************************************************/
48783/****************************************************************************/
48784/****************************************************************************/
48785
48786/**
48787 * XHRDataSource class for accessing remote data via the YUI Connection Manager
48788 * Utility
48789 *
48790 * @namespace YAHOO.util
48791 * @class YAHOO.util.XHRDataSource
48792 * @extends YAHOO.util.DataSourceBase  
48793 * @constructor
48794 * @param oLiveData {HTMLElement}  Pointer to live data.
48795 * @param oConfigs {object} (optional) Object literal of configuration values.
48796 */
48797util.XHRDataSource = function(oLiveData, oConfigs) {
48798    this.dataType = DS.TYPE_XHR;
48799    this.connMgr = this.connMgr || util.Connect;
48800    oLiveData = oLiveData || "";
48801    
48802    util.XHRDataSource.superclass.constructor.call(this, oLiveData, oConfigs); 
48803};
48804
48805// XHRDataSource extends DataSourceBase
48806lang.extend(util.XHRDataSource, DS, {
48807
48808/////////////////////////////////////////////////////////////////////////////
48809//
48810// XHRDataSource public properties
48811//
48812/////////////////////////////////////////////////////////////////////////////
48813
48814 /**
48815 * Alias to YUI Connection Manager, to allow implementers to use a custom class.
48816 *
48817 * @property connMgr
48818 * @type Object
48819 * @default YAHOO.util.Connect
48820 */
48821connMgr: null,
48822
48823 /**
48824 * Defines request/response management in the following manner:
48825 * <dl>
48826 *     <dt>queueRequests</dt>
48827 *     <dd>If a request is already in progress, wait until response is returned
48828 *     before sending the next request.</dd>
48829 *
48830 *     <dt>cancelStaleRequests</dt>
48831 *     <dd>If a request is already in progress, cancel it before sending the next
48832 *     request.</dd>
48833 *
48834 *     <dt>ignoreStaleResponses</dt>
48835 *     <dd>Send all requests, but handle only the response for the most recently
48836 *     sent request.</dd>
48837 *
48838 *     <dt>allowAll</dt>
48839 *     <dd>Send all requests and handle all responses.</dd>
48840 *
48841 * </dl>
48842 *
48843 * @property connXhrMode
48844 * @type String
48845 * @default "allowAll"
48846 */
48847connXhrMode: "allowAll",
48848
48849 /**
48850 * True if data is to be sent via POST. By default, data will be sent via GET.
48851 *
48852 * @property connMethodPost
48853 * @type Boolean
48854 * @default false
48855 */
48856connMethodPost: false,
48857
48858 /**
48859 * The connection timeout defines how many  milliseconds the XHR connection will
48860 * wait for a server response. Any non-zero value will enable the Connection Manager's
48861 * Auto-Abort feature.
48862 *
48863 * @property connTimeout
48864 * @type Number
48865 * @default 0
48866 */
48867connTimeout: 0,
48868
48869/////////////////////////////////////////////////////////////////////////////
48870//
48871// XHRDataSource public methods
48872//
48873/////////////////////////////////////////////////////////////////////////////
48874
48875/**
48876 * Overriding method passes query to Connection Manager. The returned
48877 * response is then forwarded to the handleResponse function.
48878 *
48879 * @method makeConnection
48880 * @param oRequest {Object} Request object.
48881 * @param oCallback {Object} Callback object literal.
48882 * @param oCaller {Object} (deprecated) Use oCallback.scope.
48883 * @return {Number} Transaction ID.
48884 */
48885makeConnection : function(oRequest, oCallback, oCaller) {
48886
48887    var oRawResponse = null;
48888    var tId = DS._nTransactionId++;
48889    this.fireEvent("requestEvent", {tId:tId,request:oRequest,callback:oCallback,caller:oCaller});
48890
48891    // Set up the callback object and
48892    // pass the request in as a URL query and
48893    // forward the response to the handler
48894    var oSelf = this;
48895    var oConnMgr = this.connMgr;
48896    var oQueue = this._oQueue;
48897
48898    /**
48899     * Define Connection Manager success handler
48900     *
48901     * @method _xhrSuccess
48902     * @param oResponse {Object} HTTPXMLRequest object
48903     * @private
48904     */
48905    var _xhrSuccess = function(oResponse) {
48906        // If response ID does not match last made request ID,
48907        // silently fail and wait for the next response
48908        if(oResponse && (this.connXhrMode == "ignoreStaleResponses") &&
48909                (oResponse.tId != oQueue.conn.tId)) {
48910            YAHOO.log("Ignored stale response", "warn", this.toString());
48911            return null;
48912        }
48913        // Error if no response
48914        else if(!oResponse) {
48915            this.fireEvent("dataErrorEvent", {request:oRequest, response:null,
48916                    callback:oCallback, caller:oCaller,
48917                    message:DS.ERROR_DATANULL});
48918            YAHOO.log(DS.ERROR_DATANULL, "error", this.toString());
48919
48920            // Send error response back to the caller with the error flag on
48921            DS.issueCallback(oCallback,[oRequest, {error:true}], true, oCaller);
48922
48923            return null;
48924        }
48925        // Forward to handler
48926        else {
48927            // Try to sniff data type if it has not been defined
48928            if(this.responseType === DS.TYPE_UNKNOWN) {
48929                var ctype = (oResponse.getResponseHeader) ? oResponse.getResponseHeader["Content-Type"] : null;
48930                if(ctype) {
48931                    // xml
48932                    if(ctype.indexOf("text/xml") > -1) {
48933                        this.responseType = DS.TYPE_XML;
48934                    }
48935                    else if(ctype.indexOf("application/json") > -1) { // json
48936                        this.responseType = DS.TYPE_JSON;
48937                    }
48938                    else if(ctype.indexOf("text/plain") > -1) { // text
48939                        this.responseType = DS.TYPE_TEXT;
48940                    }
48941                }
48942            }
48943            this.handleResponse(oRequest, oResponse, oCallback, oCaller, tId);
48944        }
48945    };
48946
48947    /**
48948     * Define Connection Manager failure handler
48949     *
48950     * @method _xhrFailure
48951     * @param oResponse {Object} HTTPXMLRequest object
48952     * @private
48953     */
48954    var _xhrFailure = function(oResponse) {
48955        this.fireEvent("dataErrorEvent", {request:oRequest, response: oResponse,
48956                callback:oCallback, caller:oCaller,
48957                message:DS.ERROR_DATAINVALID});
48958        YAHOO.log(DS.ERROR_DATAINVALID + ": " +
48959                oResponse.statusText, "error", this.toString());
48960
48961        // Backward compatibility
48962        if(lang.isString(this.liveData) && lang.isString(oRequest) &&
48963            (this.liveData.lastIndexOf("?") !== this.liveData.length-1) &&
48964            (oRequest.indexOf("?") !== 0)){
48965                YAHOO.log("DataSources using XHR no longer automatically supply " + 
48966                "a \"?\" between the host and query parameters" +
48967                " -- please check that the request URL is correct", "warn", this.toString());
48968        }
48969
48970        // Send failure response back to the caller with the error flag on
48971        oResponse = oResponse || {};
48972        oResponse.error = true;
48973        DS.issueCallback(oCallback,[oRequest,oResponse],true, oCaller);
48974
48975        return null;
48976    };
48977
48978    /**
48979     * Define Connection Manager callback object
48980     *
48981     * @property _xhrCallback
48982     * @param oResponse {Object} HTTPXMLRequest object
48983     * @private
48984     */
48985     var _xhrCallback = {
48986        success:_xhrSuccess,
48987        failure:_xhrFailure,
48988        scope: this
48989    };
48990
48991    // Apply Connection Manager timeout
48992    if(lang.isNumber(this.connTimeout)) {
48993        _xhrCallback.timeout = this.connTimeout;
48994    }
48995
48996    // Cancel stale requests
48997    if(this.connXhrMode == "cancelStaleRequests") {
48998            // Look in queue for stale requests
48999            if(oQueue.conn) {
49000                if(oConnMgr.abort) {
49001                    oConnMgr.abort(oQueue.conn);
49002                    oQueue.conn = null;
49003                    YAHOO.log("Canceled stale request", "warn", this.toString());
49004                }
49005                else {
49006                    YAHOO.log("Could not find Connection Manager abort() function", "error", this.toString());
49007                }
49008            }
49009    }
49010
49011    // Get ready to send the request URL
49012    if(oConnMgr && oConnMgr.asyncRequest) {
49013        var sLiveData = this.liveData;
49014        var isPost = this.connMethodPost;
49015        var sMethod = (isPost) ? "POST" : "GET";
49016        // Validate request
49017        var sUri = (isPost || !lang.isValue(oRequest)) ? sLiveData : sLiveData+oRequest;
49018        var sRequest = (isPost) ? oRequest : null;
49019
49020        // Send the request right away
49021        if(this.connXhrMode != "queueRequests") {
49022            oQueue.conn = oConnMgr.asyncRequest(sMethod, sUri, _xhrCallback, sRequest);
49023        }
49024        // Queue up then send the request
49025        else {
49026            // Found a request already in progress
49027            if(oQueue.conn) {
49028                var allRequests = oQueue.requests;
49029                // Add request to queue
49030                allRequests.push({request:oRequest, callback:_xhrCallback});
49031
49032                // Interval needs to be started
49033                if(!oQueue.interval) {
49034                    oQueue.interval = setInterval(function() {
49035                        // Connection is in progress
49036                        if(oConnMgr.isCallInProgress(oQueue.conn)) {
49037                            return;
49038                        }
49039                        else {
49040                            // Send next request
49041                            if(allRequests.length > 0) {
49042                                // Validate request
49043                                sUri = (isPost || !lang.isValue(allRequests[0].request)) ? sLiveData : sLiveData+allRequests[0].request;
49044                                sRequest = (isPost) ? allRequests[0].request : null;
49045                                oQueue.conn = oConnMgr.asyncRequest(sMethod, sUri, allRequests[0].callback, sRequest);
49046
49047                                // Remove request from queue
49048                                allRequests.shift();
49049                            }
49050                            // No more requests
49051                            else {
49052                                clearInterval(oQueue.interval);
49053                                oQueue.interval = null;
49054                            }
49055                        }
49056                    }, 50);
49057                }
49058            }
49059            // Nothing is in progress
49060            else {
49061                oQueue.conn = oConnMgr.asyncRequest(sMethod, sUri, _xhrCallback, sRequest);
49062            }
49063        }
49064    }
49065    else {
49066        YAHOO.log("Could not find Connection Manager asyncRequest() function", "error", this.toString());
49067        // Send null response back to the caller with the error flag on
49068        DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);
49069    }
49070
49071    return tId;
49072}
49073
49074});
49075
49076// Copy static members to XHRDataSource class
49077lang.augmentObject(util.XHRDataSource, DS);
49078
49079
49080
49081
49082
49083
49084
49085
49086
49087
49088
49089
49090
49091/****************************************************************************/
49092/****************************************************************************/
49093/****************************************************************************/
49094
49095/**
49096 * Factory class for creating a BaseDataSource subclass instance. The sublcass is
49097 * determined by oLiveData's type, unless the dataType config is explicitly passed in.  
49098 *
49099 * @namespace YAHOO.util
49100 * @class YAHOO.util.DataSource
49101 * @constructor
49102 * @param oLiveData {HTMLElement}  Pointer to live data.
49103 * @param oConfigs {object} (optional) Object literal of configuration values.
49104 */
49105util.DataSource = function(oLiveData, oConfigs) {
49106    oConfigs = oConfigs || {};
49107    
49108    // Point to one of the subclasses, first by dataType if given, then by sniffing oLiveData type.
49109    var dataType = oConfigs.dataType;
49110    if(dataType) {
49111        if(dataType == DS.TYPE_LOCAL) {
49112            return new util.LocalDataSource(oLiveData, oConfigs);
49113        }
49114        else if(dataType == DS.TYPE_XHR) {
49115            return new util.XHRDataSource(oLiveData, oConfigs);            
49116        }
49117        else if(dataType == DS.TYPE_SCRIPTNODE) {
49118            return new util.ScriptNodeDataSource(oLiveData, oConfigs);            
49119        }
49120        else if(dataType == DS.TYPE_JSFUNCTION) {
49121            return new util.FunctionDataSource(oLiveData, oConfigs);            
49122        }
49123    }
49124    
49125    if(YAHOO.lang.isString(oLiveData)) { // strings default to xhr
49126        return new util.XHRDataSource(oLiveData, oConfigs);
49127    }
49128    else if(YAHOO.lang.isFunction(oLiveData)) {
49129        return new util.FunctionDataSource(oLiveData, oConfigs);
49130    }
49131    else { // ultimate default is local
49132        return new util.LocalDataSource(oLiveData, oConfigs);
49133    }
49134};
49135
49136// Copy static members to DataSource class
49137lang.augmentObject(util.DataSource, DS);
49138
49139})();
49140
49141/****************************************************************************/
49142/****************************************************************************/
49143/****************************************************************************/
49144
49145/**
49146 * The static Number class provides helper functions to deal with data of type
49147 * Number.
49148 *
49149 * @namespace YAHOO.util
49150 * @requires yahoo
49151 * @class Number
49152 * @static
49153 */
49154 YAHOO.util.Number = {
49155 
49156     /**
49157     * Takes a native JavaScript Number and formats to a string for display.
49158     *
49159     * @method format
49160     * @param nData {Number} Number.
49161     * @param oConfig {Object} (Optional) Optional configuration values:
49162     *  <dl>
49163     *   <dt>format</dt>
49164     *   <dd>String used as a template for formatting positive numbers.
49165     *   {placeholders} in the string are applied from the values in this
49166     *   config object. {number} is used to indicate where the numeric portion
49167     *   of the output goes.  For example &quot;{prefix}{number} per item&quot;
49168     *   might yield &quot;$5.25 per item&quot;.  The only required
49169     *   {placeholder} is {number}.</dd>
49170     *
49171     *   <dt>negativeFormat</dt>
49172     *   <dd>Like format, but applied to negative numbers.  If set to null,
49173     *   defaults from the configured format, prefixed with -.  This is
49174     *   separate from format to support formats like &quot;($12,345.67)&quot;.
49175     *
49176     *   <dt>prefix {String} (deprecated, use format/negativeFormat)</dt>
49177     *   <dd>String prepended before each number, like a currency designator "$"</dd>
49178     *   <dt>decimalPlaces {Number}</dt>
49179     *   <dd>Number of decimal places to round.</dd>
49180     *
49181     *   <dt>decimalSeparator {String}</dt>
49182     *   <dd>Decimal separator</dd>
49183     *
49184     *   <dt>thousandsSeparator {String}</dt>
49185     *   <dd>Thousands separator</dd>
49186     *
49187     *   <dt>suffix {String} (deprecated, use format/negativeFormat)</dt>
49188     *   <dd>String appended after each number, like " items" (note the space)</dd>
49189     *  </dl>
49190     * @return {String} Formatted number for display. Note, the following values
49191     * return as "": null, undefined, NaN, "".
49192     */
49193    format : function(n, cfg) {
49194        if (n === '' || n === null || !isFinite(n)) {
49195            return '';
49196        }
49197
49198        n   = +n;
49199        cfg = YAHOO.lang.merge(YAHOO.util.Number.format.defaults, (cfg || {}));
49200
49201        var stringN = n+'',
49202            absN   = Math.abs(n),
49203            places = cfg.decimalPlaces || 0,
49204            sep    = cfg.thousandsSeparator,
49205            negFmt = cfg.negativeFormat || ('-' + cfg.format),
49206            s, bits, i, precision;
49207
49208        if (negFmt.indexOf('#') > -1) {
49209            // for backward compatibility of negativeFormat supporting '-#'
49210            negFmt = negFmt.replace(/#/, cfg.format);
49211        }
49212
49213        if (places < 0) {
49214            // Get rid of the decimal info
49215            s = absN - (absN % 1) + '';
49216            i = s.length + places;
49217
49218            // avoid 123 vs decimalPlaces -4 (should return "0")
49219            if (i > 0) {
49220                // leverage toFixed by making 123 => 0.123 for the rounding
49221                // operation, then add the appropriate number of zeros back on
49222                s = Number('.' + s).toFixed(i).slice(2) +
49223                    new Array(s.length - i + 1).join('0');
49224            } else {
49225                s = "0";
49226            }
49227        } else {
49228            // Avoid toFixed on floats:
49229            // Bug 2528976
49230            // Bug 2528977
49231            var unfloatedN = absN+'';
49232            if(places > 0 || unfloatedN.indexOf('.') > 0) {
49233                var power = Math.pow(10, places);
49234                s = Math.round(absN * power) / power + '';
49235                var dot = s.indexOf('.'),
49236                    padding, zeroes;
49237                
49238                // Add padding
49239                if(dot < 0) {
49240                    padding = places;
49241                    zeroes = (Math.pow(10, padding) + '').substring(1);
49242                    if(places > 0) {
49243                        s = s + '.' + zeroes;
49244                    }
49245                }
49246                else {
49247                    padding = places - (s.length - dot - 1);
49248                    zeroes = (Math.pow(10, padding) + '').substring(1);
49249                    s = s + zeroes;
49250                }
49251            }
49252            else {
49253                s = absN.toFixed(places)+'';
49254            }
49255        }
49256
49257        bits  = s.split(/\D/);
49258
49259        if (absN >= 1000) {
49260            i  = bits[0].length % 3 || 3;
49261
49262            bits[0] = bits[0].slice(0,i) +
49263                      bits[0].slice(i).replace(/(\d{3})/g, sep + '$1');
49264
49265        }
49266
49267        return YAHOO.util.Number.format._applyFormat(
49268            (n < 0 ? negFmt : cfg.format),
49269            bits.join(cfg.decimalSeparator),
49270            cfg);
49271    }
49272};
49273
49274/**
49275 * <p>Default values for Number.format behavior.  Override properties of this
49276 * object if you want every call to Number.format in your system to use
49277 * specific presets.</p>
49278 *
49279 * <p>Available keys include:</p>
49280 * <ul>
49281 *   <li>format</li>
49282 *   <li>negativeFormat</li>
49283 *   <li>decimalSeparator</li>
49284 *   <li>decimalPlaces</li>
49285 *   <li>thousandsSeparator</li>
49286 *   <li>prefix/suffix or any other token you want to use in the format templates</li>
49287 * </ul>
49288 *
49289 * @property Number.format.defaults
49290 * @type {Object}
49291 * @static
49292 */
49293YAHOO.util.Number.format.defaults = {
49294    format : '{prefix}{number}{suffix}',
49295    negativeFormat : null, // defaults to -(format)
49296    decimalSeparator : '.',
49297    decimalPlaces    : null,
49298    thousandsSeparator : ''
49299};
49300
49301/**
49302 * Apply any special formatting to the "d,ddd.dd" string.  Takes either the
49303 * cfg.format or cfg.negativeFormat template and replaces any {placeholders}
49304 * with either the number or a value from a so-named property of the config
49305 * object.
49306 *
49307 * @method Number.format._applyFormat
49308 * @static
49309 * @param tmpl {String} the cfg.format or cfg.numberFormat template string
49310 * @param num {String} the number with separators and decimalPlaces applied
49311 * @param data {Object} the config object, used here to populate {placeholder}s
49312 * @return {String} the number with any decorators added
49313 */
49314YAHOO.util.Number.format._applyFormat = function (tmpl, num, data) {
49315    return tmpl.replace(/\{(\w+)\}/g, function (_, token) {
49316        return token === 'number' ? num :
49317               token in data ? data[token] : '';
49318    });
49319};
49320
49321
49322/****************************************************************************/
49323/****************************************************************************/
49324/****************************************************************************/
49325
49326(function () {
49327
49328var xPad=function (x, pad, r)
49329{
49330    if(typeof r === 'undefined')
49331    {
49332        r=10;
49333    }
49334    for( ; parseInt(x, 10)<r && r>1; r/=10) {
49335        x = pad.toString() + x;
49336    }
49337    return x.toString();
49338};
49339
49340
49341/**
49342 * The static Date class provides helper functions to deal with data of type Date.
49343 *
49344 * @namespace YAHOO.util
49345 * @requires yahoo
49346 * @class Date
49347 * @static
49348 */
49349 var Dt = {
49350    formats: {
49351        a: function (d, l) { return l.a[d.getDay()]; },
49352        A: function (d, l) { return l.A[d.getDay()]; },
49353        b: function (d, l) { return l.b[d.getMonth()]; },
49354        B: function (d, l) { return l.B[d.getMonth()]; },
49355        C: function (d) { return xPad(parseInt(d.getFullYear()/100, 10), 0); },
49356        d: ['getDate', '0'],
49357        e: ['getDate', ' '],
49358        g: function (d) { return xPad(parseInt(Dt.formats.G(d)%100, 10), 0); },
49359        G: function (d) {
49360                var y = d.getFullYear();
49361                var V = parseInt(Dt.formats.V(d), 10);
49362                var W = parseInt(Dt.formats.W(d), 10);
49363    
49364                if(W > V) {
49365                    y++;
49366                } else if(W===0 && V>=52) {
49367                    y--;
49368                }
49369    
49370                return y;
49371            },
49372        H: ['getHours', '0'],
49373        I: function (d) { var I=d.getHours()%12; return xPad(I===0?12:I, 0); },
49374        j: function (d) {
49375                var gmd_1 = new Date('' + d.getFullYear() + '/1/1 GMT');
49376                var gmdate = new Date('' + d.getFullYear() + '/' + (d.getMonth()+1) + '/' + d.getDate() + ' GMT');
49377                var ms = gmdate - gmd_1;
49378                var doy = parseInt(ms/60000/60/24, 10)+1;
49379                return xPad(doy, 0, 100);
49380            },
49381        k: ['getHours', ' '],
49382        l: function (d) { var I=d.getHours()%12; return xPad(I===0?12:I, ' '); },
49383        m: function (d) { return xPad(d.getMonth()+1, 0); },
49384        M: ['getMinutes', '0'],
49385        p: function (d, l) { return l.p[d.getHours() >= 12 ? 1 : 0 ]; },
49386        P: function (d, l) { return l.P[d.getHours() >= 12 ? 1 : 0 ]; },
49387        s: function (d, l) { return parseInt(d.getTime()/1000, 10); },
49388        S: ['getSeconds', '0'],
49389        u: function (d) { var dow = d.getDay(); return dow===0?7:dow; },
49390        U: function (d) {
49391                var doy = parseInt(Dt.formats.j(d), 10);
49392                var rdow = 6-d.getDay();
49393                var woy = parseInt((doy+rdow)/7, 10);
49394                return xPad(woy, 0);
49395            },
49396        V: function (d) {
49397                var woy = parseInt(Dt.formats.W(d), 10);
49398                var dow1_1 = (new Date('' + d.getFullYear() + '/1/1')).getDay();
49399                // First week is 01 and not 00 as in the case of %U and %W,
49400                // so we add 1 to the final result except if day 1 of the year
49401                // is a Monday (then %W returns 01).
49402                // We also need to subtract 1 if the day 1 of the year is 
49403                // Friday-Sunday, so the resulting equation becomes:
49404                var idow = woy + (dow1_1 > 4 || dow1_1 <= 1 ? 0 : 1);
49405                if(idow === 53 && (new Date('' + d.getFullYear() + '/12/31')).getDay() < 4)
49406                {
49407                    idow = 1;
49408                }
49409                else if(idow === 0)
49410                {
49411                    idow = Dt.formats.V(new Date('' + (d.getFullYear()-1) + '/12/31'));
49412                }
49413    
49414                return xPad(idow, 0);
49415            },
49416        w: 'getDay',
49417        W: function (d) {
49418                var doy = parseInt(Dt.formats.j(d), 10);
49419                var rdow = 7-Dt.formats.u(d);
49420                var woy = parseInt((doy+rdow)/7, 10);
49421                return xPad(woy, 0, 10);
49422            },
49423        y: function (d) { return xPad(d.getFullYear()%100, 0); },
49424        Y: 'getFullYear',
49425        z: function (d) {
49426                var o = d.getTimezoneOffset();
49427                var H = xPad(parseInt(Math.abs(o/60), 10), 0);
49428                var M = xPad(Math.abs(o%60), 0);
49429                return (o>0?'-':'+') + H + M;
49430            },
49431        Z: function (d) {
49432		var tz = d.toString().replace(/^.*:\d\d( GMT[+-]\d+)? \(?([A-Za-z ]+)\)?\d*$/, '$2').replace(/[a-z ]/g, '');
49433		if(tz.length > 4) {
49434			tz = Dt.formats.z(d);
49435		}
49436		return tz;
49437	},
49438        '%': function (d) { return '%'; }
49439    },
49440
49441    aggregates: {
49442        c: 'locale',
49443        D: '%m/%d/%y',
49444        F: '%Y-%m-%d',
49445        h: '%b',
49446        n: '\n',
49447        r: 'locale',
49448        R: '%H:%M',
49449        t: '\t',
49450        T: '%H:%M:%S',
49451        x: 'locale',
49452        X: 'locale'
49453        //'+': '%a %b %e %T %Z %Y'
49454    },
49455
49456     /**
49457     * Takes a native JavaScript Date and formats to string for display to user.
49458     *
49459     * @method format
49460     * @param oDate {Date} Date.
49461     * @param oConfig {Object} (Optional) Object literal of configuration values:
49462     *  <dl>
49463     *   <dt>format &lt;String&gt;</dt>
49464     *   <dd>
49465     *   <p>
49466     *   Any strftime string is supported, such as "%I:%M:%S %p". strftime has several format specifiers defined by the Open group at 
49467     *   <a href="http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html">http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html</a>
49468     *   </p>
49469     *   <p>   
49470     *   PHP added a few of its own, defined at <a href="http://www.php.net/strftime">http://www.php.net/strftime</a>
49471     *   </p>
49472     *   <p>
49473     *   This javascript implementation supports all the PHP specifiers and a few more.  The full list is below:
49474     *   </p>
49475     *   <dl>
49476     *    <dt>%a</dt> <dd>abbreviated weekday name according to the current locale</dd>
49477     *    <dt>%A</dt> <dd>full weekday name according to the current locale</dd>
49478     *    <dt>%b</dt> <dd>abbreviated month name according to the current locale</dd>
49479     *    <dt>%B</dt> <dd>full month name according to the current locale</dd>
49480     *    <dt>%c</dt> <dd>preferred date and time representation for the current locale</dd>
49481     *    <dt>%C</dt> <dd>century number (the year divided by 100 and truncated to an integer, range 00 to 99)</dd>
49482     *    <dt>%d</dt> <dd>day of the month as a decimal number (range 01 to 31)</dd>
49483     *    <dt>%D</dt> <dd>same as %m/%d/%y</dd>
49484     *    <dt>%e</dt> <dd>day of the month as a decimal number, a single digit is preceded by a space (range ' 1' to '31')</dd>
49485     *    <dt>%F</dt> <dd>same as %Y-%m-%d (ISO 8601 date format)</dd>
49486     *    <dt>%g</dt> <dd>like %G, but without the century</dd>
49487     *    <dt>%G</dt> <dd>The 4-digit year corresponding to the ISO week number</dd>
49488     *    <dt>%h</dt> <dd>same as %b</dd>
49489     *    <dt>%H</dt> <dd>hour as a decimal number using a 24-hour clock (range 00 to 23)</dd>
49490     *    <dt>%I</dt> <dd>hour as a decimal number using a 12-hour clock (range 01 to 12)</dd>
49491     *    <dt>%j</dt> <dd>day of the year as a decimal number (range 001 to 366)</dd>
49492     *    <dt>%k</dt> <dd>hour as a decimal number using a 24-hour clock (range 0 to 23); single digits are preceded by a blank. (See also %H.)</dd>
49493     *    <dt>%l</dt> <dd>hour as a decimal number using a 12-hour clock (range 1 to 12); single digits are preceded by a blank. (See also %I.) </dd>
49494     *    <dt>%m</dt> <dd>month as a decimal number (range 01 to 12)</dd>
49495     *    <dt>%M</dt> <dd>minute as a decimal number</dd>
49496     *    <dt>%n</dt> <dd>newline character</dd>
49497     *    <dt>%p</dt> <dd>either `AM' or `PM' according to the given time value, or the corresponding strings for the current locale</dd>
49498     *    <dt>%P</dt> <dd>like %p, but lower case</dd>
49499     *    <dt>%r</dt> <dd>time in a.m. and p.m. notation equal to %I:%M:%S %p</dd>
49500     *    <dt>%R</dt> <dd>time in 24 hour notation equal to %H:%M</dd>
49501     *    <dt>%s</dt> <dd>number of seconds since the Epoch, ie, since 1970-01-01 00:00:00 UTC</dd>
49502     *    <dt>%S</dt> <dd>second as a decimal number</dd>
49503     *    <dt>%t</dt> <dd>tab character</dd>
49504     *    <dt>%T</dt> <dd>current time, equal to %H:%M:%S</dd>
49505     *    <dt>%u</dt> <dd>weekday as a decimal number [1,7], with 1 representing Monday</dd>
49506     *    <dt>%U</dt> <dd>week number of the current year as a decimal number, starting with the
49507     *            first Sunday as the first day of the first week</dd>
49508     *    <dt>%V</dt> <dd>The ISO 8601:1988 week number of the current year as a decimal number,
49509     *            range 01 to 53, where week 1 is the first week that has at least 4 days
49510     *            in the current year, and with Monday as the first day of the week.</dd>
49511     *    <dt>%w</dt> <dd>day of the week as a decimal, Sunday being 0</dd>
49512     *    <dt>%W</dt> <dd>week number of the current year as a decimal number, starting with the
49513     *            first Monday as the first day of the first week</dd>
49514     *    <dt>%x</dt> <dd>preferred date representation for the current locale without the time</dd>
49515     *    <dt>%X</dt> <dd>preferred time representation for the current locale without the date</dd>
49516     *    <dt>%y</dt> <dd>year as a decimal number without a century (range 00 to 99)</dd>
49517     *    <dt>%Y</dt> <dd>year as a decimal number including the century</dd>
49518     *    <dt>%z</dt> <dd>numerical time zone representation</dd>
49519     *    <dt>%Z</dt> <dd>time zone name or abbreviation</dd>
49520     *    <dt>%%</dt> <dd>a literal `%' character</dd>
49521     *   </dl>
49522     *  </dd>
49523     * </dl>
49524     * @param sLocale {String} (Optional) The locale to use when displaying days of week,
49525     *  months of the year, and other locale specific strings.  The following locales are
49526     *  built in:
49527     *  <dl>
49528     *   <dt>en</dt>
49529     *   <dd>English</dd>
49530     *   <dt>en-US</dt>
49531     *   <dd>US English</dd>
49532     *   <dt>en-GB</dt>
49533     *   <dd>British English</dd>
49534     *   <dt>en-AU</dt>
49535     *   <dd>Australian English (identical to British English)</dd>
49536     *  </dl>
49537     *  More locales may be added by subclassing of YAHOO.util.DateLocale.
49538     *  See YAHOO.util.DateLocale for more information.
49539     * @return {HTML} Formatted date for display. Non-date values are passed
49540     * through as-is.
49541     * @sa YAHOO.util.DateLocale
49542     */
49543    format : function (oDate, oConfig, sLocale) {
49544        oConfig = oConfig || {};
49545        
49546        if(!(oDate instanceof Date)) {
49547            return YAHOO.lang.isValue(oDate) ? oDate : "";
49548        }
49549
49550        var format = oConfig.format || "%m/%d/%Y";
49551
49552        // Be backwards compatible, support strings that are
49553        // exactly equal to YYYY/MM/DD, DD/MM/YYYY and MM/DD/YYYY
49554        if(format === 'YYYY/MM/DD') {
49555            format = '%Y/%m/%d';
49556        } else if(format === 'DD/MM/YYYY') {
49557            format = '%d/%m/%Y';
49558        } else if(format === 'MM/DD/YYYY') {
49559            format = '%m/%d/%Y';
49560        }
49561        // end backwards compatibility block
49562 
49563        sLocale = sLocale || "en";
49564
49565        // Make sure we have a definition for the requested locale, or default to en.
49566        if(!(sLocale in YAHOO.util.DateLocale)) {
49567            if(sLocale.replace(/-[a-zA-Z]+$/, '') in YAHOO.util.DateLocale) {
49568                sLocale = sLocale.replace(/-[a-zA-Z]+$/, '');
49569            } else {
49570                sLocale = "en";
49571            }
49572        }
49573
49574        var aLocale = YAHOO.util.DateLocale[sLocale];
49575
49576        var replace_aggs = function (m0, m1) {
49577            var f = Dt.aggregates[m1];
49578            return (f === 'locale' ? aLocale[m1] : f);
49579        };
49580
49581        var replace_formats = function (m0, m1) {
49582            var f = Dt.formats[m1];
49583            if(typeof f === 'string') {             // string => built in date function
49584                return oDate[f]();
49585            } else if(typeof f === 'function') {    // function => our own function
49586                return f.call(oDate, oDate, aLocale);
49587            } else if(typeof f === 'object' && typeof f[0] === 'string') {  // built in function with padding
49588                return xPad(oDate[f[0]](), f[1]);
49589            } else {
49590                return m1;
49591            }
49592        };
49593
49594        // First replace aggregates (run in a loop because an agg may be made up of other aggs)
49595        while(format.match(/%[cDFhnrRtTxX]/)) {
49596            format = format.replace(/%([cDFhnrRtTxX])/g, replace_aggs);
49597        }
49598
49599        // Now replace formats (do not run in a loop otherwise %%a will be replace with the value of %a)
49600        var str = format.replace(/%([aAbBCdegGHIjklmMpPsSuUVwWyYzZ%])/g, replace_formats);
49601
49602        replace_aggs = replace_formats = undefined;
49603
49604        return str;
49605    }
49606 };
49607 
49608 YAHOO.namespace("YAHOO.util");
49609 YAHOO.util.Date = Dt;
49610
49611/**
49612 * The DateLocale class is a container and base class for all
49613 * localised date strings used by YAHOO.util.Date. It is used
49614 * internally, but may be extended to provide new date localisations.
49615 *
49616 * To create your own DateLocale, follow these steps:
49617 * <ol>
49618 *  <li>Find an existing locale that matches closely with your needs</li>
49619 *  <li>Use this as your base class.  Use YAHOO.util.DateLocale if nothing
49620 *   matches.</li>
49621 *  <li>Create your own class as an extension of the base class using
49622 *   YAHOO.lang.merge, and add your own localisations where needed.</li>
49623 * </ol>
49624 * See the YAHOO.util.DateLocale['en-US'] and YAHOO.util.DateLocale['en-GB']
49625 * classes which extend YAHOO.util.DateLocale['en'].
49626 *
49627 * For example, to implement locales for French french and Canadian french,
49628 * we would do the following:
49629 * <ol>
49630 *  <li>For French french, we have no existing similar locale, so use
49631 *   YAHOO.util.DateLocale as the base, and extend it:
49632 *   <pre>
49633 *      YAHOO.util.DateLocale['fr'] = YAHOO.lang.merge(YAHOO.util.DateLocale, {
49634 *          a: ['dim', 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam'],
49635 *          A: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
49636 *          b: ['jan', 'f&eacute;v', 'mar', 'avr', 'mai', 'jun', 'jui', 'ao&ucirc;', 'sep', 'oct', 'nov', 'd&eacute;c'],
49637 *          B: ['janvier', 'f&eacute;vrier', 'mars', 'avril', 'mai', 'juin', 'juillet', 'ao&ucirc;t', 'septembre', 'octobre', 'novembre', 'd&eacute;cembre'],
49638 *          c: '%a %d %b %Y %T %Z',
49639 *          p: ['', ''],
49640 *          P: ['', ''],
49641 *          x: '%d.%m.%Y',
49642 *          X: '%T'
49643 *      });
49644 *   </pre>
49645 *  </li>
49646 *  <li>For Canadian french, we start with French french and change the meaning of \%x:
49647 *   <pre>
49648 *      YAHOO.util.DateLocale['fr-CA'] = YAHOO.lang.merge(YAHOO.util.DateLocale['fr'], {
49649 *          x: '%Y-%m-%d'
49650 *      });
49651 *   </pre>
49652 *  </li>
49653 * </ol>
49654 *
49655 * With that, you can use your new locales:
49656 * <pre>
49657 *    var d = new Date("2008/04/22");
49658 *    YAHOO.util.Date.format(d, {format: "%A, %d %B == %x"}, "fr");
49659 * </pre>
49660 * will return:
49661 * <pre>
49662 *    mardi, 22 avril == 22.04.2008
49663 * </pre>
49664 * And
49665 * <pre>
49666 *    YAHOO.util.Date.format(d, {format: "%A, %d %B == %x"}, "fr-CA");
49667 * </pre>
49668 * Will return:
49669 * <pre>
49670 *   mardi, 22 avril == 2008-04-22
49671 * </pre>
49672 * @namespace YAHOO.util
49673 * @requires yahoo
49674 * @class DateLocale
49675 */
49676 YAHOO.util.DateLocale = {
49677        a: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
49678        A: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
49679        b: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
49680        B: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
49681        c: '%a %d %b %Y %T %Z',
49682        p: ['AM', 'PM'],
49683        P: ['am', 'pm'],
49684        r: '%I:%M:%S %p',
49685        x: '%d/%m/%y',
49686        X: '%T'
49687 };
49688
49689 YAHOO.util.DateLocale['en'] = YAHOO.lang.merge(YAHOO.util.DateLocale, {});
49690
49691 YAHOO.util.DateLocale['en-US'] = YAHOO.lang.merge(YAHOO.util.DateLocale['en'], {
49692        c: '%a %d %b %Y %I:%M:%S %p %Z',
49693        x: '%m/%d/%Y',
49694        X: '%I:%M:%S %p'
49695 });
49696
49697 YAHOO.util.DateLocale['en-GB'] = YAHOO.lang.merge(YAHOO.util.DateLocale['en'], {
49698        r: '%l:%M:%S %P %Z'
49699 });
49700 YAHOO.util.DateLocale['en-AU'] = YAHOO.lang.merge(YAHOO.util.DateLocale['en']);
49701
49702})();
49703
49704YAHOO.register("datasource", YAHOO.util.DataSource, {version: "2.9.0", build: "2800"});
49705/*
49706Copyright (c) 2011, Yahoo! Inc. All rights reserved.
49707Code licensed under the BSD License:
49708http://developer.yahoo.com/yui/license.html
49709version: 2.9.0
49710*/
49711/////////////////////////////////////////////////////////////////////////////
49712//
49713// YAHOO.widget.DataSource Backwards Compatibility
49714//
49715/////////////////////////////////////////////////////////////////////////////
49716
49717YAHOO.widget.DS_JSArray = YAHOO.util.LocalDataSource;
49718
49719YAHOO.widget.DS_JSFunction = YAHOO.util.FunctionDataSource;
49720
49721YAHOO.widget.DS_XHR = function(sScriptURI, aSchema, oConfigs) {
49722    var DS = new YAHOO.util.XHRDataSource(sScriptURI, oConfigs);
49723    DS._aDeprecatedSchema = aSchema;
49724    return DS;
49725};
49726
49727YAHOO.widget.DS_ScriptNode = function(sScriptURI, aSchema, oConfigs) {
49728    var DS = new YAHOO.util.ScriptNodeDataSource(sScriptURI, oConfigs);
49729    DS._aDeprecatedSchema = aSchema;
49730    return DS;
49731};
49732
49733YAHOO.widget.DS_XHR.TYPE_JSON = YAHOO.util.DataSourceBase.TYPE_JSON;
49734YAHOO.widget.DS_XHR.TYPE_XML = YAHOO.util.DataSourceBase.TYPE_XML;
49735YAHOO.widget.DS_XHR.TYPE_FLAT = YAHOO.util.DataSourceBase.TYPE_TEXT;
49736
49737// TODO: widget.DS_ScriptNode.scriptCallbackParam
49738
49739
49740
49741 /**
49742 * The AutoComplete control provides the front-end logic for text-entry suggestion and
49743 * completion functionality.
49744 *
49745 * @module autocomplete
49746 * @requires yahoo, dom, event, datasource
49747 * @optional animation
49748 * @namespace YAHOO.widget
49749 * @title AutoComplete Widget
49750 */
49751
49752/****************************************************************************/
49753/****************************************************************************/
49754/****************************************************************************/
49755
49756/**
49757 * The AutoComplete class provides the customizable functionality of a plug-and-play DHTML
49758 * auto completion widget.  Some key features:
49759 * <ul>
49760 * <li>Navigate with up/down arrow keys and/or mouse to pick a selection</li>
49761 * <li>The drop down container can "roll down" or "fly out" via configurable
49762 * animation</li>
49763 * <li>UI look-and-feel customizable through CSS, including container
49764 * attributes, borders, position, fonts, etc</li>
49765 * </ul>
49766 *
49767 * @class AutoComplete
49768 * @constructor
49769 * @param elInput {HTMLElement} DOM element reference of an input field.
49770 * @param elInput {String} String ID of an input field.
49771 * @param elContainer {HTMLElement} DOM element reference of an existing DIV.
49772 * @param elContainer {String} String ID of an existing DIV.
49773 * @param oDataSource {YAHOO.widget.DataSource} DataSource instance.
49774 * @param oConfigs {Object} (optional) Object literal of configuration params.
49775 */
49776YAHOO.widget.AutoComplete = function(elInput,elContainer,oDataSource,oConfigs) {
49777    if(elInput && elContainer && oDataSource) {
49778        // Validate DataSource
49779        if(oDataSource && YAHOO.lang.isFunction(oDataSource.sendRequest)) {
49780            this.dataSource = oDataSource;
49781        }
49782        else {
49783            YAHOO.log("Could not instantiate AutoComplete due to an invalid DataSource", "error", this.toString());
49784            return;
49785        }
49786
49787        // YAHOO.widget.DataSource schema backwards compatibility
49788        // Converted deprecated schema into supported schema
49789        // First assume key data is held in position 0 of results array
49790        this.key = 0;
49791        var schema = oDataSource.responseSchema;
49792        // An old school schema has been defined in the deprecated DataSource constructor
49793        if(oDataSource._aDeprecatedSchema) {
49794            var aDeprecatedSchema = oDataSource._aDeprecatedSchema;
49795            if(YAHOO.lang.isArray(aDeprecatedSchema)) {
49796                
49797                if((oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_JSON) || 
49798                (oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_UNKNOWN)) { // Used to default to unknown
49799                    // Store the resultsList
49800                    schema.resultsList = aDeprecatedSchema[0];
49801                    // Store the key
49802                    this.key = aDeprecatedSchema[1];
49803                    // Only resultsList and key are defined, so grab all the data
49804                    schema.fields = (aDeprecatedSchema.length < 3) ? null : aDeprecatedSchema.slice(1);
49805                }
49806                else if(oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_XML) {
49807                    schema.resultNode = aDeprecatedSchema[0];
49808                    this.key = aDeprecatedSchema[1];
49809                    schema.fields = aDeprecatedSchema.slice(1);
49810                }                
49811                else if(oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_TEXT) {
49812                    schema.recordDelim = aDeprecatedSchema[0];
49813                    schema.fieldDelim = aDeprecatedSchema[1];
49814                }                
49815                oDataSource.responseSchema = schema;
49816            }
49817        }
49818        
49819        // Validate input element
49820        if(YAHOO.util.Dom.inDocument(elInput)) {
49821            if(YAHOO.lang.isString(elInput)) {
49822                    this._sName = "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput;
49823                    this._elTextbox = document.getElementById(elInput);
49824            }
49825            else {
49826                this._sName = (elInput.id) ?
49827                    "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput.id:
49828                    "instance" + YAHOO.widget.AutoComplete._nIndex;
49829                this._elTextbox = elInput;
49830            }
49831            YAHOO.util.Dom.addClass(this._elTextbox, "yui-ac-input");
49832        }
49833        else {
49834            YAHOO.log("Could not instantiate AutoComplete due to an invali
49834d input element", "error", this.toString());
49835            return;
49836        }
49837
49838        // Validate container element
49839        if(YAHOO.util.Dom.inDocument(elContainer)) {
49840            if(YAHOO.lang.isString(elContainer)) {
49841                    this._elContainer = document.getElementById(elContainer);
49842            }
49843            else {
49844                this._elContainer = elContainer;
49845            }
49846            if(this._elContainer.style.display == "none") {
49847                YAHOO.log("The container may not display properly if display is set to \"none\" in CSS", "warn", this.toString());
49848            }
49849            
49850            // For skinning
49851            var elParent = this._elContainer.parentNode;
49852            var elTag = elParent.tagName.toLowerCase();
49853            if(elTag == "div") {
49854                YAHOO.util.Dom.addClass(elParent, "yui-ac");
49855            }
49856            else {
49857                YAHOO.log("Could not find the wrapper element for skinning", "warn", this.toString());
49858            }
49859        }
49860        else {
49861            YAHOO.log("Could not instantiate AutoComplete due to an invalid container element", "error", this.toString());
49862            return;
49863        }
49864
49865        // Default applyLocalFilter setting is to enable for local sources
49866        if(this.dataSource.dataType === YAHOO.util.DataSourceBase.TYPE_LOCAL) {
49867            this.applyLocalFilter = true;
49868        }
49869        
49870        // Set any config params passed in to override defaults
49871        if(oConfigs && (oConfigs.constructor == Object)) {
49872            for(var sConfig in oConfigs) {
49873                if(sConfig) {
49874                    this[sConfig] = oConfigs[sConfig];
49875                }
49876            }
49877        }
49878
49879        // Initialization sequence
49880        this._initContainerEl();
49881        this._initProps();
49882        this._initListEl();
49883        this._initContainerHelperEls();
49884
49885        // Set up events
49886        var oSelf = this;
49887        var elTextbox = this._elTextbox;
49888
49889        // Dom events
49890        YAHOO.util.Event.addListener(elTextbox,"keyup",oSelf._onTextboxKeyUp,oSelf);
49891        YAHOO.util.Event.addListener(elTextbox,"keydown",oSelf._onTextboxKeyDown,oSelf);
49892        YAHOO.util.Event.addListener(elTextbox,"focus",oSelf._onTextboxFocus,oSelf);
49893        YAHOO.util.Event.addListener(elTextbox,"blur",oSelf._onTextboxBlur,oSelf);
49894        YAHOO.util.Event.addListener(elContainer,"mouseover",oSelf._onContainerMouseover,oSelf);
49895        YAHOO.util.Event.addListener(elContainer,"mouseout",oSelf._onContainerMouseout,oSelf);
49896        YAHOO.util.Event.addListener(elContainer,"click",oSelf._onContainerClick,oSelf);
49897        YAHOO.util.Event.addListener(elContainer,"scroll",oSelf._onContainerScroll,oSelf);
49898        YAHOO.util.Event.addListener(elContainer,"resize",oSelf._onContainerResize,oSelf);
49899        YAHOO.util.Event.addListener(elTextbox,"keypress",oSelf._onTextboxKeyPress,oSelf);
49900        YAHOO.util.Event.addListener(window,"unload",oSelf._onWindowUnload,oSelf);
49901
49902        // Custom events
49903        this.textboxFocusEvent = new YAHOO.util.CustomEvent("textboxFocus", this);
49904        this.textboxKeyEvent = new YAHOO.util.CustomEvent("textboxKey", this);
49905        this.dataRequestEvent = new YAHOO.util.CustomEvent("dataRequest", this);
49906        this.dataRequestCancelEvent = new YAHOO.util.CustomEvent("dataRequestCancel", this);
49907        this.dataReturnEvent = new YAHOO.util.CustomEvent("dataReturn", this);
49908        this.dataErrorEvent = new YAHOO.util.CustomEvent("dataError", this);
49909        this.containerPopulateEvent = new YAHOO.util.CustomEvent("containerPopulate", this);
49910        this.containerExpandEvent = new YAHOO.util.CustomEvent("containerExpand", this);
49911        this.typeAheadEvent = new YAHOO.util.CustomEvent("typeAhead", this);
49912        this.itemMouseOverEvent = new YAHOO.util.CustomEvent("itemMouseOver", this);
49913        this.itemMouseOutEvent = new YAHOO.util.CustomEvent("itemMouseOut", this);
49914        this.itemArrowToEvent = new YAHOO.util.CustomEvent("itemArrowTo", this);
49915        this.itemArrowFromEvent = new YAHOO.util.CustomEvent("itemArrowFrom", this);
49916        this.itemSelectEvent = new YAHOO.util.CustomEvent("itemSelect", this);
49917        this.unmatchedItemSelectEvent = new YAHOO.util.CustomEvent("unmatchedItemSelect", this);
49918        this.selectionEnforceEvent = new YAHOO.util.CustomEvent("selectionEnforce", this);
49919        this.containerCollapseEvent = new YAHOO.util.CustomEvent("containerCollapse", this);
49920        this.textboxBlurEvent = new YAHOO.util.CustomEvent("textboxBlur", this);
49921        this.textboxChangeEvent = new YAHOO.util.CustomEvent("textboxChange", this);
49922        
49923        // Finish up
49924        elTextbox.setAttribute("autocomplete","off");
49925        YAHOO.widget.AutoComplete._nIndex++;
49926        YAHOO.log("AutoComplete initialized","info",this.toString());
49927    }
49928    // Required arguments were not found
49929    else {
49930        YAHOO.log("Could not instantiate AutoComplete due invalid arguments", "error", this.toString());
49931    }
49932};
49933
49934/////////////////////////////////////////////////////////////////////////////
49935//
49936// Public member variables
49937//
49938/////////////////////////////////////////////////////////////////////////////
49939
49940/**
49941 * The DataSource object that encapsulates the data used for auto completion.
49942 * This object should be an inherited object from YAHOO.widget.DataSource.
49943 *
49944 * @property dataSource
49945 * @type YAHOO.widget.DataSource
49946 */
49947YAHOO.widget.AutoComplete.prototype.dataSource = null;
49948
49949/**
49950 * By default, results from local DataSources will pass through the filterResults
49951 * method to apply a client-side matching algorithm. 
49952 * 
49953 * @property applyLocalFilter
49954 * @type Boolean
49955 * @default true for local arrays and json, otherwise false
49956 */
49957YAHOO.widget.AutoComplete.prototype.applyLocalFilter = null;
49958
49959/**
49960 * When applyLocalFilter is true, the local filtering algorthim can have case sensitivity
49961 * enabled. 
49962 * 
49963 * @property queryMatchCase
49964 * @type Boolean
49965 * @default false
49966 */
49967YAHOO.widget.AutoComplete.prototype.queryMatchCase = false;
49968
49969/**
49970 * When applyLocalFilter is true, results can  be locally filtered to return
49971 * matching strings that "contain" the query string rather than simply "start with"
49972 * the query string.
49973 * 
49974 * @property queryMatchContains
49975 * @type Boolean
49976 * @default false
49977 */
49978YAHOO.widget.AutoComplete.prototype.queryMatchContains = false;
49979
49980/**
49981 * Enables query subset matching. When the DataSource's cache is enabled and queryMatchSubset is
49982 * true, substrings of queries will return matching cached results. For
49983 * instance, if the first query is for "abc" susequent queries that start with
49984 * "abc", like "abcd", will be queried against the cache, and not the live data
49985 * source. Recommended only for DataSources that return comprehensive results
49986 * for queries with very few characters.
49987 *
49988 * @property queryMatchSubset
49989 * @type Boolean
49990 * @default false
49991 *
49992 */
49993YAHOO.widget.AutoComplete.prototype.queryMatchSubset = false;
49994
49995/**
49996 * Number of characters that must be entered before querying for results. A negative value
49997 * effectively turns off the widget. A value of 0 allows queries of null or empty string
49998 * values.
49999 *
50000 * @property minQueryLength
50001 * @type Number
50002 * @default 1
50003 */
50004YAHOO.widget.AutoComplete.prototype.minQueryLength = 1;
50005
50006/**
50007 * Maximum number of results to display in results container.
50008 *
50009 * @property maxResultsDisplayed
50010 * @type Number
50011 * @default 10
50012 */
50013YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed = 10;
50014
50015/**
50016 * Number of seconds to delay before submitting a query request.  If a query
50017 * request is received before a previous one has completed its delay, the
50018 * previous request is cancelled and the new request is set to the delay. If 
50019 * typeAhead is also enabled, this value must always be less than the typeAheadDelay
50020 * in order to avoid certain race conditions. 
50021 *
50022 * @property queryDelay
50023 * @type Number
50024 * @default 0.2
50025 */
50026YAHOO.widget.AutoComplete.prototype.queryDelay = 0.2;
50027
50028/**
50029 * If typeAhead is true, number of seconds to delay before updating input with
50030 * typeAhead value. In order to prevent certain race conditions, this value must
50031 * always be greater than the queryDelay.
50032 *
50033 * @property typeAheadDelay
50034 * @type Number
50035 * @default 0.5
50036 */
50037YAHOO.widget.AutoComplete.prototype.typeAheadDelay = 0.5;
50038
50039/**
50040 * When IME usage is detected or interval detection is explicitly enabled,
50041 * AutoComplete will detect the input value at the given interval and send a
50042 * query if the value has changed.
50043 *
50044 * @property queryInterval
50045 * @type Number
50046 * @default 500
50047 */
50048YAHOO.widget.AutoComplete.prototype.queryInterval = 500;
50049
50050/**
50051 * Class name of a highlighted item within results container.
50052 *
50053 * @property highlightClassName
50054 * @type String
50055 * @default "yui-ac-highlight"
50056 */
50057YAHOO.widget.AutoComplete.prototype.highlightClassName = "yui-ac-highlight";
50058
50059/**
50060 * Class name of a pre-highlighted item within results container.
50061 *
50062 * @property prehighlightClassName
50063 * @type String
50064 */
50065YAHOO.widget.AutoComplete.prototype.prehighlightClassName = null;
50066
50067/**
50068 * Query delimiter. A single character separator for multiple delimited
50069 * selections. Multiple delimiter characteres may be defined as an array of
50070 * strings. A null value or empty string indicates that query results cannot
50071 * be delimited. This feature is not recommended if you need forceSelection to
50072 * be true.
50073 *
50074 * @property delimChar
50075 * @type String | String[]
50076 */
50077YAHOO.widget.AutoComplete.prototype.delimChar = null;
50078
50079/**
50080 * Whether or not the first item in results container should be automatically highlighted
50081 * on expand.
50082 *
50083 * @property autoHighlight
50084 * @type Boolean
50085 * @default true
50086 */
50087YAHOO.widget.AutoComplete.prototype.autoHighlight = true;
50088
50089/**
50090 * If autohighlight is enabled, whether or not the input field should be automatically updated
50091 * with the first query result as the user types, auto-selecting the substring portion
50092 * of the first result that the user has not yet typed.
50093 *
50094 * @property typeAhead
50095 * @type Boolean
50096 * @default false
50097 */
50098YAHOO.widget.AutoComplete.prototype.typeAhead = false;
50099
50100/**
50101 * Whether or not to animate the expansion/collapse of the results container in the
50102 * horizontal direction.
50103 *
50104 * @property animHoriz
50105 * @type Boolean
50106 * @default false
50107 */
50108YAHOO.widget.AutoComplete.prototype.animHoriz = false;
50109
50110/**
50111 * Whether or not to animate the expansion/collapse of the results container in the
50112 * vertical direction.
50113 *
50114 * @property animVert
50115 * @type Boolean
50116 * @default true
50117 */
50118YAHOO.widget.AutoComplete.prototype.animVert = true;
50119
50120/**
50121 * Speed of container expand/collapse animation, in seconds..
50122 *
50123 * @property animSpeed
50124 * @type Number
50125 * @default 0.3
50126 */
50127YAHOO.widget.AutoComplete.prototype.animSpeed = 0.3;
50128
50129/**
50130 * Whether or not to force the user's selection to match one of the query
50131 * results. Enabling this feature essentially transforms the input field into a
50132 * &lt;select&gt; field. This feature is not recommended with delimiter character(s)
50133 * defined.
50134 *
50135 * @property forceSelection
50136 * @type Boolean
50137 * @default false
50138 */
50139YAHOO.widget.AutoComplete.prototype.forceSelection = false;
50140
50141/**
50142 * Whether or not to allow browsers to cache user-typed input in the input
50143 * field. Disabling this feature will prevent the widget from setting the
50144 * autocomplete="off" on the input field. When autocomplete="off"
50145 * and users click the back button after form submission, user-typed input can
50146 * be prefilled by the browser from its cache. This caching of user input may
50147 * not be desired for sensitive data, such as credit card numbers, in which
50148 * case, implementers should consider setting allowBrowserAutocomplete to false.
50149 *
50150 * @property allowBrowserAutocomplete
50151 * @type Boolean
50152 * @default true
50153 */
50154YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete = true;
50155
50156/**
50157 * Enabling this feature prevents the toggling of the container to a collapsed state.
50158 * Setting to true does not automatically trigger the opening of the container.
50159 * Implementers are advised to pre-load the container with an explicit "sendQuery()" call.   
50160 *
50161 * @property alwaysShowContainer
50162 * @type Boolean
50163 * @default false
50164 */
50165YAHOO.widget.AutoComplete.prototype.alwaysShowContainer = false;
50166
50167/**
50168 * Whether or not to use an iFrame to layer over Windows form elements in
50169 * IE. Set to true only when the results container will be on top of a
50170 * &lt;select&gt; field in IE and thus exposed to the IE z-index bug (i.e.,
50171 * 5.5 < IE < 7).
50172 *
50173 * @property useIFrame
50174 * @type Boolean
50175 * @default false
50176 */
50177YAHOO.widget.AutoComplete.prototype.useIFrame = false;
50178
50179/**
50180 * Whether or not the results container should have a shadow.
50181 *
50182 * @property useShadow
50183 * @type Boolean
50184 * @default false
50185 */
50186YAHOO.widget.AutoComplete.prototype.useShadow = false;
50187
50188/**
50189 * Whether or not the input field should be updated with selections.
50190 *
50191 * @property suppressInputUpdate
50192 * @type Boolean
50193 * @default false
50194 */
50195YAHOO.widget.AutoComplete.prototype.suppressInputUpdate = false;
50196
50197/**
50198 * For backward compatibility to pre-2.6.0 formatResults() signatures, setting
50199 * resultsTypeList to true will take each object literal result returned by
50200 * DataSource and flatten into an array.  
50201 *
50202 * @property resultTypeList
50203 * @type Boolean
50204 * @default true
50205 */
50206YAHOO.widget.AutoComplete.prototype.resultTypeList = true;
50207
50208/**
50209 * For XHR DataSources, AutoComplete will automatically insert a "?" between the server URI and 
50210 * the "query" param/value pair. To prevent this behavior, implementers should
50211 * set this value to false. To more fully customize the query syntax, implementers
50212 * should override the generateRequest() method.
50213 *
50214 * @property queryQuestionMark
50215 * @type Boolean
50216 * @default true
50217 */
50218YAHOO.widget.AutoComplete.prototype.queryQuestionMark = true;
50219
50220/**
50221 * If true, before each time the container expands, the container element will be
50222 * positioned to snap to the bottom-left corner of the input element. If
50223 * autoSnapContainer is set to false, this positioning will not be done.  
50224 *
50225 * @property autoSnapContainer
50226 * @type Boolean
50227 * @default true
50228 */
50229YAHOO.widget.AutoComplete.prototype.autoSnapContainer = true;
50230
50231/////////////////////////////////////////////////////////////////////////////
50232//
50233// Public methods
50234//
50235/////////////////////////////////////////////////////////////////////////////
50236
50237 /**
50238 * Public accessor to the unique name of the AutoComplete instance.
50239 *
50240 * @method toString
50241 * @return {String} Unique name of the AutoComplete instance.
50242 */
50243YAHOO.widget.AutoComplete.prototype.toString = function() {
50244    return "AutoComplete " + this._sName;
50245};
50246
50247 /**
50248 * Returns DOM reference to input element.
50249 *
50250 * @method getInputEl
50251 * @return {HTMLELement} DOM reference to input element.
50252 */
50253YAHOO.widget.AutoComplete.prototype.getInputEl = function() {
50254    return this._elTextbox;
50255};
50256
50257 /**
50258 * Returns DOM reference to container element.
50259 *
50260 * @method getContainerEl
50261 * @return {HTMLELement} DOM reference to container element.
50262 */
50263YAHOO.widget.AutoComplete.prototype.getContainerEl = function() {
50264    return this._elContainer;
50265};
50266
50267 /**
50268 * Returns true if widget instance is currently active.
50269 *
50270 * @method isFocused
50271 * @return {Boolean} Returns true if widget instance is currently active.
50272 */
50273YAHOO.widget.AutoComplete.prototype.isFocused = function() {
50274    return this._bFocused;
50275};
50276
50277 /**
50278 * Returns true if container is in an expanded state, false otherwise.
50279 *
50280 * @method isContainerOpen
50281 * @return {Boolean} Returns true if container is in an expanded state, false otherwise.
50282 */
50283YAHOO.widget.AutoComplete.prototype.isContainerOpen = function() {
50284    return this._bContainerOpen;
50285};
50286
50287/**
50288 * Public accessor to the &lt;ul&gt; element that displays query results within the results container.
50289 *
50290 * @method getListEl
50291 * @return {HTMLElement[]} Reference to &lt;ul&gt; element within the results container.
50292 */
50293YAHOO.widget.AutoComplete.prototype.getListEl = function() {
50294    return this._elList;
50295};
50296
50297/**
50298 * Public accessor to the matching string associated with a given &lt;li&gt; result.
50299 *
50300 * @method getListItemMatch
50301 * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
50302 * @return {String} Matching string.
50303 */
50304YAHOO.widget.AutoComplete.prototype.getListItemMatch = function(elListItem) {
50305    if(elListItem._sResultMatch) {
50306        return elListItem._sResultMatch;
50307    }
50308    else {
50309        return null;
50310    }
50311};
50312
50313/**
50314 * Public accessor to the result data associated with a given &lt;li&gt; result.
50315 *
50316 * @method getListItemData
50317 * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
50318 * @return {Object} Result data.
50319 */
50320YAHOO.widget.AutoComplete.prototype.getListItemData = function(elListItem) {
50321    if(elListItem._oResultData) {
50322        return elListItem._oResultData;
50323    }
50324    else {
50325        return null;
50326    }
50327};
50328
50329/**
50330 * Public accessor to the index of the associated with a given &lt;li&gt; result.
50331 *
50332 * @method getListItemIndex
50333 * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
50334 * @return {Number} Index.
50335 */
50336YAHOO.widget.AutoComplete.prototype.getListItemIndex = function(elListItem) {
50337    if(YAHOO.lang.isNumber(elListItem._nItemIndex)) {
50338        return elListItem._nItemIndex;
50339    }
50340    else {
50341        return null;
50342    }
50343};
50344
50345/**
50346 * Sets HTML markup for the results container header. This markup will be
50347 * inserted within a &lt;div&gt;
50347 tag with a class of "yui-ac-hd".
50348 *
50349 * @method setHeader
50350 * @param sHeader {HTML} HTML markup for results container header.
50351 */
50352YAHOO.widget.AutoComplete.prototype.setHeader = function(sHeader) {
50353    if(this._elHeader) {
50354        var elHeader = this._elHeader;
50355        if(sHeader) {
50356            elHeader.innerHTML = sHeader;
50357            elHeader.style.display = "";
50358        }
50359        else {
50360            elHeader.innerHTML = "";
50361            elHeader.style.display = "none";
50362        }
50363    }
50364};
50365
50366/**
50367 * Sets HTML markup for the results container footer. This markup will be
50368 * inserted within a &lt;div&gt; tag with a class of "yui-ac-ft".
50369 *
50370 * @method setFooter
50371 * @param sFooter {HTML} HTML markup for results container footer.
50372 */
50373YAHOO.widget.AutoComplete.prototype.setFooter = function(sFooter) {
50374    if(this._elFooter) {
50375        var elFooter = this._elFooter;
50376        if(sFooter) {
50377                elFooter.innerHTML = sFooter;
50378                elFooter.style.display = "";
50379        }
50380        else {
50381            elFooter.innerHTML = "";
50382            elFooter.style.display = "none";
50383        }
50384    }
50385};
50386
50387/**
50388 * Sets HTML markup for the results container body. This markup will be
50389 * inserted within a &lt;div&gt; tag with a class of "yui-ac-bd".
50390 *
50391 * @method setBody
50392 * @param sBody {HTML} HTML markup for results container body.
50393 */
50394YAHOO.widget.AutoComplete.prototype.setBody = function(sBody) {
50395    if(this._elBody) {
50396        var elBody = this._elBody;
50397        YAHOO.util.Event.purgeElement(elBody, true);
50398        if(sBody) {
50399            elBody.innerHTML = sBody;
50400            elBody.style.display = "";
50401        }
50402        else {
50403            elBody.innerHTML = "";
50404            elBody.style.display = "none";
50405        }
50406        this._elList = null;
50407    }
50408};
50409
50410/**
50411* A function that converts an AutoComplete query into a request value which is then
50412* passed to the DataSource's sendRequest method in order to retrieve data for 
50413* the query. By default, returns a String with the syntax: "query={query}"
50414* Implementers can customize this method for custom request syntaxes.
50415* 
50416* @method generateRequest
50417* @param sQuery {String} Query string
50418* @return {MIXED} Request
50419*/
50420YAHOO.widget.AutoComplete.prototype.generateRequest = function(sQuery) {
50421    var dataType = this.dataSource.dataType;
50422    
50423    // Transform query string in to a request for remote data
50424    // By default, local data doesn't need a transformation, just passes along the query as is.
50425    if(dataType === YAHOO.util.DataSourceBase.TYPE_XHR) {
50426        // By default, XHR GET requests look like "{scriptURI}?{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
50427        if(!this.dataSource.connMethodPost) {
50428            sQuery = (this.queryQuestionMark ? "?" : "") + (this.dataSource.scriptQueryParam || "query") + "=" + sQuery + 
50429                (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");        
50430        }
50431        // By default, XHR POST bodies are sent to the {scriptURI} like "{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
50432        else {
50433            sQuery = (this.dataSource.scriptQueryParam || "query") + "=" + sQuery + 
50434                (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");
50435        }
50436    }
50437    // By default, remote script node requests look like "{scriptURI}&{scriptCallbackParam}={callbackString}&{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
50438    else if(dataType === YAHOO.util.DataSourceBase.TYPE_SCRIPTNODE) {
50439        sQuery = "&" + (this.dataSource.scriptQueryParam || "query") + "=" + sQuery + 
50440            (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");    
50441    }
50442    
50443    return sQuery;
50444};
50445
50446/**
50447 * Makes query request to the DataSource.
50448 *
50449 * @method sendQuery
50450 * @param sQuery {String} Query string.
50451 */
50452YAHOO.widget.AutoComplete.prototype.sendQuery = function(sQuery) {
50453    // Activate focus for a new interaction
50454    this._bFocused = true;
50455    
50456    // Adjust programatically sent queries to look like they were input by user
50457    // when delimiters are enabled
50458    var newQuery = (this.delimChar) ? this._elTextbox.value + sQuery : sQuery;
50459    this._sendQuery(newQuery);
50460};
50461
50462/**
50463 * Snaps container to bottom-left corner of input element
50464 *
50465 * @method snapContainer
50466 */
50467YAHOO.widget.AutoComplete.prototype.snapContainer = function() {
50468    var oTextbox = this._elTextbox,
50469        pos = YAHOO.util.Dom.getXY(oTextbox);
50470    pos[1] += YAHOO.util.Dom.get(oTextbox).offsetHeight + 2;
50471    YAHOO.util.Dom.setXY(this._elContainer,pos);
50472};
50473
50474/**
50475 * Expands container.
50476 *
50477 * @method expandContainer
50478 */
50479YAHOO.widget.AutoComplete.prototype.expandContainer = function() {
50480    this._toggleContainer(true);
50481};
50482
50483/**
50484 * Collapses container.
50485 *
50486 * @method collapseContainer
50487 */
50488YAHOO.widget.AutoComplete.prototype.collapseContainer = function() {
50489    this._toggleContainer(false);
50490};
50491
50492/**
50493 * Clears entire list of suggestions.
50494 *
50495 * @method clearList
50496 */
50497YAHOO.widget.AutoComplete.prototype.clearList = function() {
50498    var allItems = this._elList.childNodes,
50499        i=allItems.length-1;
50500    for(; i>-1; i--) {
50501          allItems[i].style.display = "none";
50502    }
50503};
50504
50505/**
50506 * Handles subset matching for when queryMatchSubset is enabled.
50507 *
50508 * @method getSubsetMatches
50509 * @param sQuery {String} Query string.
50510 * @return {Object} oParsedResponse or null. 
50511 */
50512YAHOO.widget.AutoComplete.prototype.getSubsetMatches = function(sQuery) {
50513    var subQuery, oCachedResponse, subRequest;
50514    // Loop through substrings of each cached element's query property...
50515    for(var i = sQuery.length; i >= this.minQueryLength ; i--) {
50516        subRequest = this.generateRequest(sQuery.substr(0,i));
50517        this.dataRequestEvent.fire(this, subQuery, subRequest);
50518        YAHOO.log("Searching for query subset \"" + subQuery + "\" in cache", "info", this.toString());
50519        
50520        // If a substring of the query is found in the cache
50521        oCachedResponse = this.dataSource.getCachedResponse(subRequest);
50522        if(oCachedResponse) {
50523            YAHOO.log("Found match for query subset \"" + subQuery + "\": " + YAHOO.lang.dump(oCachedResponse), "info", this.toString());
50524            return this.filterResults.apply(this.dataSource, [sQuery, oCachedResponse, oCachedResponse, {scope:this}]);
50525        }
50526    }
50527    YAHOO.log("Did not find subset match for query subset \"" + sQuery + "\"" , "info", this.toString());
50528    return null;
50529};
50530
50531/**
50532 * Executed by DataSource (within DataSource scope via doBeforeParseData()) to
50533 * handle responseStripAfter cleanup.
50534 *
50535 * @method preparseRawResponse
50536 * @param sQuery {String} Query string.
50537 * @return {Object} oParsedResponse or null. 
50538 */
50539YAHOO.widget.AutoComplete.prototype.preparseRawResponse = function(oRequest, oFullResponse, oCallback) {
50540    var nEnd = ((this.responseStripAfter !== "") && (oFullResponse.indexOf)) ?
50541        oFullResponse.indexOf(this.responseStripAfter) : -1;
50542    if(nEnd != -1) {
50543        oFullResponse = oFullResponse.substring(0,nEnd);
50544    }
50545    return oFullResponse;
50546};
50547
50548/**
50549 * Executed by DataSource (within DataSource scope via doBeforeCallback()) to
50550 * filter results through a simple client-side matching algorithm. 
50551 *
50552 * @method filterResults
50553 * @param sQuery {String} Original request.
50554 * @param oFullResponse {Object} Full response object.
50555 * @param oParsedResponse {Object} Parsed response object.
50556 * @param oCallback {Object} Callback object. 
50557 * @return {Object} Filtered response object.
50558 */
50559
50560YAHOO.widget.AutoComplete.prototype.filterResults = function(sQuery, oFullResponse, oParsedResponse, oCallback) {
50561    // If AC has passed a query string value back to itself, grab it
50562    if(oCallback && oCallback.argument && YAHOO.lang.isValue(oCallback.argument.query)) {
50563        sQuery = oCallback.argument.query;
50564    }
50565
50566    // Only if a query string is available to match against
50567    if(sQuery && sQuery !== "") {
50568        // First make a copy of the oParseResponse
50569        oParsedResponse = YAHOO.widget.AutoComplete._cloneObject(oParsedResponse);
50570        
50571        var oAC = oCallback.scope,
50572            oDS = this,
50573            allResults = oParsedResponse.results, // the array of results
50574            filteredResults = [], // container for filtered results,
50575            nMax = oAC.maxResultsDisplayed, // max to find
50576            bMatchCase = (oDS.queryMatchCase || oAC.queryMatchCase), // backward compat
50577            bMatchContains = (oDS.queryMatchContains || oAC.queryMatchContains); // backward compat
50578            
50579        // Loop through each result object...
50580        for(var i=0, len=allResults.length; i<len; i++) {
50581            var oResult = allResults[i];
50582
50583            // Grab the data to match against from the result object...
50584            var sResult = null;
50585            
50586            // Result object is a simple string already
50587            if(YAHOO.lang.isString(oResult)) {
50588                sResult = oResult;
50589            }
50590            // Result object is an array of strings
50591            else if(YAHOO.lang.isArray(oResult)) {
50592                sResult = oResult[0];
50593            
50594            }
50595            // Result object is an object literal of strings
50596            else if(this.responseSchema.fields) {
50597                var key = this.responseSchema.fields[0].key || this.responseSchema.fields[0];
50598                sResult = oResult[key];
50599            }
50600            // Backwards compatibility
50601            else if(this.key) {
50602                sResult = oResult[this.key];
50603            }
50604            
50605            if(YAHOO.lang.isString(sResult)) {
50606                
50607                var sKeyIndex = (bMatchCase) ?
50608                sResult.indexOf(decodeURIComponent(sQuery)) :
50609                sResult.toLowerCase().indexOf(decodeURIComponent(sQuery).toLowerCase());
50610
50611                // A STARTSWITH match is when the query is found at the beginning of the key string...
50612                if((!bMatchContains && (sKeyIndex === 0)) ||
50613                // A CONTAINS match is when the query is found anywhere within the key string...
50614                (bMatchContains && (sKeyIndex > -1))) {
50615                    // Stash the match
50616                    filteredResults.push(oResult);
50617                }
50618            }
50619            
50620            // Filter no more if maxResultsDisplayed is reached
50621            if(len>nMax && filteredResults.length===nMax) {
50622                break;
50623            }
50624        }
50625        oParsedResponse.results = filteredResults;
50626        YAHOO.log("Filtered " + filteredResults.length + " results against query \""  + sQuery + "\": " + YAHOO.lang.dump(filteredResults), "info", this.toString());
50627    }
50628    else {
50629        YAHOO.log("Did not filter results against query", "info", this.toString());
50630    }
50631    
50632    return oParsedResponse;
50633};
50634
50635/**
50636 * Handles response for display. This is the callback function method passed to
50637 * YAHOO.util.DataSourceBase#sendRequest so results from the DataSource are
50638 * returned to the AutoComplete instance.
50639 *
50640 * @method handleResponse
50641 * @param sQuery {String} Original request.
50642 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
50643 * @param oPayload {MIXED} (optional) Additional argument(s)
50644 */
50645YAHOO.widget.AutoComplete.prototype.handleResponse = function(sQuery, oResponse, oPayload) {
50646    if((this instanceof YAHOO.widget.AutoComplete) && this._sName) {
50647        this._populateList(sQuery, oResponse, oPayload);
50648    }
50649};
50650
50651/**
50652 * Overridable method called before container is loaded with result data.
50653 *
50654 * @method doBeforeLoadData
50655 * @param sQuery {String} Original request.
50656 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
50657 * @param oPayload {MIXED} (optional) Additional argument(s)
50658 * @return {Boolean} Return true to continue loading data, false to cancel.
50659 */
50660YAHOO.widget.AutoComplete.prototype.doBeforeLoadData = function(sQuery, oResponse, oPayload) {
50661    return true;
50662};
50663
50664/**
50665 * Overridable method that returns HTML markup for one result to be populated
50666 * as innerHTML of an &lt;LI&gt; element. 
50667 *
50668 * @method formatResult
50669 * @param oResultData {Object} Result data object.
50670 * @param sQuery {String} The corresponding query string.
50671 * @param sResultMatch {HTMLElement} The current query string. 
50672 * @return {HTML} HTML markup of formatted result data.
50673 */
50674YAHOO.widget.AutoComplete.prototype.formatResult = function(oResultData, sQuery, sResultMatch) {
50675    var sMarkup = (sResultMatch) ? sResultMatch : "";
50676    return sMarkup;
50677};
50678
50679/**
50680 * An alternative to the formatResult() method, escapes the result data before
50681 * inserting into DOM. Implementers should point to this method when accessing
50682 * data from third-party sources, from user input, or from otherwise
50683 * untrustworthy sources:
50684 * myAutoComplete.formatResult = myAutoComplete.formatEscapedResult;
50685 *
50686 * @method formatEscapedResult
50687 * @param oResultData {Object} Result data object.
50688 * @param sQuery {String} The corresponding query string.
50689 * @param sResultMatch {HTMLElement} The current query string.
50690 * @return {String} Formatted result data.
50691 */
50692YAHOO.widget.AutoComplete.prototype.formatEscapedResult = function(oResultData, sQuery, sResultMatch) {
50693    var sResult = (sResultMatch) ? sResultMatch : "";
50694    return YAHOO.lang.escapeHTML(sResult);
50695};
50696
50697/**
50698 * Overridable method called before container expands allows implementers to access data
50699 * and DOM elements.
50700 *
50701 * @method doBeforeExpandContainer
50702 * @param elTextbox {HTMLElement} The text input box.
50703 * @param elContainer {HTMLElement} The container element.
50704 * @param sQuery {String} The query string.
50705 * @param aResults {Object[]}  An array of query results.
50706 * @return {Boolean} Return true to continue expanding container, false to cancel the expand.
50707 */
50708YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer = function(elTextbox, elContainer, sQuery, aResults) {
50709    return true;
50710};
50711
50712
50713/**
50714 * Nulls out the entire AutoComplete instance and related objects, removes attached
50715 * event listeners, and clears out DOM elements inside the container. After
50716 * calling this method, the instance reference should be expliclitly nulled by
50717 * implementer, as in myAutoComplete = null. Use with caution!
50718 *
50719 * @method destroy
50720 */
50721YAHOO.widget.AutoComplete.prototype.destroy = function() {
50722    var instanceName = this.toString();
50723    var elInput = this._elTextbox;
50724    var elContainer = this._elContainer;
50725
50726    // Unhook custom events
50727    this.textboxFocusEvent.unsubscribeAll();
50728    this.textboxKeyEvent.unsubscribeAll();
50729    this.dataRequestEvent.unsubscribeAll();
50730    this.dataReturnEvent.unsubscribeAll();
50731    this.dataErrorEvent.unsubscribeAll();
50732    this.containerPopulateEvent.unsubscribeAll();
50733    this.containerExpandEvent.unsubscribeAll();
50734    this.typeAheadEvent.unsubscribeAll();
50735    this.itemMouseOverEvent.unsubscribeAll();
50736    this.itemMouseOutEvent.unsubscribeAll();
50737    this.itemArrowToEvent.unsubscribeAll();
50738    this.itemArrowFromEvent.unsubscribeAll();
50739    this.itemSelectEvent.unsubscribeAll();
50740    this.unmatchedItemSelectEvent.unsubscribeAll();
50741    this.selectionEnforceEvent.unsubscribeAll();
50742    this.containerCollapseEvent.unsubscribeAll();
50743    this.textboxBlurEvent.unsubscribeAll();
50744    this.textboxChangeEvent.unsubscribeAll();
50745
50746    // Unhook DOM events
50747    YAHOO.util.Event.purgeElement(elInput, true);
50748    YAHOO.util.Event.purgeElement(elContainer, true);
50749
50750    // Remove DOM elements
50751    elContainer.innerHTML = "";
50752
50753    // Null out objects
50754    for(var key in this) {
50755        if(YAHOO.lang.hasOwnProperty(this, key)) {
50756            this[key] = null;
50757        }
50758    }
50759
50760    YAHOO.log("AutoComplete instance destroyed: " + instanceName);
50761};
50762
50763/////////////////////////////////////////////////////////////////////////////
50764//
50765// Public events
50766//
50767/////////////////////////////////////////////////////////////////////////////
50768
50769/**
50770 * Fired when the input field receives focus.
50771 *
50772 * @event textboxFocusEvent
50773 * @param type {String} Name of the event.
50774 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50775 */
50776YAHOO.widget.AutoComplete.prototype.textboxFocusEvent = null;
50777
50778/**
50779 * Fired when the input field receives key input.
50780 *
50781 * @event textboxKeyEvent
50782 * @param type {String} Name of the event.
50783 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50784 * @param args[1] {Number} The keycode number.
50785 */
50786YAHOO.widget.AutoComplete.prototype.textboxKeyEvent = null;
50787
50788/**
50789 * Fired when the AutoComplete instance makes a request to the DataSource.
50790 * 
50791 * @event dataRequestEvent
50792 * @param type {String} Name of the event.
50793 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50794 * @param args[1] {String} The query string.
50795 * @param args[2] {Object} The request.
50796 */
50797YAHOO.widget.AutoComplete.prototype.dataRequestEvent = null;
50798
50799/**
50800 * Fired when the AutoComplete request to the DataSource is canceled.
50801 *
50802 * @event dataRequestCancelEvent
50803 * @param type {String} Name of the event.
50804 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50805 * @param args[1] {String} The query string.
50806 */
50807YAHOO.widget.AutoComplete.prototype.dataRequestCancelEvent = null;
50808
50809/**
50810 * Fired when the AutoComplete instance receives query results from the data
50811 * source.
50812 *
50813 * @event dataReturnEvent
50814 * @param type {String} Name of the event.
50815 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50816 * @param args[1] {String} The query string.
50817 * @param args[2] {Object[]} Results array.
50818 */
50819YAHOO.widget.AutoComplete.prototype.dataReturnEvent = null;
50820
50821/**
50822 * Fired when the AutoComplete instance does not receive query results from the
50823 * DataSource due to an error.
50824 *
50825 * @event dataErrorEvent
50826 * @param type {String} Name of the event.
50827 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50828 * @param args[1] {String} The query string.
50829 * @param args[2] {Object} The response object, if available.
50830 */
50831YAHOO.widget.AutoComplete.prototype.dataErrorEvent = null;
50832
50833/**
50834 * Fired when the results container is populated.
50835 *
50836 * @event containerPopulateEvent
50837 * @param type {String} Name of the event.
50838 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50839 */
50840YAHOO.widget.AutoComplete.prototype.containerPopulateEvent = null;
50841
50842/**
50843 * Fired when the results container is expanded.
50844 *
50845 * @event containerExpandEvent
50846 * @param type {String} Name of the event.
50847 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50848 */
50849YAHOO.widget.AutoComplete.prototype.containerExpandEvent = null;
50850
50851/**
50852 * Fired when the input field has been prefilled by the type-ahead
50853 * feature. 
50854 *
50855 * @event typeAheadEvent
50856 * @param type {String} Name of the event.
50857 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50858 * @param args[1] {String} The query string.
50859 * @param args[2] {String} The prefill string.
50860 */
50861YAHOO.widget.AutoComplete.prototype.typeAheadEvent = null;
50862
50863/**
50864 * Fired when result item has been moused over.
50865 *
50866 * @event itemMouseOverEvent
50867 * @param type {String} Name of the event.
50868 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50869 * @param args[1] {HTMLElement} The &lt;li&gt element item moused to.
50870 */
50871YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent = null;
50872
50873/**
50874 * Fired when result item has been moused out.
50875 *
50876 * @event itemMouseOutEvent
50877 * @param type {String} Name of the event.
50878 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50879 * @param args[1] {HTMLElement} The &lt;li&gt; element item moused from.
50880 */
50881YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent = null;
50882
50883/**
50884 * Fired when result item has been arrowed to. 
50885 *
50886 * @event itemArrowToEvent
50887 * @param type {String} Name of the event.
50888 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50889 * @param args[1] {HTMLElement} The &lt;li&gt; element item arrowed to.
50890 */
50891YAHOO.widget.AutoComplete.prototype.itemArrowToEvent = null;
50892
50893/**
50894 * Fired when result item has been arrowed away from.
50895 *
50896 * @event itemArrowFromEvent
50897 * @param type {String} Name of the event.
50898 * @param args[0[ {YAHOO.widget.AutoComplete} The AutoComplete instance.
50899 * @param args[1] {HTMLElement} The &lt;li&gt; element item arrowed from.
50900 */
50901YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent = null;
50902
50903/**
50904 * Fired when an item is selected via mouse click, ENTER key, or TAB key.
50905 *
50906 * @event itemSelectEvent
50907 * @param type {String} Name of the event.
50908 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50909 * @param args[1] {HTMLElement} The selected &lt;li&gt; element item.
50910 * @param args[2] {Object} The data returned for the item, either as an object,
50911 * or mapped from the schema into an array.
50912 */
50913YAHOO.widget.AutoComplete.prototype.itemSelectEvent = null;
50914
50915/**
50916 * Fired when a user selection does not match any of the displayed result items.
50917 *
50918 * @event unmatchedItemSelectEvent
50919 * @param type {String} Name of the event.
50920 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50921 * @param args[1] {String} The selected string.
50922 */
50923YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent = null;
50924
50925/**
50926 * Fired if forceSelection is enabled and the user's input has been cleared
50927 * because it did not match one of the returned query results.
50928 *
50929 * @event selectionEnforceEvent
50930 * @param type {String} Name of the event.
50931 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50932 * @param args[1] {String} The cleared value (including delimiters if applicable).
50933 */
50934YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent = null;
50935
50936/**
50937 * Fired when the results container is collapsed.
50938 *
50939 * @event containerCollapseEvent
50940 * @param type {String} Name of the event.
50941 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50942 */
50943YAHOO.widget.AutoComplete.prototype.containerCollapseEvent = null;
50944
50945/**
50946 * Fired when the input field loses focus.
50947 *
50948 * @event textboxBlurEvent
50949 * @param type {String} Name of the event.
50950 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50951 */
50952YAHOO.widget.AutoComplete.prototype.textboxBlurEvent = null;
50953
50954/**
50955 * Fired when the input field value has changed when it loses focus.
50956 *
50957 * @event textboxChangeEvent
50958 * @param type {String} Name of the event.
50959 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
50960 */
50961YAHOO.widget.AutoComplete.prototype.textboxChangeEvent = null;
50962
50963/////////////////////////////////////////////////////////////////////////////
50964//
50965// Private member variables
50966//
50967/////////////////////////////////////////////////////////////////////////////
50968
50969/**
50970 * Internal class variable to index multiple AutoComplete instances.
50971 *
50972 * @property _nIndex
50973 * @type Number
50974 * @default 0
50975 * @private
50976 */
50977YAHOO.widget.AutoComplete._nIndex = 0;
50978
50979/**
50980 * Name of AutoComplete instance.
50981 *
50982 * @property _sName
50983 * @type String
50984 * @private
50985 */
50986YAHOO.widget.AutoComplete.prototype._sName = null;
50987
50988/**
50989 * Text input field DOM element.
50990 *
50991 * @property _elTextbox
50992 * @type HTMLElement
50993 * @private
50994 */
50995YAHOO.widget.AutoComplete.prototype._elTextbox = null;
50996
50997/**
50998 * Container DOM element.
50999 *
51000 * @property _elContainer
51001 * @type HTMLElement
51002 * @private
51003 */
51004YAHOO.widget.AutoComplete.prototype._elContainer = null;
51005
51006/**
51007 * Reference to content element within container element.
51008 *
51009 * @property _elContent
51010 * @type HTMLElement
51011 * @private
51012 */
51013YAHOO.widget.AutoComplete.prototype._elContent = null;
51014
51015/**
51016 * Reference to header element within content element.
51017 *
51018 * @property _elHeader
51019 * @type HTMLElement
51020 * @private
51021 */
51022YAHOO.widget.AutoComplete.prototype._elHeader = null;
51023
51024/**
51025 * Reference to body element within content element.
51026 *
51027 * @property _elBody
51028 * @type HTMLElement
51029 * @private
51030 */
51031YAHOO.widget.AutoComplete.prototype._elBody = null;
51032
51033/**
51034 * Reference to footer element within content element.
51035 *
51036 * @property _elFooter
51037 * @type HTMLElement
51038 * @private
51039 */
51040YAHOO.widget.AutoComplete.prototype._elFooter = null;
51041
51042/**
51043 * Reference to shadow element within container element.
51044 *
51045 * @property _elShadow
51046 * @type HTMLElement
51047 * @private
51048 */
51049YAHOO.widget.AutoComplete.prototype._elShadow = null;
51050
51051/**
51052 * Reference to iframe element within container element.
51053 *
51054 * @property _elIFrame
51055 * @type HTMLElement
51056 * @private
51057 */
51058YAHOO.widget.AutoComplete.prototype._elIFrame = null;
51059
51060/**
51061 * Whether or not the widget instance is currently active. If query results come back
51062 * but the user has already moved on, do not proceed with auto complete behavior.
51063 *
51064 * @property _bFocused
51065 * @type Boolean
51066 * @private
51067 */
51068YAHOO.widget.AutoComplete.prototype._bFocused = false;
51069
51070/**
51071 * Animation instance for container expand/collapse.
51072 *
51073 * @property _oAnim
51074 * @type Boolean
51075 * @private
51076 */
51077YAHOO.widget.AutoComplete.prototype._oAnim = null;
51078
51079/**
51080 * Whether or not the results container is currently open.
51081 *
51082 * @property _bContainerOpen
51083 * @type Boolean
51084 * @private
51085 */
51086YAHOO.widget.AutoComplete.prototype._bContainerOpen = false;
51087
51088/**
51089 * Whether or not the mouse is currently over the results
51090 * container. This is necessary in order to prevent clicks on container items
51091 * from being text input field blur events.
51092 *
51093 * @property _bOverContainer
51094 * @type Boolean
51095 * @private
51096 */
51097YAHOO.widget.AutoComplete.prototype._bOverContainer = false;
51098
51099/**
51100 * Internal reference to &lt;ul&gt; elements that contains query results within the
51101 * results container.
51102 *
51103 * @property _elList
51104 * @type HTMLElement
51105 * @private
51106 */
51107YAHOO.widget.AutoComplete.prototype._elList = null;
51108
51109/*
51110 * Array of &lt;li&gt; elements references that contain query results within the
51111 * results container.
51112 *
51113 * @property _aListItemEls
51114 * @type HTMLElement[]
51115 * @private
51116 */
51117//YAHOO.widget.AutoComplete.prototype._aListItemEls = null;
51118
51119/**
51120 * Number of &lt;li&gt; elements currently displayed in results container.
51121 *
51122 * @property _nDisplayedItems
51123 * @type Number
51124 * @private
51125 */
51126YAHOO.widget.AutoComplete.prototype._nDisplayedItems = 0;
51127
51128/*
51129 * Internal count of &lt;li&gt; elements displayed and hidden in results container.
51130 *
51131 * @property _maxResultsDisplayed
51132 * @type Number
51133 * @private
51134 */
51135//YAHOO.widget.AutoComplete.prototype._maxResultsDisplayed = 0;
51136
51137/**
51138 * Current query string
51139 *
51140 * @property _sCurQuery
51141 * @type String
51142 * @private
51143 */
51144YAHOO.widget.AutoComplete.prototype._sCurQuery = null;
51145
51146/**
51147 * Selections from previous queries (for saving delimited queries).
51148 *
51149 * @property _sPastSelections
51150 * @type String
51151 * @default "" 
51152 * @private
51153 */
51154YAHOO.widget.AutoComplete.prototype._sPastSelections = "";
51155
51156/**
51157 * Stores initial input value used to determine if textboxChangeEvent should be fired.
51158 *
51159 * @property _sInitInputValue
51160 * @type String
51161 * @private
51162 */
51163YAHOO.widget.AutoComplete.prototype._sInitInputValue = null;
51164
51165/**
51166 * Pointer to the currently highlighted &lt;li&gt; element in the container.
51167 *
51168 * @property _elCurListItem
51169 * @type HTMLElement
51170 * @private
51171 */
51172YAHOO.widget.AutoComplete.prototype._elCurListItem = null;
51173
51174/**
51175 * Pointer to the currently pre-highlighted &lt;li&gt; element in the container.
51176 *
51177 * @property _elCurPrehighlightItem
51178 * @type HTMLElement
51179 * @private
51180 */
51181YAHOO.widget.AutoComplete.prototype._elCurPrehighlightItem = null;
51182
51183/**
51184 * Whether or not an item has been selected since the container was populated
51185 * with results. Reset to false by _populateList, and set to true when item is
51186 * selected.
51187 *
51188 * @property _bItemSelected
51189 * @type Boolean
51190 * @private
51191 */
51192YAHOO.widget.AutoComplete.prototype._bItemSelected = false;
51193
51194/**
51195 * Key code of the last key pressed in textbox.
51196 *
51197 * @property _nKeyCode
51198 * @type Number
51199 * @private
51200 */
51201YAHOO.widget.AutoComplete.prototype._nKeyCode = null;
51202
51203/**
51204 * Delay timeout ID.
51205 *
51206 * @property _nDelayID
51207 * @type Number
51208 * @private
51209 */
51210YAHOO.widget.AutoComplete.prototype._nDelayID = -1;
51211
51212/**
51213 * TypeAhead delay timeout ID.
51214 *
51215 * @property _nTypeAheadDelayID
51216 * @type Number
51217 * @private
51218 */
51219YAHOO.widget.AutoComplete.prototype._nTypeAheadDelayID = -1;
51220
51221/**
51222 * Src to iFrame used when useIFrame = true. Supports implementations over SSL
51223 * as well.
51224 *
51225 * @property _iFrameSrc
51226 * @type String
51227 * @private
51228 */
51229YAHOO.widget.AutoComplete.prototype._iFrameSrc = "javascript:false;";
51230
51231/**
51232 * For users typing via certain IMEs, queries must be triggered by intervals,
51233 * since key events yet supported across all browsers for all IMEs.
51234 *
51235 * @property _queryInterval
51236 * @type Object
51237 * @private
51238 */
51239YAHOO.widget.AutoComplete.prototype._queryInterval = null;
51240
51241/**
51242 * Internal tracker to last known textbox value, used to determine whether or not
51243 * to trigger a query via interval for certain IME users.
51244 *
51245 * @event _sLastTextboxValue
51246 * @type String
51247 * @private
51248 */
51249YAHOO.widget.AutoComplete.prototype._sLastTextboxValue = null;
51250
51251/////////////////////////////////////////////////////////////////////////////
51252//
51253// Private methods
51254//
51255/////////////////////////////////////////////////////////////////////////////
51256
51257/**
51258 * Updates and validates latest public config properties.
51259 *
51260 * @method __initProps
51261 * @private
51262 */
51263YAHOO.widget.AutoComplete.prototype._initProps = function() {
51264    // Correct any invalid values
51265    var minQueryLength = this.minQueryLength;
51266    if(!YAHOO.lang.isNumber(minQueryLength)) {
51267        this.minQueryLength = 1;
51268    }
51269    var maxResultsDisplayed = this.maxResultsDisplayed;
51270    if(!YAHOO.lang.isNumber(maxResultsDisplayed) || (maxResultsDisplayed < 1)) {
51271        this.maxResultsDisplayed = 10;
51272    }
51273    var queryDelay = this.queryDelay;
51274    if(!YAHOO.lang.isNumber(queryDelay) || (queryDelay < 0)) {
51275        this.queryDelay = 0.2;
51276    }
51277    var typeAheadDelay = this.typeAheadDelay;
51278    if(!YAHOO.lang.isNumber(typeAheadDelay) || (typeAheadDelay < 0)) {
51279        this.typeAheadDelay = 0.2;
51280    }
51281    var delimChar = this.delimChar;
51282    if(YAHOO.lang.isString(delimChar) && (delimChar.length > 0)) {
51283        this.delimChar = [delimChar];
51284    }
51285    else if(!YAHOO.lang.isArray(delimChar)) {
51286        this.delimChar = null;
51287    }
51288    var animSpeed = this.animSpeed;
51289    if((this.animHoriz || this.animVert) && YAHOO.util.Anim) {
51290        if(!YAHOO.lang.isNumber(animSpeed) || (animSpeed < 0)) {
51291            this.animSpeed = 0.3;
51292        }
51293        if(!this._oAnim ) {
51294            this._oAnim = new YAHOO.util.Anim(this._elContent, {}, this.animSpeed);
51295        }
51296        else {
51297            this._oAnim.duration = this.animSpeed;
51298        }
51299    }
51300    if(this.forceSelection && delimChar) {
51301        YAHOO.log("The forceSelection feature has been enabled with delimChar defined.","warn", this.toString());
51302    }
51303};
51304
51305/**
51306 * Initializes the results container helpers if they are enabled and do
51307 * not exist
51308 *
51309 * @method _initContainerHelperEls
51310 * @private
51311 */
51312YAHOO.widget.AutoComplete.prototype._initContainerHelperEls = function() {
51313    if(this.useShadow && !this._elShadow) {
51314        var elShadow = document.createElement("div");
51315        elShadow.className = "yui-ac-shadow";
51316        elShadow.style.width = 0;
51317        elShadow.style.height = 0;
51318        this._elShadow = this._elContainer.appendChild(elShadow);
51319    }
51320    if(this.useIFrame && !this._elIFrame) {
51321        var elIFrame = document.createElement("iframe");
51322        elIFrame.src = this._iFrameSrc;
51323        elIFrame.frameBorder = 0;
51324        elIFrame.scrolling = "no";
51325        elIFrame.style.position = "absolute";
51326        elIFrame.style.width = 0;
51327        elIFrame.style.height = 0;
51328        elIFrame.style.padding = 0;
51329        elIFrame.tabIndex = -1;
51330        elIFrame.role = "presentation";
51331        elIFrame.title = "Presentational iframe shim";
51332        this._elIFrame = this._elContainer.appendChild(elIFrame);
51333    }
51334};
51335
51336/**
51337 * Initializes the results container once at object creation
51338 *
51339 * @method _initContainerEl
51340 * @private
51341 */
51342YAHOO.widget.AutoComplete.prototype._initContainerEl = function() {
51343    YAHOO.util.Dom.addClass(this._elContainer, "yui-ac-container");
51344    
51345    if(!this._elContent) {
51346        // The elContent div is assigned DOM listeners and 
51347        // helps size the iframe and shadow properly
51348        var elContent = document.createElement("div");
51349        elContent.className = "yui-ac-content";
51350        elContent.style.display = "none";
51351
51352        this._elContent = this._elContainer.appendChild(elContent);
51353
51354        var elHeader = document.createElement("div");
51355        elHeader.className = "yui-ac-hd";
51356        elHeader.style.display = "none";
51357        this._elHeader = this._elContent.appendChild(elHeader);
51358
51359        var elBody = document.createElement("div");
51360        elBody.className = "yui-ac-bd";
51361        this._elBody = this._elContent.appendChild(elBody);
51362
51363        var elFooter = document.createElement("div");
51364        elFooter.className = "yui-ac-ft";
51365        elFooter.style.display = "none";
51366        this._elFooter = this._elContent.appendChild(elFooter);
51367    }
51368    else {
51369        YAHOO.log("Could not initialize the container","warn",this.toString());
51370    }
51371};
51372
51373/**
51374 * Clears out contents of container body and creates up to
51375 * YAHOO.widget.AutoComplete#maxResultsDisplayed &lt;li&gt; elements in an
51376 * &lt;ul&gt; element.
51377 *
51378 * @method _initListEl
51379 * @private
51380 */
51381YAHOO.widget.AutoComplete.prototype._initListEl = function() {
51382    var nListLength = this.maxResultsDisplayed,
51383        elList = this._elList || document.createElement("ul"),
51384        elListItem;
51385    
51386    while(elList.childNodes.length < nListLength) {
51387        elListItem = document.createElement("li");
51388        elListItem.style.display = "none";
51389        elListItem._nItemIndex = elList.childNodes.length;
51390        elList.appendChild(elListItem);
51391    }
51392    if(!this._elList) {
51393        var elBody = this._elBody;
51394        YAHOO.util.Event.purgeElement(elBody, true);
51395        elBody.innerHTML = "";
51396        this._elList = elBody.appendChild(elList);
51397    }
51398    
51399    this._elBody.style.display = "";
51400};
51401
51402/**
51403 * Focuses input field.
51404 *
51405 * @method _focus
51406 * @private
51407 */
51408YAHOO.widget.AutoComplete.prototype._focus = function() {
51409    // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
51410    var oSelf = this;
51411    setTimeout(function() {
51412        try {
51413            oSelf._elTextbox.focus();
51414        }
51415        catch(e) {
51416        }
51417    },0);
51418};
51419
51420/**
51421 * Enables interval detection for IME support.
51422 *
51423 * @method _enableIntervalDetection
51424 * @private
51425 */
51426YAHOO.widget.AutoComplete.prototype._enableIntervalDetection = function() {
51427    var oSelf = this;
51428    if(!oSelf._queryInterval && oSelf.queryInterval) {
51429        oSelf._queryInterval = setInterval(function() { oSelf._onInterval(); }, oSelf.queryInterval);
51430        YAHOO.log("Interval set", "info", this.toString());
51431    }
51432};
51433
51434/**
51435 * Enables interval detection for a less performant but brute force mechanism to
51436 * detect input values at an interval set by queryInterval and send queries if
51437 * input value has changed. Needed to support right-click+paste or shift+insert
51438 * edge cases. Please note that intervals are cleared at the end of each interaction,
51439 * so enableIntervalDetection must be called for each new interaction. The
51440 * recommended approach is to call it in response to textboxFocusEvent.
51441 *
51442 * @method enableIntervalDetection
51443 */
51444YAHOO.widget.AutoComplete.prototype.enableIntervalDetection =
51445    YAHOO.widget.AutoComplete.prototype._enableIntervalDetection;
51446
51447/**
51448 * Enables query triggers based on text input detection by intervals (rather
51449 * than by key events).
51450 *
51451 * @method _onInterval
51452 * @private
51453 */
51454YAHOO.widget.AutoComplete.prototype._onInterval = function() {
51455    var currValue = this._elTextbox.value;
51456    var lastValue = this._sLastTextboxValue;
51457    if(currValue != lastValue) {
51458        this._sLastTextboxValue = currValue;
51459        this._sendQuery(currValue);
51460    }
51461};
51462
51463/**
51464 * Cancels text input detection by intervals.
51465 *
51466 * @method _clearInterval
51467 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
51468 * @private
51469 */
51470YAHOO.widget.AutoComplete.prototype._clearInterval = function() {
51471    if(this._queryInterval) {
51472        clearInterval(this._queryInterval);
51473        this._queryInterval = null;
51474        YAHOO.log("Interval cleared", "info", this.toString());
51475    }
51476};
51477
51478/**
51479 * Whether or not key is functional or should be ignored. Note that the right
51480 * arrow key is NOT an ignored key since it triggers queries for certain intl
51481 * charsets.
51482 *
51483 * @method _isIgnoreKey
51484 * @param nKeycode {Number} Code of key pressed.
51485 * @return {Boolean} True if key should be ignored, false otherwise.
51486 * @private
51487 */
51488YAHOO.widget.AutoComplete.prototype._isIgnoreKey = function(nKeyCode) {
51489    if((nKeyCode == 9) || (nKeyCode == 13)  || // tab, enter
51490            (nKeyCode == 16) || (nKeyCode == 17) || // shift, ctl
51491            (nKeyCode >= 18 && nKeyCode <= 20) || // alt, pause/break,caps lock
51492            (nKeyCode == 27) || // esc
51493            (nKeyCode >= 33 && nKeyCode <= 35) || // page up,page down,end
51494            /*(nKeyCode >= 36 && nKeyCode <= 38) || // home,left,up
51495            (nKeyCode == 40) || // down*/
51496            (nKeyCode >= 36 && nKeyCode <= 40) || // home,left,up, right, down
51497            (nKeyCode >= 44 && nKeyCode <= 45) || // print screen,insert
51498            (nKeyCode == 229) // Bug 2041973: Korean XP fires 2 keyup events, the key and 229
51499        ) { 
51500        return true;
51501    }
51502    return false;
51503};
51504
51505/**
51506 * Makes query request to the DataSource.
51507 *
51508 * @method _sendQuery
51509 * @param sQuery {String} Query string.
51510 * @private
51511 */
51512YAHOO.widget.AutoComplete.prototype._sendQuery = function(sQuery) {
51513    // Widget has been effectively turned off
51514    if(this.minQueryLength < 0) {
51515        this._toggleContainer(false);
51516        YAHOO.log("Property minQueryLength is less than 0", "info", this.toString());
51517        return;
51518    }
51519    // Delimiter has been enabled
51520    if(this.delimChar) {
51521        var extraction = this._extractQuery(sQuery);
51522        // Here is the query itself
51523        sQuery = extraction.query;
51524        // ...and save the rest of the string for later
51525        this._sPastSelections = extraction.previous;
51526    }
51527
51528    // Don't search queries that are too short
51529    if((sQuery && (sQuery.length < this.minQueryLength)) || (!sQuery && this.minQueryLength > 0)) {
51530        if(this._nDelayID != -1) {
51531            clearTimeout(this._nDelayID);
51532        }
51533        this._toggleContainer(false);
51534        YAHOO.log("Query \"" + sQuery + "\" is too short", "info", this.toString());
51535        return;
51536    }
51537
51538    sQuery = encodeURIComponent(sQuery);
51539    this._nDelayID = -1;    // Reset timeout ID because request is being made
51540    
51541    // Subset matching
51542    if(this.dataSource.queryMatchSubset || this.queryMatchSubset) { // backward compat
51543        var oResponse = this.getSubsetMatches(sQuery);
51544        if(oResponse) {
51545            this.handleResponse(sQuery, oResponse, {query: sQuery});
51546            return;
51547        }
51548    }
51549    
51550    if(this.dataSource.responseStripAfter) {
51551        this.dataSource.doBeforeParseData = this.preparseRawResponse;
51552    }
51553    if(this.applyLocalFilter) {
51554        this.dataSource.doBeforeCallback = this.filterResults;
51555    }
51556    
51557    var sRequest = this.generateRequest(sQuery);
51558    
51559    if(sRequest !== undefined) {
51560        this.dataRequestEvent.fire(this, sQuery, sRequest);
51561        YAHOO.log("Sending query \"" + sRequest + "\"", "info", this.toString());
51562
51563        this.dataSource.sendRequest(sRequest, {
51564                success : this.handleResponse,
51565                failure : this.handleResponse,
51566                scope   : this,
51567                argument: {
51568                    query: sQuery
51569                }
51570        });
51571    }
51572    else {
51573        this.dataRequestCancelEvent.fire(this, sQuery);
51574        YAHOO.log("Canceled query \"" + sQuery + "\"", "info", this.toString());
51575    }
51576};
51577
51578/**
51579 * Populates the given &lt;li&gt; element with return value from formatResult().
51580 *
51581 * @method _populateListItem
51582 * @param elListItem {HTMLElement} The LI element.
51583 * @param oResult {Object} The result object.
51584 * @param sCurQuery {String} The query string.
51585 * @private
51586 */
51587YAHOO.widget.AutoComplete.prototype._populateListItem = function(elListItem, oResult, sQuery) {
51588    elListItem.innerHTML = this.formatResult(oResult, sQuery, elListItem._sResultMatch);
51589};
51590
51591/**
51592 * Populates the array of &lt;li&gt; elements in the container with query
51593 * results.
51594 *
51595 * @method _populateList
51596 * @param sQuery {String} Original request.
51597 * @param oResponse {Object}
51597 <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
51598 * @param oPayload {MIXED} (optional) Additional argument(s)
51599 * @private
51600 */
51601YAHOO.widget.AutoComplete.prototype._populateList = function(sQuery, oResponse, oPayload) {
51602    // Clear previous timeout
51603    if(this._nTypeAheadDelayID != -1) {
51604        clearTimeout(this._nTypeAheadDelayID);
51605    }
51606        
51607    sQuery = (oPayload && oPayload.query) ? oPayload.query : sQuery;
51608    
51609    // Pass data through abstract method for any transformations
51610    var ok = this.doBeforeLoadData(sQuery, oResponse, oPayload);
51611
51612    // Data is ok
51613    if(ok && !oResponse.error) {
51614        this.dataReturnEvent.fire(this, sQuery, oResponse.results);
51615        
51616        // Continue only if instance is still active (i.e., user hasn't already moved on)
51617        if(this._bFocused) {
51618            // Store state for this interaction
51619            var sCurQuery = decodeURIComponent(sQuery);
51620            this._sCurQuery = sCurQuery;
51621            this._bItemSelected = false;
51622        
51623            var allResults = oResponse.results,
51624                nItemsToShow = Math.min(allResults.length,this.maxResultsDisplayed),
51625                sMatchKey = (this.dataSource.responseSchema.fields) ? 
51626                    (this.dataSource.responseSchema.fields[0].key || this.dataSource.responseSchema.fields[0]) : 0;
51627            
51628            if(nItemsToShow > 0) {
51629                // Make sure container and helpers are ready to go
51630                if(!this._elList || (this._elList.childNodes.length < nItemsToShow)) {
51631                    this._initListEl();
51632                }
51633                this._initContainerHelperEls();
51634                
51635                var allListItemEls = this._elList.childNodes;
51636                // Fill items with data from the bottom up
51637                for(var i = nItemsToShow-1; i >= 0; i--) {
51638                    var elListItem = allListItemEls[i],
51639                    oResult = allResults[i];
51640                    
51641                    // Backward compatibility
51642                    if(this.resultTypeList) {
51643                        // Results need to be converted back to an array
51644                        var aResult = [];
51645                        // Match key is first
51646                        aResult[0] = (YAHOO.lang.isString(oResult)) ? oResult : oResult[sMatchKey] || oResult[this.key];
51647                        // Add additional data to the result array
51648                        var fields = this.dataSource.responseSchema.fields;
51649                        if(YAHOO.lang.isArray(fields) && (fields.length > 1)) {
51650                            for(var k=1, len=fields.length; k<len; k++) {
51651                                aResult[aResult.length] = oResult[fields[k].key || fields[k]];
51652                            }
51653                        }
51654                        // No specific fields defined, so pass along entire data object
51655                        else {
51656                            // Already an array
51657                            if(YAHOO.lang.isArray(oResult)) {
51658                                aResult = oResult;
51659                            }
51660                            // Simple string 
51661                            else if(YAHOO.lang.isString(oResult)) {
51662                                aResult = [oResult];
51663                            }
51664                            // Object
51665                            else {
51666                                aResult[1] = oResult;
51667                            }
51668                        }
51669                        oResult = aResult;
51670                    }
51671
51672                    // The matching value, including backward compatibility for array format and safety net
51673                    elListItem._sResultMatch = (YAHOO.lang.isString(oResult)) ? oResult : (YAHOO.lang.isArray(oResult)) ? oResult[0] : (oResult[sMatchKey] || "");
51674                    elListItem._oResultData = oResult; // Additional data
51675                    this._populateListItem(elListItem, oResult, sCurQuery);
51676                    elListItem.style.display = "";
51677                }
51678        
51679                // Clear out extraneous items
51680                if(nItemsToShow < allListItemEls.length) {
51681                    var extraListItem;
51682                    for(var j = allListItemEls.length-1; j >= nItemsToShow; j--) {
51683                        extraListItem = allListItemEls[j];
51684                        extraListItem.style.display = "none";
51685                    }
51686                }
51687                
51688                this._nDisplayedItems = nItemsToShow;
51689                
51690                this.containerPopulateEvent.fire(this, sQuery, allResults);
51691                
51692                // Highlight the first item
51693                if(this.autoHighlight) {
51694                    var elFirstListItem = this._elList.firstChild;
51695                    this._toggleHighlight(elFirstListItem,"to");
51696                    this.itemArrowToEvent.fire(this, elFirstListItem);
51697                    YAHOO.log("Arrowed to first item", "info", this.toString());
51698                    this._typeAhead(elFirstListItem,sQuery);
51699                }
51700                // Unhighlight any previous time
51701                else {
51702                    this._toggleHighlight(this._elCurListItem,"from");
51703                }
51704        
51705                // Pre-expansion stuff
51706                ok = this._doBeforeExpandContainer(this._elTextbox, this._elContainer, sQuery, allResults);
51707                
51708                // Expand the container
51709                this._toggleContainer(ok);
51710            }
51711            else {
51712                this._toggleContainer(false);
51713            }
51714
51715            YAHOO.log("Container populated with " + nItemsToShow +  " list items", "info", this.toString());
51716            return;
51717        }
51718    }
51719    // Error
51720    else {
51721        this.dataErrorEvent.fire(this, sQuery, oResponse);
51722    }
51723        
51724    YAHOO.log("Could not populate list", "info", this.toString());    
51725};
51726
51727/**
51728 * Called before container expands, by default snaps container to the
51729 * bottom-left corner of the input element, then calls public overrideable method.
51730 *
51731 * @method _doBeforeExpandContainer
51732 * @param elTextbox {HTMLElement} The text input box.
51733 * @param elContainer {HTMLElement} The container element.
51734 * @param sQuery {String} The query string.
51735 * @param aResults {Object[]}  An array of query results.
51736 * @return {Boolean} Return true to continue expanding container, false to cancel the expand.
51737 * @private 
51738 */
51739YAHOO.widget.AutoComplete.prototype._doBeforeExpandContainer = function(elTextbox, elContainer, sQuery, aResults) {
51740    if(this.autoSnapContainer) {
51741        this.snapContainer();
51742    }
51743
51744    return this.doBeforeExpandContainer(elTextbox, elContainer, sQuery, aResults);
51745};
51746
51747/**
51748 * When forceSelection is true and the user attempts
51749 * leave the text input box without selecting an item from the query results,
51750 * the user selection is cleared.
51751 *
51752 * @method _clearSelection
51753 * @private
51754 */
51755YAHOO.widget.AutoComplete.prototype._clearSelection = function() {
51756    var extraction = (this.delimChar) ? this._extractQuery(this._elTextbox.value) :
51757            {previous:"",query:this._elTextbox.value};
51758    this._elTextbox.value = extraction.previous;
51759    this.selectionEnforceEvent.fire(this, extraction.query);
51760    YAHOO.log("Selection enforced", "info", this.toString());
51761};
51762
51763/**
51764 * Whether or not user-typed value in the text input box matches any of the
51765 * query results.
51766 *
51767 * @method _textMatchesOption
51768 * @return {HTMLElement} Matching list item element if user-input text matches
51769 * a result, null otherwise.
51770 * @private
51771 */
51772YAHOO.widget.AutoComplete.prototype._textMatchesOption = function() {
51773    var elMatch = null;
51774
51775    for(var i=0; i<this._nDisplayedItems; i++) {
51776        var elListItem = this._elList.childNodes[i];
51777        var sMatch = ("" + elListItem._sResultMatch).toLowerCase();
51778        if(sMatch == this._sCurQuery.toLowerCase()) {
51779            elMatch = elListItem;
51780            break;
51781        }
51782    }
51783    return(elMatch);
51784};
51785
51786/**
51787 * Updates in the text input box with the first query result as the user types,
51788 * selecting the substring that the user has not typed.
51789 *
51790 * @method _typeAhead
51791 * @param elListItem {HTMLElement} The &lt;li&gt; element item whose data populates the input field.
51792 * @param sQuery {String} Query string.
51793 * @private
51794 */
51795YAHOO.widget.AutoComplete.prototype._typeAhead = function(elListItem, sQuery) {
51796    // Don't typeAhead if turned off or is backspace
51797    if(!this.typeAhead || (this._nKeyCode == 8)) {
51798        return;
51799    }
51800
51801    var oSelf = this,
51802        elTextbox = this._elTextbox;
51803        
51804    // Only if text selection is supported
51805    if(elTextbox.setSelectionRange || elTextbox.createTextRange) {
51806        // Set and store timeout for this typeahead
51807        this._nTypeAheadDelayID = setTimeout(function() {
51808                // Select the portion of text that the user has not typed
51809                var nStart = elTextbox.value.length; // any saved queries plus what user has typed
51810                oSelf._updateValue(elListItem);
51811                var nEnd = elTextbox.value.length;
51812                oSelf._selectText(elTextbox,nStart,nEnd);
51813                var sPrefill = elTextbox.value.substr(nStart,nEnd);
51814                // Bug 2528552: Store as a selection
51815                oSelf._sCurQuery = elListItem._sResultMatch;
51816                oSelf.typeAheadEvent.fire(oSelf,sQuery,sPrefill);
51817                YAHOO.log("Typeahead occured with prefill string \"" + sPrefill + "\"", "info", oSelf.toString());
51818            },(this.typeAheadDelay*1000));            
51819    }
51820};
51821
51822/**
51823 * Selects text in the input field.
51824 *
51825 * @method _selectText
51826 * @param elTextbox {HTMLElement} Text input box element in which to select text.
51827 * @param nStart {Number} Starting index of text string to select.
51828 * @param nEnd {Number} Ending index of text selection.
51829 * @private
51830 */
51831YAHOO.widget.AutoComplete.prototype._selectText = function(elTextbox, nStart, nEnd) {
51832    if(elTextbox.setSelectionRange) { // For Mozilla
51833        elTextbox.setSelectionRange(nStart,nEnd);
51834    }
51835    else if(elTextbox.createTextRange) { // For IE
51836        var oTextRange = elTextbox.createTextRange();
51837        oTextRange.moveStart("character", nStart);
51838        oTextRange.moveEnd("character", nEnd-elTextbox.value.length);
51839        oTextRange.select();
51840    }
51841    else {
51842        elTextbox.select();
51843    }
51844};
51845
51846/**
51847 * Extracts rightmost query from delimited string.
51848 *
51849 * @method _extractQuery
51850 * @param sQuery {String} String to parse
51851 * @return {Object} Object literal containing properties "query" and "previous".  
51852 * @private
51853 */
51854YAHOO.widget.AutoComplete.prototype._extractQuery = function(sQuery) {
51855    var aDelimChar = this.delimChar,
51856        nDelimIndex = -1,
51857        nNewIndex, nQueryStart,
51858        i = aDelimChar.length-1,
51859        sPrevious;
51860        
51861    // Loop through all possible delimiters and find the rightmost one in the query
51862    // A " " may be a false positive if they are defined as delimiters AND
51863    // are used to separate delimited queries
51864    for(; i >= 0; i--) {
51865        nNewIndex = sQuery.lastIndexOf(aDelimChar[i]);
51866        if(nNewIndex > nDelimIndex) {
51867            nDelimIndex = nNewIndex;
51868        }
51869    }
51870    // If we think the last delimiter is a space (" "), make sure it is NOT
51871    // a false positive by also checking the char directly before it
51872    if(aDelimChar[i] == " ") {
51873        for (var j = aDelimChar.length-1; j >= 0; j--) {
51874            if(sQuery[nDelimIndex - 1] == aDelimChar[j]) {
51875                nDelimIndex--;
51876                break;
51877            }
51878        }
51879    }
51880    // A delimiter has been found in the query so extract the latest query from past selections
51881    if(nDelimIndex > -1) {
51882        nQueryStart = nDelimIndex + 1;
51883        // Trim any white space from the beginning...
51884        while(sQuery.charAt(nQueryStart) == " ") {
51885            nQueryStart += 1;
51886        }
51887        // ...and save the rest of the string for later
51888        sPrevious = sQuery.substring(0,nQueryStart);
51889        // Here is the query itself
51890        sQuery = sQuery.substr(nQueryStart);
51891    }
51892    // No delimiter found in the query, so there are no selections from past queries
51893    else {
51894        sPrevious = "";
51895    }
51896    
51897    return {
51898        previous: sPrevious,
51899        query: sQuery
51900    };
51901};
51902
51903/**
51904 * Syncs results container with its helpers.
51905 *
51906 * @method _toggleContainerHelpers
51907 * @param bShow {Boolean} True if container is expanded, false if collapsed
51908 * @private
51909 */
51910YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers = function(bShow) {
51911    var width = this._elContent.offsetWidth + "px";
51912    var height = this._elContent.offsetHeight + "px";
51913
51914    if(this.useIFrame && this._elIFrame) {
51915    var elIFrame = this._elIFrame;
51916        if(bShow) {
51917            elIFrame.style.width = width;
51918            elIFrame.style.height = height;
51919            elIFrame.style.padding = "";
51920            YAHOO.log("Iframe expanded", "info", this.toString());
51921        }
51922        else {
51923            elIFrame.style.width = 0;
51924            elIFrame.style.height = 0;
51925            elIFrame.style.padding = 0;
51926            YAHOO.log("Iframe collapsed", "info", this.toString());
51927        }
51928    }
51929    if(this.useShadow && this._elShadow) {
51930    var elShadow = this._elShadow;
51931        if(bShow) {
51932            elShadow.style.width = width;
51933            elShadow.style.height = height;
51934            YAHOO.log("Shadow expanded", "info", this.toString());
51935        }
51936        else {
51937            elShadow.style.width = 0;
51938            elShadow.style.height = 0;
51939            YAHOO.log("Shadow collapsed", "info", this.toString());
51940        }
51941    }
51942};
51943
51944/**
51945 * Animates expansion or collapse of the container.
51946 *
51947 * @method _toggleContainer
51948 * @param bShow {Boolean} True if container should be expanded, false if container should be collapsed
51949 * @private
51950 */
51951YAHOO.widget.AutoComplete.prototype._toggleContainer = function(bShow) {
51952    YAHOO.log("Toggling container " + ((bShow) ? "open" : "closed"), "info", this.toString());
51953
51954    var elContainer = this._elContainer;
51955
51956    // If implementer has container always open and it's already open, don't mess with it
51957    // Container is initialized with display "none" so it may need to be shown first time through
51958    if(this.alwaysShowContainer && this._bContainerOpen) {
51959        return;
51960    }
51961    
51962    // Reset states
51963    if(!bShow) {
51964        this._toggleHighlight(this._elCurListItem,"from");
51965        this._nDisplayedItems = 0;
51966        this._sCurQuery = null;
51967        
51968        // Container is already closed, so don't bother with changing the UI
51969        if(this._elContent.style.display == "none") {
51970            return;
51971        }
51972    }
51973
51974    // If animation is enabled...
51975    var oAnim = this._oAnim;
51976    if(oAnim && oAnim.getEl() && (this.animHoriz || this.animVert)) {
51977        if(oAnim.isAnimated()) {
51978            oAnim.stop(true);
51979        }
51980
51981        // Clone container to grab current size offscreen
51982        var oClone = this._elContent.cloneNode(true);
51983        elContainer.appendChild(oClone);
51984        oClone.style.top = "-9000px";
51985        oClone.style.width = "";
51986        oClone.style.height = "";
51987        oClone.style.display = "";
51988
51989        // Current size of the container is the EXPANDED size
51990        var wExp = oClone.offsetWidth;
51991        var hExp = oClone.offsetHeight;
51992
51993        // Calculate COLLAPSED sizes based on horiz and vert anim
51994        var wColl = (this.animHoriz) ? 0 : wExp;
51995        var hColl = (this.animVert) ? 0 : hExp;
51996
51997        // Set animation sizes
51998        oAnim.attributes = (bShow) ?
51999            {width: { to: wExp }, height: { to: hExp }} :
52000            {width: { to: wColl}, height: { to: hColl }};
52001
52002        // If opening anew, set to a collapsed size...
52003        if(bShow && !this._bContainerOpen) {
52004            this._elContent.style.width = wColl+"px";
52005            this._elContent.style.height = hColl+"px";
52006        }
52007        // Else, set it to its last known size.
52008        else {
52009            this._elContent.style.width = wExp+"px";
52010            this._elContent.style.height = hExp+"px";
52011        }
52012
52013        elContainer.removeChild(oClone);
52014        oClone = null;
52015
52016    	var oSelf = this;
52017    	var onAnimComplete = function() {
52018            // Finish the collapse
52019    		oAnim.onComplete.unsubscribeAll();
52020
52021            if(bShow) {
52022                oSelf._toggleContainerHelpers(true);
52023                oSelf._bContainerOpen = bShow;
52024                oSelf.containerExpandEvent.fire(oSelf);
52025                YAHOO.log("Container expanded", "info", oSelf.toString());
52026            }
52027            else {
52028                oSelf._elContent.style.display = "none";
52029                oSelf._bContainerOpen = bShow;
52030                oSelf.containerCollapseEvent.fire(oSelf);
52031                YAHOO.log("Container collapsed", "info", oSelf.toString());
52032            }
52033     	};
52034
52035        // Display container and animate it
52036        this._toggleContainerHelpers(false); // Bug 1424486: Be early to hide, late to show;
52037        this._elContent.style.display = "";
52038        oAnim.onComplete.subscribe(onAnimComplete);
52039        oAnim.animate();
52040    }
52041    // Else don't animate, just show or hide
52042    else {
52043        if(bShow) {
52044            this._elContent.style.display = "";
52045            this._toggleContainerHelpers(true);
52046            this._bContainerOpen = bShow;
52047            this.containerExpandEvent.fire(this);
52048            YAHOO.log("Container expanded", "info", this.toString());
52049        }
52050        else {
52051            this._toggleContainerHelpers(false);
52052            this._elContent.style.display = "none";
52053            this._bContainerOpen = bShow;
52054            this.containerCollapseEvent.fire(this);
52055            YAHOO.log("Container collapsed", "info", this.toString());
52056        }
52057   }
52058
52059};
52060
52061/**
52062 * Toggles the highlight on or off for an item in the container, and also cleans
52063 * up highlighting of any previous item.
52064 *
52065 * @method _toggleHighlight
52066 * @param elNewListItem {HTMLElement} The &lt;li&gt; element item to receive highlight behavior.
52067 * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
52068 * @private
52069 */
52070YAHOO.widget.AutoComplete.prototype._toggleHighlight = function(elNewListItem, sType) {
52071    if(elNewListItem) {
52072        var sHighlight = this.highlightClassName;
52073        if(this._elCurListItem) {
52074            // Remove highlight from old item
52075            YAHOO.util.Dom.removeClass(this._elCurListItem, sHighlight);
52076            this._elCurListItem = null;
52077        }
52078    
52079        if((sType == "to") && sHighlight) {
52080            // Apply highlight to new item
52081            YAHOO.util.Dom.addClass(elNewListItem, sHighlight);
52082            this._elCurListItem = elNewListItem;
52083        }
52084    }
52085};
52086
52087/**
52088 * Toggles the pre-highlight on or off for an item in the container, and also cleans
52089 * up pre-highlighting of any previous item.
52090 *
52091 * @method _togglePrehighlight
52092 * @param elNewListItem {HTMLElement} The &lt;li&gt; element item to receive highlight behavior.
52093 * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
52094 * @private
52095 */
52096YAHOO.widget.AutoComplete.prototype._togglePrehighlight = function(elNewListItem, sType) {
52097    var sPrehighlight = this.prehighlightClassName;
52098
52099    if(this._elCurPrehighlightItem) {
52100        YAHOO.util.Dom.removeClass(this._elCurPrehighlightItem, sPrehighlight);
52101    }
52102    if(elNewListItem == this._elCurListItem) {
52103        return;
52104    }
52105
52106    if((sType == "mouseover") && sPrehighlight) {
52107        // Apply prehighlight to new item
52108        YAHOO.util.Dom.addClass(elNewListItem, sPrehighlight);
52109        this._elCurPrehighlightItem = elNewListItem;
52110    }
52111    else {
52112        // Remove prehighlight from old item
52113        YAHOO.util.Dom.removeClass(elNewListItem, sPrehighlight);
52114    }
52115};
52116
52117/**
52118 * Updates the text input box value with selected query result. If a delimiter
52119 * has been defined, then the value gets appended with the delimiter.
52120 *
52121 * @method _updateValue
52122 * @param elListItem {HTMLElement} The &lt;li&gt; element item with which to update the value.
52123 * @private
52124 */
52125YAHOO.widget.AutoComplete.prototype._updateValue = function(elListItem) {
52126    if(!this.suppressInputUpdate) {    
52127        var elTextbox = this._elTextbox;
52128        var sDelimChar = (this.delimChar) ? (this.delimChar[0] || this.delimChar) : null;
52129        var sResultMatch = elListItem._sResultMatch;
52130    
52131        // Calculate the new value
52132        var sNewValue = "";
52133        if(sDelimChar) {
52134            // Preserve selections from past queries
52135            sNewValue = this._sPastSelections;
52136            // Add new selection plus delimiter
52137            sNewValue += sResultMatch + sDelimChar;
52138            if(sDelimChar != " ") {
52139                sNewValue += " ";
52140            }
52141        }
52142        else { 
52143            sNewValue = sResultMatch;
52144        }
52145        
52146        // Update input field
52147        elTextbox.value = sNewValue;
52148    
52149        // Scroll to bottom of textarea if necessary
52150        if(elTextbox.type == "textarea") {
52151            elTextbox.scrollTop = elTextbox.scrollHeight;
52152        }
52153    
52154        // Move cursor to end
52155        var end = elTextbox.value.length;
52156        this._selectText(elTextbox,end,end);
52157    
52158        this._elCurListItem = elListItem;
52159    }
52160};
52161
52162/**
52163 * Selects a result item from the container
52164 *
52165 * @method _selectItem
52166 * @param elListItem {HTMLElement} The selected &lt;li&gt; element item.
52167 * @private
52168 */
52169YAHOO.widget.AutoComplete.prototype._selectItem = function(elListItem) {
52170    this._bItemSelected = true;
52171    this._updateValue(elListItem);
52172    this._sPastSelections = this._elTextbox.value;
52173    this._clearInterval();
52174    this.itemSelectEvent.fire(this, elListItem, elListItem._oResultData);
52175    YAHOO.log("Item selected: " + YAHOO.lang.dump(elListItem._oResultData), "info", this.toString());
52176    this._toggleContainer(false);
52177};
52178
52179/**
52180 * If an item is highlighted in the container, the right arrow key jumps to the
52181 * end of the textbox and selects the highlighted item, otherwise the container
52182 * is closed.
52183 *
52184 * @method _jumpSelection
52185 * @private
52186 */
52187YAHOO.widget.AutoComplete.prototype._jumpSelection = function() {
52188    if(this._elCurListItem) {
52189        this._selectItem(this._elCurListItem);
52190    }
52191    else {
52192        this._toggleContainer(false);
52193    }
52194};
52195
52196/**
52197 * Triggered by up and down arrow keys, changes the current highlighted
52198 * &lt;li&gt; element item. Scrolls container if necessary.
52199 *
52200 * @method _moveSelection
52201 * @param nKeyCode {Number} Code of key pressed.
52202 * @private
52203 */
52204YAHOO.widget.AutoComplete.prototype._moveSelection = function(nKeyCode) {
52205    if(this._bContainerOpen) {
52206        // Determine current item's id number
52207        var elCurListItem = this._elCurListItem,
52208            nCurItemIndex = -1;
52209
52210        if(elCurListItem) {
52211            nCurItemIndex = elCurListItem._nItemIndex;
52212        }
52213
52214        var nNewItemIndex = (nKeyCode == 40) ?
52215                (nCurItemIndex + 1) : (nCurItemIndex - 1);
52216
52217        // Out of bounds
52218        if(nNewItemIndex < -2 || nNewItemIndex >= this._nDisplayedItems) {
52219            return;
52220        }
52221
52222        if(elCurListItem) {
52223            // Unhighlight current item
52224            this._toggleHighlight(elCurListItem, "from");
52225            this.itemArrowFromEvent.fire(this, elCurListItem);
52226            YAHOO.log("Item arrowed from: " + elCurListItem._nItemIndex, "info", this.toString());
52227        }
52228        if(nNewItemIndex == -1) {
52229           // Go back to query (remove type-ahead string)
52230            if(this.delimChar) {
52231                this._elTextbox.value = this._sPastSelections + this._sCurQuery;
52232            }
52233            else {
52234                this._elTextbox.value = this._sCurQuery;
52235            }
52236            return;
52237        }
52238        if(nNewItemIndex == -2) {
52239            // Close container
52240            this._toggleContainer(false);
52241            return;
52242        }
52243        
52244        var elNewListItem = this._elList.childNodes[nNewItemIndex],
52245
52246        // Scroll the container if necessary
52247            elContent = this._elContent,
52248            sOF = YAHOO.util.Dom.getStyle(elContent,"overflow"),
52249            sOFY = YAHOO.util.Dom.getStyle(elContent,"overflowY"),
52250            scrollOn = ((sOF == "auto") || (sOF == "scroll") || (sOFY == "auto") || (sOFY == "scroll"));
52251        if(scrollOn && (nNewItemIndex > -1) &&
52252        (nNewItemIndex < this._nDisplayedItems)) {
52253            // User is keying down
52254            if(nKeyCode == 40) {
52255                // Bottom of selected item is below scroll area...
52256                if((elNewListItem.offsetTop+elNewListItem.offsetHeight) > (elContent.scrollTop + elContent.offsetHeight)) {
52257                    // Set bottom of scroll area to bottom of selected item
52258                    elContent.scrollTop = (elNewListItem.offsetTop+elNewListItem.offsetHeight) - elContent.offsetHeight;
52259                }
52260                // Bottom of selected item is above scroll area...
52261                else if((elNewListItem.offsetTop+elNewListItem.offsetHeight) < el
52261Content.scrollTop) {
52262                    // Set top of selected item to top of scroll area
52263                    elContent.scrollTop = elNewListItem.offsetTop;
52264
52265                }
52266            }
52267            // User is keying up
52268            else {
52269                // Top of selected item is above scroll area
52270                if(elNewListItem.offsetTop < elContent.scrollTop) {
52271                    // Set top of scroll area to top of selected item
52272                    this._elContent.scrollTop = elNewListItem.offsetTop;
52273                }
52274                // Top of selected item is below scroll area
52275                else if(elNewListItem.offsetTop > (elContent.scrollTop + elContent.offsetHeight)) {
52276                    // Set bottom of selected item to bottom of scroll area
52277                    this._elContent.scrollTop = (elNewListItem.offsetTop+elNewListItem.offsetHeight) - elContent.offsetHeight;
52278                }
52279            }
52280        }
52281
52282        this._toggleHighlight(elNewListItem, "to");
52283        this.itemArrowToEvent.fire(this, elNewListItem);
52284        YAHOO.log("Item arrowed to " + elNewListItem._nItemIndex, "info", this.toString());
52285        if(this.typeAhead) {
52286            this._updateValue(elNewListItem);
52287            // Bug 2528552: Store as a selection
52288            this._sCurQuery = elNewListItem._sResultMatch;
52289        }
52290    }
52291};
52292
52293/////////////////////////////////////////////////////////////////////////////
52294//
52295// Private event handlers
52296//
52297/////////////////////////////////////////////////////////////////////////////
52298
52299/**
52300 * Handles container mouseover events.
52301 *
52302 * @method _onContainerMouseover
52303 * @param v {HTMLEvent} The mouseover event.
52304 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
52305 * @private
52306 */
52307YAHOO.widget.AutoComplete.prototype._onContainerMouseover = function(v,oSelf) {
52308    var elTarget = YAHOO.util.Event.getTarget(v);
52309    var elTag = elTarget.nodeName.toLowerCase();
52310    while(elTarget && (elTag != "table")) {
52311        switch(elTag) {
52312            case "body":
52313                return;
52314            case "li":
52315                if(oSelf.prehighlightClassName) {
52316                    oSelf._togglePrehighlight(elTarget,"mouseover");
52317                }
52318                else {
52319                    oSelf._toggleHighlight(elTarget,"to");
52320                }
52321            
52322                oSelf.itemMouseOverEvent.fire(oSelf, elTarget);
52323                YAHOO.log("Item moused over " + elTarget._nItemIndex, "info", oSelf.toString());
52324                break;
52325            case "div":
52326                if(YAHOO.util.Dom.hasClass(elTarget,"yui-ac-container")) {
52327                    oSelf._bOverContainer = true;
52328                    return;
52329                }
52330                break;
52331            default:
52332                break;
52333        }
52334        
52335        elTarget = elTarget.parentNode;
52336        if(elTarget) {
52337            elTag = elTarget.nodeName.toLowerCase();
52338        }
52339    }
52340};
52341
52342/**
52343 * Handles container mouseout events.
52344 *
52345 * @method _onContainerMouseout
52346 * @param v {HTMLEvent} The mouseout event.
52347 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
52348 * @private
52349 */
52350YAHOO.widget.AutoComplete.prototype._onContainerMouseout = function(v,oSelf) {
52351    var elTarget = YAHOO.util.Event.getTarget(v);
52352    var elTag = elTarget.nodeName.toLowerCase();
52353    while(elTarget && (elTag != "table")) {
52354        switch(elTag) {
52355            case "body":
52356                return;
52357            case "li":
52358                if(oSelf.prehighlightClassName) {
52359                    oSelf._togglePrehighlight(elTarget,"mouseout");
52360                }
52361                else {
52362                    oSelf._toggleHighlight(elTarget,"from");
52363                }
52364            
52365                oSelf.itemMouseOutEvent.fire(oSelf, elTarget);
52366                YAHOO.log("Item moused out " + elTarget._nItemIndex, "info", oSelf.toString());
52367                break;
52368            case "ul":
52369                oSelf._toggleHighlight(oSelf._elCurListItem,"to");
52370                break;
52371            case "div":
52372                if(YAHOO.util.Dom.hasClass(elTarget,"yui-ac-container")) {
52373                    oSelf._bOverContainer = false;
52374                    return;
52375                }
52376                break;
52377            default:
52378                break;
52379        }
52380
52381        elTarget = elTarget.parentNode;
52382        if(elTarget) {
52383            elTag = elTarget.nodeName.toLowerCase();
52384        }
52385    }
52386};
52387
52388/**
52389 * Handles container click events.
52390 *
52391 * @method _onContainerClick
52392 * @param v {HTMLEvent} The click event.
52393 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
52394 * @private
52395 */
52396YAHOO.widget.AutoComplete.prototype._onContainerClick = function(v,oSelf) {
52397    var elTarget = YAHOO.util.Event.getTarget(v);
52398    var elTag = elTarget.nodeName.toLowerCase();
52399    while(elTarget && (elTag != "table")) {
52400        switch(elTag) {
52401            case "body":
52402                return;
52403            case "li":
52404                // In case item has not been moused over
52405                oSelf._toggleHighlight(elTarget,"to");
52406                oSelf._selectItem(elTarget);
52407                return;
52408            default:
52409                break;
52410        }
52411
52412        elTarget = elTarget.parentNode;
52413        if(elTarget) {
52414            elTag = elTarget.nodeName.toLowerCase();
52415        }
52416    }    
52417};
52418
52419
52420/**
52421 * Handles container scroll events.
52422 *
52423 * @method _onContainerScroll
52424 * @param v {HTMLEvent} The scroll event.
52425 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
52426 * @private
52427 */
52428YAHOO.widget.AutoComplete.prototype._onContainerScroll = function(v,oSelf) {
52429    oSelf._focus();
52430};
52431
52432/**
52433 * Handles container resize events.
52434 *
52435 * @method _onContainerResize
52436 * @param v {HTMLEvent} The resize event.
52437 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
52438 * @private
52439 */
52440YAHOO.widget.AutoComplete.prototype._onContainerResize = function(v,oSelf) {
52441    oSelf._toggleContainerHelpers(oSelf._bContainerOpen);
52442};
52443
52444
52445/**
52446 * Handles textbox keydown events of functional keys, mainly for UI behavior.
52447 *
52448 * @method _onTextboxKeyDown
52449 * @param v {HTMLEvent} The keydown event.
52450 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
52451 * @private
52452 */
52453YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown = function(v,oSelf) {
52454    var nKeyCode = v.keyCode;
52455
52456    // Clear timeout
52457    if(oSelf._nTypeAheadDelayID != -1) {
52458        clearTimeout(oSelf._nTypeAheadDelayID);
52459    }
52460    
52461    switch (nKeyCode) {
52462        case 9: // tab
52463            if(!YAHOO.env.ua.opera && (navigator.userAgent.toLowerCase().indexOf("mac") == -1) || (YAHOO.env.ua.webkit>420)) {
52464                // select an item or clear out
52465                if(oSelf._elCurListItem) {
52466                    if(oSelf.delimChar && (oSelf._nKeyCode != nKeyCode)) {
52467                        if(oSelf._bContainerOpen) {
52468                            YAHOO.util.Event.stopEvent(v);
52469                        }
52470                    }
52471                    oSelf._selectItem(oSelf._elCurListItem);
52472                }
52473                else {
52474                    oSelf._toggleContainer(false);
52475                }
52476            }
52477            break;
52478        case 13: // enter
52479            if(!YAHOO.env.ua.opera && (navigator.userAgent.toLowerCase().indexOf("mac") == -1) || (YAHOO.env.ua.webkit>420)) {
52480                if(oSelf._elCurListItem) {
52481                    if(oSelf._nKeyCode != nKeyCode) {
52482                        if(oSelf._bContainerOpen) {
52483                            YAHOO.util.Event.stopEvent(v);
52484                        }
52485                    }
52486                    oSelf._selectItem(oSelf._elCurListItem);
52487                }
52488                else {
52489                    oSelf._toggleContainer(false);
52490                }
52491            }
52492            break;
52493        case 27: // esc
52494            oSelf._toggleContainer(false);
52495            return;
52496        case 39: // right
52497            oSelf._jumpSelection();
52498            break;
52499        case 38: // up
52500            if(oSelf._bContainerOpen) {
52501                YAHOO.util.Event.stopEvent(v);
52502                oSelf._moveSelection(nKeyCode);
52503            }
52504            break;
52505        case 40: // down
52506            if(oSelf._bContainerOpen) {
52507                YAHOO.util.Event.stopEvent(v);
52508                oSelf._moveSelection(nKeyCode);
52509            }
52510            break;
52511        default: 
52512            oSelf._bItemSelected = false;
52513            oSelf._toggleHighlight(oSelf._elCurListItem, "from");
52514
52515            oSelf.textboxKeyEvent.fire(oSelf, nKeyCode);
52516            YAHOO.log("Textbox keyed", "info", oSelf.toString());
52517            break;
52518    }
52519
52520    if(nKeyCode === 18){
52521        oSelf._enableIntervalDetection();
52522    }    
52523    oSelf._nKeyCode = nKeyCode;
52524};
52525
52526/**
52527 * Handles textbox keypress events.
52528 * @method _onTextboxKeyPress
52529 * @param v {HTMLEvent} The keypress event.
52530 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
52531 * @private
52532 */
52533YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress = function(v,oSelf) {
52534    var nKeyCode = v.keyCode;
52535
52536        // Expose only to non SF3 (bug 1978549) Mac browsers (bug 790337) and  Opera browsers (bug 583531),
52537        // where stopEvent is ineffective on keydown events 
52538        if(YAHOO.env.ua.opera || (navigator.userAgent.toLowerCase().indexOf("mac") != -1) && (YAHOO.env.ua.webkit < 420)) {
52539            switch (nKeyCode) {
52540            case 9: // tab
52541                // select an item or clear out
52542                if(oSelf._bContainerOpen) {
52543                    if(oSelf.delimChar) {
52544                        YAHOO.util.Event.stopEvent(v);
52545                    }
52546                    if(oSelf._elCurListItem) {
52547                        oSelf._selectItem(oSelf._elCurListItem);
52548                    }
52549                    else {
52550                        oSelf._toggleContainer(false);
52551                    }
52552                }
52553                break;
52554            case 13: // enter
52555                if(oSelf._bContainerOpen) {
52556                    YAHOO.util.Event.stopEvent(v);
52557                    if(oSelf._elCurListItem) {
52558                        oSelf._selectItem(oSelf._elCurListItem);
52559                    }
52560                    else {
52561                        oSelf._toggleContainer(false);
52562                    }
52563                }
52564                break;
52565            default:
52566                break;
52567            }
52568        }
52569
52570        //TODO: (?) limit only to non-IE, non-Mac-FF for Korean IME support (bug 811948)
52571        // Korean IME detected
52572        else if(nKeyCode == 229) {
52573            oSelf._enableIntervalDetection();
52574        }
52575};
52576
52577/**
52578 * Handles textbox keyup events to trigger queries.
52579 *
52580 * @method _onTextboxKeyUp
52581 * @param v {HTMLEvent} The keyup event.
52582 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
52583 * @private
52584 */
52585YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp = function(v,oSelf) {
52586    var sText = this.value; //string in textbox
52587    
52588    // Check to see if any of the public properties have been updated
52589    oSelf._initProps();
52590
52591    // Filter out chars that don't trigger queries
52592    var nKeyCode = v.keyCode;
52593    if(oSelf._isIgnoreKey(nKeyCode)) {
52594        return;
52595    }
52596
52597    // Clear previous timeout
52598    if(oSelf._nDelayID != -1) {
52599        clearTimeout(oSelf._nDelayID);
52600    }
52601
52602    // Set new timeout
52603    oSelf._nDelayID = setTimeout(function(){
52604            oSelf._sendQuery(sText);
52605        },(oSelf.queryDelay * 1000));
52606};
52607
52608/**
52609 * Handles text input box receiving focus.
52610 *
52611 * @method _onTextboxFocus
52612 * @param v {HTMLEvent} The focus event.
52613 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
52614 * @private
52615 */
52616YAHOO.widget.AutoComplete.prototype._onTextboxFocus = function (v,oSelf) {
52617    // Start of a new interaction
52618    if(!oSelf._bFocused) {
52619        oSelf._elTextbox.setAttribute("autocomplete","off");
52620        oSelf._bFocused = true;
52621        oSelf._sInitInputValue = oSelf._elTextbox.value;
52622        oSelf.textboxFocusEvent.fire(oSelf);
52623        YAHOO.log("Textbox focused", "info", oSelf.toString());
52624    }
52625};
52626
52627/**
52628 * Handles text input box losing focus.
52629 *
52630 * @method _onTextboxBlur
52631 * @param v {HTMLEvent} The focus event.
52632 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
52633 * @private
52634 */
52635YAHOO.widget.AutoComplete.prototype._onTextboxBlur = function (v,oSelf) {
52636    // Is a true blur
52637    if(!oSelf._bOverContainer || (oSelf._nKeyCode == 9)) {
52638        // Current query needs to be validated as a selection
52639        if(!oSelf._bItemSelected) {
52640            var elMatchListItem = oSelf._textMatchesOption();
52641            // Container is closed or current query doesn't match any result
52642            if(!oSelf._bContainerOpen || (oSelf._bContainerOpen && (elMatchListItem === null))) {
52643                // Force selection is enabled so clear the current query
52644                if(oSelf.forceSelection) {
52645                    oSelf._clearSelection();
52646                }
52647                // Treat current query as a valid selection
52648                else {
52649                    oSelf.unmatchedItemSelectEvent.fire(oSelf, oSelf._sCurQuery);
52650                    YAHOO.log("Unmatched item selected: " + oSelf._sCurQuery, "info", oSelf.toString());
52651                }
52652            }
52653            // Container is open and current query matches a result
52654            else {
52655                // Force a selection when textbox is blurred with a match
52656                if(oSelf.forceSelection) {
52657                    oSelf._selectItem(elMatchListItem);
52658                }
52659            }
52660        }
52661
52662        oSelf._clearInterval();
52663        oSelf._bFocused = false;
52664        if(oSelf._sInitInputValue !== oSelf._elTextbox.value) {
52665            oSelf.textboxChangeEvent.fire(oSelf);
52666        }
52667        oSelf.textboxBlurEvent.fire(oSelf);
52668        YAHOO.log("Textbox blurred", "info", oSelf.toString());
52669
52670        oSelf._toggleContainer(false);
52671    }
52672    // Not a true blur if it was a selection via mouse click
52673    else {
52674        oSelf._focus();
52675    }
52676};
52677
52678/**
52679 * Handles window unload event.
52680 *
52681 * @method _onWindowUnload
52682 * @param v {HTMLEvent} The unload event.
52683 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
52684 * @private
52685 */
52686YAHOO.widget.AutoComplete.prototype._onWindowUnload = function(v,oSelf) {
52687    if(oSelf && oSelf._elTextbox && oSelf.allowBrowserAutocomplete) {
52688        oSelf._elTextbox.setAttribute("autocomplete","on");
52689    }
52690};
52691
52692/////////////////////////////////////////////////////////////////////////////
52693//
52694// Deprecated for Backwards Compatibility
52695//
52696/////////////////////////////////////////////////////////////////////////////
52697/**
52698 * @method doBeforeSendQuery
52699 * @deprecated Use generateRequest.
52700 */
52701YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery = function(sQuery) {
52702    return this.generateRequest(sQuery);
52703};
52704
52705/**
52706 * @method getListItems
52707 * @deprecated Use getListEl().childNodes.
52708 */
52709YAHOO.widget.AutoComplete.prototype.getListItems = function() {
52710    var allListItemEls = [],
52711        els = this._elList.childNodes;
52712    for(var i=els.length-1; i>=0; i--) {
52713        allListItemEls[i] = els[i];
52714    }
52715    return allListItemEls;
52716};
52717
52718/////////////////////////////////////////////////////////////////////////
52719//
52720// Private static methods
52721//
52722/////////////////////////////////////////////////////////////////////////
52723
52724/**
52725 * Clones object literal or array of object literals.
52726 *
52727 * @method AutoComplete._cloneObject
52728 * @param o {Object} Object.
52729 * @private
52730 * @static     
52731 */
52732YAHOO.widget.AutoComplete._cloneObject = function(o) {
52733    if(!YAHOO.lang.isValue(o)) {
52734        return o;
52735    }
52736    
52737    var copy = {};
52738    
52739    if(YAHOO.lang.isFunction(o)) {
52740        copy = o;
52741    }
52742    else if(YAHOO.lang.isArray(o)) {
52743        var array = [];
52744        for(var i=0,len=o.length;i<len;i++) {
52745            array[i] = YAHOO.widget.AutoComplete._cloneObject(o[i]);
52746        }
52747        copy = array;
52748    }
52749    else if(YAHOO.lang.isObject(o)) { 
52750        for (var x in o){
52751            if(YAHOO.lang.hasOwnProperty(o, x)) {
52752                if(YAHOO.lang.isValue(o[x]) && YAHOO.lang.isObject(o[x]) || YAHOO.lang.isArray(o[x])) {
52753                    copy[x] = YAHOO.widget.AutoComplete._cloneObject(o[x]);
52754                }
52755                else {
52756                    copy[x] = o[x];
52757                }
52758            }
52759        }
52760    }
52761    else {
52762        copy = o;
52763    }
52764
52765    return copy;
52766};
52767
52768
52769
52770
52771YAHOO.register("autocomplete", YAHOO.widget.AutoComplete, {version: "2.9.0", build: "2800"});
52772/*
52773Copyright (c) 2011, Yahoo! Inc. All rights reserved.
52774Code licensed under the BSD License:
52775http://developer.yahoo.com/yui/license.html
52776version: 2.9.0
52777*/
52778(function () {
52779/**
52780 * The Paginator widget provides a set of controls to navigate through paged
52781 * data.
52782 *
52783 * @module paginator
52784 * @uses YAHOO.util.EventProvider
52785 * @uses YAHOO.util.AttributeProvider
52786 */
52787
52788var Dom        = YAHOO.util.Dom,
52789    lang       = YAHOO.lang,
52790    isObject   = lang.isObject,
52791    isFunction = lang.isFunction,
52792    isArray    = lang.isArray,
52793    isString   = lang.isString;
52794
52795/**
52796 * Instantiate a Paginator, passing a configuration object to the contructor.
52797 * The configuration object should contain the following properties:
52798 * <ul>
52799 *   <li>rowsPerPage : <em>n</em> (int)</li>
52800 *   <li>totalRecords : <em>n</em> (int or Paginator.VALUE_UNLIMITED)</li>
52801 *   <li>containers : <em>id | el | arr</em> (HTMLElement reference, its id, or an array of either)</li>
52802 * </ul>
52803 *
52804 * @namespace YAHOO.widget
52805 * @class Paginator
52806 * @constructor
52807 * @param config {Object} Object literal to set instance and ui component
52808 * configuration.
52809 */
52810function Paginator(config) {
52811    var UNLIMITED = Paginator.VALUE_UNLIMITED,
52812        attrib, initialPage, records, perPage, startIndex;
52813
52814    config = isObject(config) ? config : {};
52815
52816    this.initConfig();
52817
52818    this.initEvents();
52819
52820    // Set the basic config keys first
52821    this.set('rowsPerPage',config.rowsPerPage,true);
52822    if (Paginator.isNumeric(config.totalRecords)) {
52823        this.set('totalRecords',config.totalRecords,true);
52824    }
52825    
52826    this.initUIComponents();
52827
52828    // Update the other config values
52829    for (attrib in config) {
52830        if (config.hasOwnProperty(attrib)) {
52831            this.set(attrib,config[attrib],true);
52832        }
52833    }
52834
52835    // Calculate the initial record offset
52836    initialPage = this.get('initialPage');
52837    records     = this.get('totalRecords');
52838    perPage     = this.get('rowsPerPage');
52839    if (initialPage > 1 && perPage !== UNLIMITED) {
52840        startIndex = (initialPage - 1) * perPage;
52841        if (records === UNLIMITED || startIndex < records) {
52842            this.set('recordOffset',startIndex,true);
52843        }
52844    }
52845}
52846
52847
52848// Static members
52849lang.augmentObject(Paginator, {
52850    /**
52851     * Incrementing index used to give instances unique ids.
52852     * @static
52853     * @property Paginator.id
52854     * @type number
52855     * @private
52856     */
52857    id : 0,
52858
52859    /**
52860     * Base of id strings used for ui components.
52861     * @static
52862     * @property Paginator.ID_BASE
52863     * @type string
52864     * @private
52865     */
52866    ID_BASE : 'yui-pg',
52867
52868    /**
52869     * Used to identify unset, optional configurations, or used explicitly in
52870     * the case of totalRecords to indicate unlimited pagination.
52871     * @static
52872     * @property Paginator.VALUE_UNLIMITED
52873     * @type number
52874     * @final
52875     */
52876    VALUE_UNLIMITED : -1,
52877
52878    /**
52879     * Default template used by Paginator instances.  Update this if you want
52880     * all new Paginators to use a different default template.
52881     * @static
52882     * @property Paginator.TEMPLATE_DEFAULT
52883     * @type string
52884     */
52885    TEMPLATE_DEFAULT : "{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink}",
52886
52887    /**
52888     * Common alternate pagination format, including page links, links for
52889     * previous, next, first and last pages as well as a rows-per-page
52890     * dropdown.  Offered as a convenience.
52891     * @static
52892     * @property Paginator.TEMPLATE_ROWS_PER_PAGE
52893     * @type string
52894     */
52895    TEMPLATE_ROWS_PER_PAGE : "{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}",
52896
52897    /**
52898     * Storage object for UI Components
52899     * @static
52900     * @property Paginator.ui
52901     */
52902    ui : {},
52903
52904    /**
52905     * Similar to YAHOO.lang.isNumber, but allows numeric strings.  This is
52906     * is used for attribute validation in conjunction with getters that return
52907     * numbers.
52908     *
52909     * @method Paginator.isNumeric
52910     * @param v {Number|String} value to be checked for number or numeric string
52911     * @returns {Boolean} true if the input is coercable into a finite number
52912     * @static
52913     */
52914    isNumeric : function (v) {
52915        return isFinite(+v);
52916    },
52917
52918    /**
52919     * Return a number or null from input
52920     *
52921     * @method Paginator.toNumber
52922     * @param n {Number|String} a number or numeric string
52923     * @return Number
52924     * @static
52925     */
52926    toNumber : function (n) {
52927        return isFinite(+n) ? +n : null;
52928    }
52929
52930},true);
52931
52932
52933// Instance members and methods
52934Paginator.prototype = {
52935
52936    // Instance members
52937
52938    /**
52939     * Array of nodes in which to render pagination controls.  This is set via
52940     * the &quot;containers&quot; attribute.
52941     * @property _containers
52942     * @type Array(HTMLElement)
52943     * @private
52944     */
52945    _containers : [],
52946
52947    /**
52948     * Flag used to indicate multiple attributes are being updated via setState
52949     * @property _batch
52950     * @type boolean
52951     * @protected
52952     */
52953    _batch : false,
52954
52955    /**
52956     * Used by setState to indicate when a page change has occurred
52957     * @property _pageChanged
52958     * @type boolean
52959     * @protected
52960     */
52961    _pageChanged : false,
52962
52963    /**
52964     * Temporary state cache used by setState to keep track of the previous
52965     * state for eventual pageChange event firing
52966     * @property _state
52967     * @type Object
52968     * @protected
52969     */
52970    _state : null,
52971
52972
52973    // Instance methods
52974
52975    /**
52976     * Initialize the Paginator's attributes (see YAHOO.util.Element class
52977     * AttributeProvider).
52978     * @method initConfig
52979     * @private
52980     */
52981    initConfig : function () {
52982
52983        var UNLIMITED = Paginator.VALUE_UNLIMITED;
52984
52985        /**
52986         * REQUIRED. Number of records constituting a &quot;page&quot;
52987         * @attribute rowsPerPage
52988         * @type integer
52989         */
52990        this.setAttributeConfig('rowsPerPage', {
52991            value     : 0,
52992            validator : Paginator.isNumeric,
52993            setter    : Paginator.toNumber
52994        });
52995
52996        /**
52997         * REQUIRED. Node references or ids of nodes in which to render the
52998         * pagination controls.
52999         * @attribute containers
53000         * @type {string|HTMLElement|Array(string|HTMLElement)}
53001         */
53002        this.setAttributeConfig('containers', {
53003            value     : null,
53004            validator : function (val) {
53005                if (!isArray(val)) {
53006                    val = [val];
53007                }
53008                for (var i = 0, len = val.length; i < len; ++i) {
53009                    if (isString(val[i]) || 
53010                        (isObject(val[i]) && val[i].nodeType === 1)) {
53011                        continue;
53012                    }
53013                    return false;
53014                }
53015                return true;
53016            },
53017            method : function (val) {
53018                val = Dom.get(val);
53019                if (!isArray(val)) {
53020                    val = [val];
53021                }
53022                this._containers = val;
53023            }
53024        });
53025
53026        /**
53027         * Total number of records to paginate through
53028         * @attribute totalRecords
53029         * @type integer
53030         * @default 0
53031         */
53032        this.setAttributeConfig('totalRecords', {
53033            value     : 0,
53034            validator : Paginator.isNumeric,
53035            setter    : Paginator.toNumber
53036        });
53037
53038        /**
53039         * Zero based index of the record considered first on the current page.
53040         * For page based interactions, don't modify this attribute directly;
53041         * use setPage(n).
53042         * @attribute recordOffset
53043         * @type integer
53044         * @default 0
53045         */
53046        this.setAttributeConfig('recordOffset', {
53047            value     : 0,
53048            validator : function (val) {
53049                var total = this.get('totalRecords');
53050                if (Paginator.isNumeric(val)) {
53051                    val = +val;
53052                    return total === UNLIMITED || total > val ||
53053                           (total === 0 && val === 0);
53054                }
53055
53056                return false;
53057            },
53058            setter    : Paginator.toNumber
53059        });
53060
53061        /**
53062         * Page to display on initial paint
53063         * @attribute initialPage
53064         * @type integer
53065         * @default 1
53066         */
53067        this.setAttributeConfig('initialPage', {
53068            value     : 1,
53069            validator : Paginator.isNumeric,
53070            setter    : Paginator.toNumber
53071        });
53072
53073        /**
53074         * Template used to render controls.  The string will be used as
53075         * innerHTML on all specified container nodes.  Bracketed keys
53076         * (e.g. {pageLinks}) in the string will be replaced with an instance
53077         * of the so named ui component.
53078         * @see Paginator.TEMPLATE_DEFAULT
53079         * @see Paginator.TEMPLATE_ROWS_PER_PAGE
53080         * @attribute template
53081         * @type string
53082         */
53083        this.setAttributeConfig('template', {
53084            value : Paginator.TEMPLATE_DEFAULT,
53085            validator : isString
53086        });
53087
53088        /**
53089         * Class assigned to the element(s) containing pagination controls.
53090         * @attribute containerClass
53091         * @type string
53092         * @default 'yui-pg-container'
53093         */
53094        this.setAttributeConfig('containerClass', {
53095            value : 'yui-pg-container',
53096            validator : isString
53097        });
53098
53099        /**
53100         * Display pagination controls even when there is only one page.  Set
53101         * to false to forgo rendering and/or hide the containers when there
53102         * is only one page of data.  Note if you are using the rowsPerPage
53103         * dropdown ui component, visibility will be maintained as long as the
53104         * number of records exceeds the smallest page size.
53105         * @attribute alwaysVisible
53106         * @type boolean
53107         * @default true
53108         */
53109        this.setAttributeConfig('alwaysVisible', {
53110            value : true,
53111            validator : lang.isBoolean
53112        });
53113
53114        /**
53115         * Update the UI immediately upon interaction.  If false, changeRequest
53116         * subscribers or other external code will need to explicitly set the
53117         * new values in the paginator to trigger repaint.
53118         * @attribute updateOnChange
53119         * @type boolean
53120         * @default false
53121         * @deprecated use changeRequest listener that calls setState
53122         */
53123        this.setAttributeConfig('updateOnChange', {
53124            value     : false,
53125            validator : lang.isBoolean
53126        });
53127
53128
53129
53130        // Read only attributes
53131
53132        /**
53133         * Unique id assigned to this instance
53134         * @attribute id
53135         * @type integer
53136         * @final
53137         */
53138        this.setAttributeConfig('id', {
53139            value    : Paginator.id++,
53140            readOnly : true
53141        });
53142
53143        /**
53144         * Indicator of whether the DOM nodes have been initially created
53145         * @attribute rendered
53146         * @type boolean
53147         * @final
53148         */
53149        this.setAttributeConfig('rendered', {
53150            value    : false,
53151            readOnly : true
53152        });
53153
53154    },
53155
53156    /**
53157     * Initialize registered ui components onto this instance.
53158     * @method initUIComponents
53159     * @private
53160     */
53161    initUIComponents : function () {
53162        var ui = Paginator.ui,
53163            name,UIComp;
53164        for (name in ui) {
53165            if (ui.hasOwnProperty(name)) {
53166                UIComp = ui[name];
53167                if (isObject(UIComp) && isFunction(UIComp.init)) {
53168                    UIComp.init(this);
53169                }
53170            }
53171        }
53172    },
53173
53174    /**
53175     * Initialize this instance's CustomEvents.
53176     * @method initEvents
53177     * @private
53178     */
53179    initEvents : function () {
53180        /**
53181         * Event fired when the Paginator is initially rendered
53182         * @event render
53183         */
53184        this.createEvent('render');
53185
53186        /**
53187         * Event fired when the Paginator is initially rendered
53188         * @event rendered
53189         * @deprecated use render event
53190         */
53191        this.createEvent('rendered'); // backward compatibility
53192
53193        /**
53194         * Event fired when a change in pagination values is requested,
53195         * either by interacting with the various ui components or via the
53196         * setStartIndex(n) etc APIs.
53197         * Subscribers will receive the proposed state as the first parameter.
53198         * The proposed state object will contain the following keys:
53199         * <ul>
53200         *   <li>paginator - the Paginator instance</li>
53201         *   <li>page</li>
53202         *   <li>totalRecords</li>
53203         *   <li>recordOffset - index of the first record on the new page</li>
53204         *   <li>rowsPerPage</li>
53205         *   <li>records - array containing [start index, end index] for the records on the new page</li>
53206         *   <li>before - object literal with all these keys for the current state</li>
53207         * </ul>
53208         * @event changeRequest
53209         */
53210        this.createEvent('changeRequest');
53211
53212        /**
53213         * Event fired when attribute changes have resulted in the calculated
53214         * current page changing.
53215         * @event pageChange
53216         */
53217        this.createEvent('pageChange');
53218
53219        /**
53220         * Event that fires before the destroy event.
53221         * @event beforeDestroy
53222         */
53223        this.createEvent('beforeDestroy');
53224
53225        /**
53226         * Event used to trigger cleanup of ui components
53227         * @event destroy
53228         */
53229        this.createEvent('destroy');
53230
53231        this._selfSubscribe();
53232    },
53233
53234    /**
53235     * Subscribes to instance attribute change events to automate certain
53236     * behaviors.
53237     * @method _selfSubscribe
53238     * @protected
53239     */
53240    _selfSubscribe : function () {
53241        // Listen for changes to totalRecords and alwaysVisible 
53242        this.subscribe('totalRecordsChange',this.updateVisibility,this,true);
53243        this.subscribe('alwaysVisibleChange',this.updateVisibility,this,true);
53244
53245        // Fire the pageChange event when appropriate
53246        this.subscribe('totalRecordsChange',this._handleStateChange,this,true);
53247        this.subscribe('recordOffsetChange',this._handleStateChange,this,true);
53248        this.subscribe('rowsPerPageChange',this._handleStateChange,this,true);
53249
53250        // Update recordOffset when totalRecords is reduced below
53251        this.subscribe('totalRecordsChange',this._syncRecordOffset,this,true);
53252    },
53253
53254    /**
53255     * Sets recordOffset to the starting index of the previous page when
53256     * totalRecords is reduced below the current recordOffset.
53257     * @method _syncRecordOffset
53258     * @param e {Event} totalRecordsChange event
53259     * @protected
53260     */
53261    _syncRecordOffset : function (e) {
53262        var v = e.newValue,rpp,state;
53263        if (e.prevValue !== v) {
53264            if (v !== Paginator.VALUE_UNLIMITED) {
53265                rpp = this.get('rowsPerPage');
53266
53267                if (rpp && this.get('recordOffset') >= v) {
53268                    state = this.getState({
53269                        totalRecords : e.prevValue,
53270                        recordOffset : this.get('recordOffset')
53271                    });
53272
53273                    this.set('recordOffset', state.before.recordOffset);
53274                    this._firePageChange(state);
53275                }
53276            }
53277        }
53278    },
53279
53280    /**
53281     * Fires the pageChange event when the state attributes have changed in
53282     * such a way as to locate the current recordOffset on a new page.
53283     * @method _handleStateChange
53284     * @param e {Event} the attribute change event
53285     * @protected
53286     */
53287    _handleStateChange : function (e) {
53288        if (e.prevValue !== e.newValue) {
53289            var change = this._state || {},
53290                state;
53291
53292            change[e.type.replace(/Change$/,'')] = e.prevValue;
53293            state = this.getState(change);
53294
53295            if (state.page !== state.before.page) {
53296                if (this._batch) {
53297                    this._pageChanged = true;
53298                } else {
53299                    this._firePageChange(state);
53300                }
53301            }
53302        }
53303    },
53304
53305    /**
53306     * Fires a pageChange event in the form of a standard attribute change
53307     * event with additional properties prevState and newState.
53308     * @method _firePageChange
53309     * @param state {Object} the result of getState(oldState)
53310     * @protected
53311     */
53312    _firePageChange : function (state) {
53313        if (isObject(state)) {
53314            var current = state.before;
53315            delete state.before;
53316            this.fireEvent('pageChange',{
53317                type      : 'pageChange',
53318                prevValue : state.page,
53319                newValue  : current.page,
53320                prevState : state,
53321                newState  : current
53322            });
53323        }
53324    },
53325
53326    /**
53327     * Render the pagination controls per the format attribute into the
53328     * specified container nodes.
53329     * @method render
53330     * @return the Paginator instance
53331     * @chainable
53332     */
53333    render : function () {
53334        if (this.get('rendered')) {
53335            return this;
53336        }
53337
53338        var template = this.get('template'),
53339            state    = this.getState(),
53340            // ex. yui-pg0-1 (first paginator, second container)
53341            id_base  = Paginator.ID_BASE + this.get('id') + '-',
53342            i, len;
53343
53344        // Assemble the containers, keeping them hidden
53345        for (i = 0, len = this._containers.length; i < len; ++i) {
53346            this._renderTemplate(this._containers[i],template,id_base+i,true);
53347        }
53348
53349        // Show the containers if appropriate
53350        this.updateVisibility();
53351
53352        // Set render attribute manually to support its readOnly contract
53353        if (this._containers.length) {
53354            this.setAttributeConfig('rendered', { value: true });
53355
53356            this.fireEvent('render', state);
53357            // For backward compatibility
53358            this.fireEvent('rendered', state);
53359        }
53360
53361        return this;
53362    },
53363
53364    /**
53365     * Creates the individual ui components and renders them into a container.
53366     *
53367     * @method _renderTemplate
53368     * @param container {HTMLElement} where to add the ui components
53369     * @param template {String} the template to use as a guide for rendering
53370     * @param id_base {String} id base for the container's ui components
53371     * @param hide {Boolean} leave the container hidden after assembly
53372     * @protected
53373     */
53374    _renderTemplate : function (container, template, id_base, hide) {
53375        var containerClass = this.get('containerClass'),
53376            markers, i, len;
53377
53378        if (!container) {
53379            return;
53380        }
53381
53382        // Hide the container while its contents are rendered
53383        Dom.setStyle(container,'display','none');
53384
53385        Dom.addClass(container, containerClass);
53386
53387        // Place the template innerHTML, adding marker spans to the template
53388        // html to indicate drop zones for ui components
53389        container.innerHTML = template.replace(/\{([a-z0-9_ \-]+)\}/gi,
53390            '<span class="yui-pg-ui yui-pg-ui-$1"></span>');
53391
53392        // Replace each marker with the ui component's render() output
53393        markers = Dom.getElementsByClassName('yui-pg-ui','span',container);
53394
53395        for (i = 0, len = markers.length; i < len; ++i) {
53396            this.renderUIComponent(markers[i], id_base);
53397        }
53398
53399        if (!hide) {
53400            // Show the container allowing page reflow
53401            Dom.setStyle(container,'display','');
53402        }
53403    },
53404
53405    /**
53406     * Replaces a marker node with a rendered UI component, determined by the
53407     * yui-pg-ui-(UI component class name) in the marker's className. e.g.
53408     * yui-pg-ui-PageLinks => new YAHOO.widget.Paginator.ui.PageLinks(this)
53409     *
53410     * @method renderUIComponent
53411     * @param marker {HTMLElement} the marker node to replace
53412     * @param id_base {String} string base the component's generated id
53413     * @return the Paginator instance
53414     * @chainable
53415     */
53416    renderUIComponent : function (marker, id_base) {
53417        var par    = marker.parentNode,
53418            name   = /yui-pg-ui-(\w+)/.exec(marker.className),
53419            UIComp = name && Paginator.ui[name[1]],
53420            comp;
53421
53422        if (isFunction(UIComp)) {
53423            comp = new UIComp(this);
53424            if (isFunction(comp.render)) {
53425                par.replaceChild(comp.render(id_base),marker);
53426            }
53427        }
53428
53429        return this;
53430    },
53431
53432    /**
53433     * Removes controls from the page and unhooks events.
53434     * @method destroy
53435     */
53436    destroy : function () {
53437        this.fireEvent('beforeDestroy');
53438        this.fireEvent('destroy');
53439
53440        this.setAttributeConfig('rendered',{value:false});
53441        this.unsubscribeAll();
53442    },
53443
53444    /**
53445     * Hides the containers if there is only one page of data and attribute
53446     * alwaysVisible is false.  Conversely, it displays the containers if either
53447     * there is more than one page worth of data or alwaysVisible is turned on.
53448     * @method updateVisibility
53449     */
53450    updateVisibility : function (e) {
53451        var alwaysVisible = this.get('alwaysVisible'),
53452            totalRecords, visible, rpp, rppOptions, i, len, opt;
53453
53454        if (!e || e.type === 'alwaysVisibleChange' || !alwaysVisible) {
53455            totalRecords = this.get('totalRecords');
53456            visible      = true;
53457            rpp          = this.get('rowsPerPage');
53458            rppOptions   = this.get('rowsPerPageOptions');
53459
53460            if (isArray(rppOptions)) {
53461                for (i = 0, len = rppOptions.length; i < len; ++i) {
53462                    opt = rppOptions[i];
53463                    // account for value 'all'
53464                    if (lang.isNumber(opt || opt.value)) {
53465                        rpp = Math.min(rpp, (opt.value || opt));
53466                    }
53467                }
53468            }
53469
53470            if (totalRecords !== Paginator.VALUE_UNLIMITED &&
53471                totalRecords <= rpp) {
53472                visible = false;
53473            }
53474
53475            visible = visible || alwaysVisible;
53476
53477            for (i = 0, len = this._containers.length; i < len; ++i) {
53478                Dom.setStyle(this._containers[i],'display',
53479                    visible ? '' : 'none');
53480            }
53481        }
53482    },
53483
53484
53485
53486
53487    /**
53488     * Get the configured container nodes
53489     * @method getContainerNodes
53490     * @return {Array} array of HTMLElement nodes
53491     */
53492    getContainerNodes : function () {
53493        return this._containers;
53494    },
53495
53496    /**
53497     * Get the total number of pages in the data set according to the current
53498     * rowsPerPage and totalRecords values.  If totalRecords is not set, or
53499     * set to YAHOO.widget.Paginator.VALUE_UNLIMITED, returns
53500     * YAHOO.widget.Paginator.VALUE_UNLIMITED.
53501     * @method getTotalPages
53502     * @return {number}
53503     */
53504    getTotalPages : function () {
53505        var records = this.get('totalRecords'),
53506            perPage = this.get('rowsPerPage');
53507
53508        // rowsPerPage not set.  Can't calculate
53509        if (!perPage) {
53510            return null;
53511        }
53512
53513        if (records === Paginator.VALUE_UNLIMITED) {
53514            return Paginator.VALUE_UNLIMITED;
53515        }
53516
53517        return Math.ceil(records/perPage);
53518    },
53519
53520    /**
53521     * Does the requested page have any records?
53522     * @method hasPage
53523     * @param page {number} the page in question
53524     * @return {boolean}
53525     */
53526    hasPage : function (page) {
53527        if (!lang.isNumber(page) || page < 1) {
53528            return false;
53529        }
53530
53531        var totalPages = this.getTotalPages();
53532
53533        return (totalPages === Paginator.VALUE_UNLIMITED || totalPages >= page);
53534    },
53535
53536    /**
53537     * Get the page number corresponding to the current record offset.
53538     * @method getCurrentPage
53539     * @return {number}
53540     */
53541    getCurrentPage : function () {
53542        var perPage = this.get('rowsPerPage');
53543        if (!perPage || !this.get('totalRecords')) {
53544            return 0;
53545        }
53546        return Math.floor(this.get('recordOffset') / perPage) + 1;
53547    },
53548
53549    /**
53550     * Are there records on the next page?
53551     * @method hasNextPage
53552     * @return {boolean}
53553     */
53554    hasNextPage : function () {
53555        var currentPage = this.getCurrentPage(),
53556            totalPages  = this.getTotalPages();
53557
53558        return currentPage && (totalPages === Paginator.VALUE_UNLIMITED || currentPage < totalPages);
53559    },
53560
53561    /**
53562     * Get the page number of the next page, or null if the current page is the
53563     * last page.
53564     * @method getNextPage
53565     * @return {number}
53566     */
53567    getNextPage : function () {
53568        return this.hasNextPage() ? this.getCurrentPage() + 1 : null;
53569    },
53570
53571    /**
53572     * Is there a page before the current page?
53573     * @method hasPreviousPage
53574     * @return {boolean}
53575     */
53576    hasPreviousPage : function () {
53577        return (this.getCurrentPage() > 1);
53578    },
53579
53580    /**
53581     * Get the page number of the previous page, or null if the current page
53582     * is the first page.
53583     * @method getPreviousPage
53584     * @return {number}
53585     */
53586    getPreviousPage : function () {
53587        return (this.hasPreviousPage() ? this.getCurrentPage() - 1 : 1);
53588    },
53589
53590    /**
53591     * Get the start and end record indexes of the specified page.
53592     * @method getPageRecords
53593     * @param page {number} (optional) The page (current page if not specified)
53594     * @return {Array} [start_index, end_index]
53595     */
53596    getPageRecords : function (page) {
53597        if (!lang.isNumber(page)) {
53598            page = this.getCurrentPage();
53599        }
53600
53601        var perPage = this.get('rowsPerPage'),
53602            records = this.get('totalRecords'),
53603            start, end;
53604
53605        if (!page || !perPage) {
53606            return null;
53607        }
53608
53609        start = (page - 1) * perPage;
53610        if (records !== Paginator.VALUE_UNLIMITED) {
53611            if (start >= records) {
53612                return null;
53613            }
53614            end = Math.min(start + perPage, records) - 1;
53615        } else {
53616            end = start + perPage - 1;
53617        }
53618
53619        return [start,end];
53620    },
53621
53622    /**
53623     * Set the current page to the provided page number if possible.
53624     * @method setPage
53625     * @param newPage {number} the new page number
53626     * @param silent {boolean} whether to forcibly avoid firing the
53627     * changeRequest event
53628     */
53629    setPage : function (page,silent) {
53630        if (this.hasPage(page) && page !== this.getCurrentPage()) {
53631            if (this.get('updateOnChange') || silent) {
53632                this.set('recordOffset', (page - 1) * this.get('rowsPerPage'));
53633            } else {
53634                this.fireEvent('changeRequest',this.getState({'page':page}));
53635            }
53636        }
53637    },
53638
53639    /**
53640     * Get the number of rows per page.
53641     * @method getRowsPerPage
53642     * @return {number} the current setting of the rowsPerPage attribute
53643     */
53644    getRowsPerPage : function () {
53645        return this.get('rowsPerPage');
53646    },
53647
53648    /**
53649     * Set the number of rows per page.
53650     * @method setRowsPerPage
53651     * @param rpp {number} the new number of rows per page
53652     * @param silent {boolean} whether to forcibly avoid firing the
53653     * changeRequest event
53654     */
53655    setRowsPerPage : function (rpp,silent) {
53656        if (Paginator.isNumeric(rpp) && +rpp > 0 &&
53657            +rpp !== this.get('rowsPerPage')) {
53658            if (this.get('updateOnChange') || silent) {
53659                this.set('rowsPerPage',rpp);
53660            } else {
53661                this.fireEvent('changeRequest',
53662                    this.getState({'rowsPerPage':+rpp}));
53663            }
53664        }
53665    },
53666
53667    /**
53668     * Get the total number of records.
53669     * @method getTotalRecords
53670     * @return {number} the current setting of totalRecords attribute
53671     */
53672    getTotalRecords : function () {
53673        return this.get('totalRecords');
53674    },
53675
53676    /**
53677     * Set the total number of records.
53678     * @method setTotalRecords
53679     * @param total {number} the new total number of records
53680     * @param silent {boolean} whether to forcibly avoid firing the changeRequest event
53681     */
53682    setTotalRecords : function (total,silent) {
53683        if (Paginator.isNumeric(total) && +total >= 0 &&
53684            +total !== this.get('totalRecords')) {
53685            if (this.get('updateOnChange') || silent) {
53686                this.set('totalRecords',total);
53687            } else {
53688                this.fireEvent('changeRequest',
53689                    this.getState({'totalRecords':+total}));
53690            }
53691        }
53692    },
53693
53694    /**
53695     * Get the index of the first record on the current page
53696     * @method getStartIndex
53697     * @return {number} the index of the first record on the current page
53698     */
53699    getStartIndex : function () {
53700        return this.get('recordOffset');
53701    },
53702
53703    /**
53704     * Move the record offset to a new starting index.  This will likely cause
53705     * the calculated current page to change.  You should probably use setPage.
53706     * @method setStartIndex
53707     * @param offset {number} the new record offset
53708     * @param silent {boolean} whether to forcibly avoid firing the changeRequest event
53709     */
53710    setStartIndex : function (offset,silent) {
53711        if (Paginator.isNumeric(offset) && +offset >= 0 &&
53712            +offset !== this.get('recordOffset')) {
53713            if (this.get('updateOnChange') || silent) {
53714                this.set('recordOffset',offset);
53715            } else {
53716                this.fireEvent('changeRequest',
53717                    this.getState({'recordOffset':+offset}));
53718            }
53719        }
53720    },
53721
53722    /**
53723     * Get an object literal describing the current state of the paginator.  If
53724     * an object literal of proposed values is passed, the proposed state will
53725     * be returned as an object literal with the following keys:
53726     * <ul>
53727     * <li>paginator - instance of the Paginator</li>
53728     * <li>page - number</li>
53729     * <li>totalRecords - number</li>
53730     * <li>recordOffset - number</li>
53731     * <li>rowsPerPage - number</li>
53732     * <li>records - [ start_index, end_index ]</li>
53733     * <li>before - (OPTIONAL) { state object literal for current state }</li>
53734     * </ul>
53735     * @method getState
53736     * @return {object}
53737     * @param changes {object} OPTIONAL object literal with proposed values
53738     * Supported change keys include:
53739     * <ul>
53740     * <li>rowsPerPage</li>
53741     * <li>totalRecords</li>
53742     * <li>recordOffset OR</li>
53743     * <li>page</li>
53744     * </ul>
53745     */
53746    getState : function (changes) {
53747        var UNLIMITED = Paginator.VALUE_UNLIMITED,
53748            M = Math, max = M.max, ceil = M.ceil,
53749            currentState, state, offset;
53750
53751        function normalizeOffset(offset,total,rpp) {
53752            if (offset <= 0 || total === 0) {
53753                return 0;
53754            }
53755            if (total === UNLIMITED || total > offset) {
53756                return offset - (offset % rpp);
53757            }
53758            return total - (total % rpp || rpp);
53759        }
53760
53761        currentState = {
53762            paginator    : this,
53763            totalRecords : this.get('totalRecords'),
53764            rowsPerPage  : this.get('rowsPerPage'),
53765            records      : this.getPageRecords()
53766        };
53767        currentState.recordOffset = normalizeOffset(
53768                                        this.get('recordOffset'),
53769                                        currentState.totalRecords,
53770                                        currentState.rowsPerPage);
53771        currentState.page = ceil(currentState.recordOffset /
53772                                 currentState.rowsPerPage) + 1;
53773
53774        if (!changes) {
53775            return currentState;
53776        }
53777
53778        state = {
53779            paginator    : this,
53780            before       : currentState,
53781
53782            rowsPerPage  : changes.rowsPerPage || currentState.rowsPerPage,
53783            totalRecords : (Paginator.isNumeric(changes.totalRecords) ?
53784                                max(changes.totalRecords,UNLIMITED) :
53785                                +currentState.totalRecords)
53786        };
53787
53788        if (state.totalRecords === 0) {
53789            state.recordOffset =
53790            state.page         = 0;
53791        } else {
53792            offset = Paginator.isNumeric(changes.page) ?
53793                        (changes.page - 1) * state.rowsPerPage :
53794                        Paginator.isNumeric(changes.recordOffset) ?
53795                            +changes.recordOffset :
53796                            currentState.recordOffset;
53797
53798            state.recordOffset = normalizeOffset(offset,
53799                                    state.totalRecords,
53800                                    state.rowsPerPage);
53801
53802            state.page = ceil(state.recordOffset / state.rowsPerPage) + 1;
53803        }
53804
53805        state.records = [ state.recordOffset,
53806                          state.recordOffset + state.rowsPerPage - 1 ];
53807
53808        // limit upper index to totalRecords - 1
53809        if (state.totalRecords !== UNLIMITED &&
53810            state.recordOffset < state.totalRecords && state.records &&
53811            state.records[1] > state.totalRecords - 1) {
53812            state.records[1] = state.totalRecords - 1;
53813        }
53814
53815        return state;
53816    },
53817
53818    /**
53819     * Convenience method to facilitate setting state attributes rowsPerPage,
53820     * totalRecords, recordOffset in batch.  Also supports calculating
53821     * recordOffset from state.page if state.recordOffset is not provided.
53822     * Fires only a single pageChange event, if appropriate.
53823     * This will not fire a changeRequest event.
53824     * @method setState
53825     * @param state {Object} Object literal of attribute:value pairs to set
53826     */
53827    setState : function (state) {
53828        if (isObject(state)) {
53829            // get flux state based on current state with before state as well
53830            this._state = this.getState({});
53831
53832            // use just the state props from the input obj
53833            state = {
53834                page         : state.page,
53835                rowsPerPage  : state.rowsPerPage,
53836                totalRecords : state.totalRecords,
53837                recordOffset : state.recordOffset
53838            };
53839
53840            // calculate recordOffset from page if recordOffset not specified.
53841            // not using lang.isNumber for support of numeric strings
53842            if (state.page && state.recordOffset === undefined) {
53843                state.recordOffset = (state.page - 1) *
53844                    (state.rowsPerPage || this.get('rowsPerPage'));
53845            }
53846
53847            this._batch = true;
53848            this._pageChanged = false;
53849
53850            for (var k in state) {
53851                if (state.hasOwnProperty(k) && this._configs.hasOwnProperty(k)) {
53852                    this.set(k,state[k]);
53853                }
53854            }
53855
53856            this._batch = false;
53857            
53858            if (this._pageChanged) {
53859                this._pageChanged = false;
53860
53861                this._firePageChange(this.getState(this._state));
53862            }
53863        }
53864    }
53865};
53866
53867lang.augmentProto(Paginator, YAHOO.util.AttributeProvider);
53868
53869YAHOO.widget.Paginator = Paginator;
53870})();
53871(function () {
53872
53873var Paginator = YAHOO.widget.Paginator,
53874    l         = YAHOO.lang,
53875    setId     = YAHOO.util.Dom.generateId;
53876
53877/**
53878 * ui Component to generate the textual report of current pagination status.
53879 * E.g. "Now viewing page 1 of 13".
53880 *
53881 * @namespace YAHOO.widget.Paginator.ui
53882 * @class CurrentPageReport
53883 * @for YAHOO.widget.Paginator
53884 *
53885 * @constructor
53886 * @param p {Pagintor} Paginator instance to attach to
53887 */
53888Paginator.ui.CurrentPageReport = function (p) {
53889    this.paginator = p;
53890
53891    p.subscribe('recordOffsetChange', this.update,this,true);
53892    p.subscribe('rowsPerPageChange', this.update,this,true);
53893    p.subscribe('totalRecordsChange',this.update,this,true);
53894    p.subscribe('pageReportTemplateChange', this.update,this,true);
53895    p.subscribe('destroy',this.destroy,this,true);
53896
53897    //TODO: make this work
53898    p.subscribe('pageReportClassChange', this.update,this,true);
53899};
53900
53901/**
53902 * Decorates Paginator instances with new attributes. Called during
53903 * Paginator instantiation.
53904 * @method init
53905 * @param p {Paginator} Paginator instance to decorate
53906 * @static
53907 */
53908Paginator.ui.CurrentPageReport.init = function (p) {
53909
53910    /**
53911     * CSS class assigned to the span containing the info.
53912     * @attribute pageReportClass
53913     * @default 'yui-pg-current'
53914     */
53915    p.setAttributeConfig('pageReportClass', {
53916        value : 'yui-pg-current',
53917        validator : l.isString
53918    });
53919
53920    /**
53921     * Used as innerHTML for the span.  Place holders in the form of {name}
53922     * will be replaced with the so named value from the key:value map
53923     * generated by the function held in the pageReportValueGenerator attribute.
53924     * @attribute pageReportTemplate
53925     * @default '({currentPage} of {totalPages})'
53926     * @see pageReportValueGenerator attribute
53927     */
53928    p.setAttributeConfig('pageReportTemplate', {
53929        value : '({currentPage} of {totalPages})',
53930        validator : l.isString
53931    });
53932
53933    /**
53934     * Function to generate the value map used to populate the
53935     * pageReportTemplate.  The function is passed the Paginator instance as a
53936     * parameter.  The default function returns a map with the following keys:
53937     * <ul>
53938     * <li>currentPage</li>
53939     * <li>totalPages</li>
53940     * <li>startIndex</li>
53941     * <li>endIndex</li>
53942     * <li>startRecord</li>
53943     * <li>endRecord</li>
53944     * <li>totalRecords</li>
53945     * </ul>
53946     * @attribute pageReportValueGenarator
53947     */
53948    p.setAttributeConfig('pageReportValueGenerator', {
53949        value : function (paginator) {
53950            var curPage = paginator.getCurrentPage(),
53951                records = paginator.getPageRecords();
53952
53953            return {
53954                'currentPage' : records ? curPage : 0,
53955                'totalPages'  : paginator.getTotalPages(),
53956                'startIndex'  : records ? records[0] : 0,
53957                'endIndex'    : records ? records[1] : 0,
53958                'startRecord' : records ? records[0] + 1 : 0,
53959                'endRecord'   : records ? records[1] + 1 : 0,
53960                'totalRecords': paginator.get('totalRecords')
53961            };
53962        },
53963        validator : l.isFunction
53964    });
53965};
53966
53967/**
53968 * Replace place holders in a string with the named values found in an
53969 * object literal.
53970 * @static
53971 * @method sprintf
53972 * @param template {string} The content string containing place holders
53973 * @param values {object} The key:value pairs used to replace the place holders
53974 * @return {string}
53975 */
53976Paginator.ui.CurrentPageReport.sprintf = function (template, values) {
53977    return template.replace(/\{([\w\s\-]+)\}/g, function (x,key) {
53978            return (key in values) ? values[key] : '';
53979        });
53980};
53981
53982Paginator.ui.CurrentPageReport.prototype = {
53983
53984    /**
53985     * Span node containing the formatted info
53986     * @property span
53987     * @type HTMLElement
53988     * @private
53989     */
53990    span : null,
53991
53992
53993    /**
53994     * Generate the span containing info formatted per the pageReportTemplate
53995     * attribute.
53996     * @method render
53997     * @param id_base {string} used to create unique ids for generated nodes
53998     * @return {HTMLElement}
53999     */
54000    render : function (id_base) {
54001        this.span = document.createElement('span');
54002        this.span.className = this.paginator.get('pageReportClass');
54003        setId(this.span, id_base + '-page-report');
54004        this.update();
54005        
54006        return this.span;
54007    },
54008    
54009    /**
54010     * Regenerate the content of the span if appropriate. Calls
54011     * CurrentPageReport.sprintf with the value of the pageReportTemplate
54012     * attribute and the value map returned from pageReportValueGenerator
54013     * function.
54014     * @method update
54015     * @param e {CustomEvent} The calling change event
54016     */
54017    update : function (e) {
54018        if (e && e.prevValue === e.newValue) {
54019            return;
54020        }
54021
54022        this.span.innerHTML = Paginator.ui.CurrentPageReport.sprintf(
54023            this.paginator.get('pageReportTemplate'),
54024            this.paginator.get('pageReportValueGenerator')(this.paginator));
54025    },
54026
54027    /**
54028     * Removes the link/span node and clears event listeners
54029     * removal.
54030     * @method destroy
54031     * @private
54032     */
54033    destroy : function () {
54034        this.span.parentNode.removeChild(this.span);
54035        this.span = null;
54036    }
54037
54038};
54039
54040})();
54041(function () {
54042
54043var Paginator = YAHOO.widget.Paginator,
54044    l         = YAHOO.lang,
54045    setId     = YAHOO.util.Dom.generateId;
54046
54047/**
54048 * ui Component to generate the page links
54049 *
54050 * @namespace YAHOO.widget.Paginator.ui
54051 * @class PageLinks
54052 * @for YAHOO.widget.Paginator
54053 *
54054 * @constructor
54055 * @param p {Pagintor} Paginator instance to attach to
54056 */
54057Paginator.ui.PageLinks = function (p) {
54058    this.paginator = p;
54059
54060    p.subscribe('recordOffsetChange',this.update,this,true);
54061    p.subscribe('rowsPerPageChange',this.update,this,true);
54062    p.subscribe('totalRecordsChange',this.update,this,true);
54063    p.subscribe('pageLinksChange',   this.rebuild,this,true);
54064    p.subscribe('pageLinkClassChange', this.rebuild,this,true);
54065    p.subscribe('currentPageClassChange', this.rebuild,this,true);
54066    p.subscribe('destroy',this.destroy,this,true);
54067
54068    //TODO: Make this work
54069    p.subscribe('pageLinksContainerClassChange', this.rebuild,this,true);
54070};
54071
54072/**
54073 * Decorates Paginator instances with new attributes. Called during
54074 * Paginator instantiation.
54075 * @method init
54076 * @param p {Paginator} Paginator instance to decorate
54077 * @static
54078 */
54079Paginator.ui.PageLinks.init = function (p) {
54080
54081    /**
54082     * CSS class assigned to each page link/span.
54083     * @attribute pageLinkClass
54084     * @default 'yui-pg-page'
54085     */
54086    p.setAttributeConfig('pageLinkClass', {
54087        value : 'yui-pg-page',
54088        validator : l.isString
54089    });
54090
54091    /**
54092     * CSS class assigned to the current page span.
54093     * @attribute currentPageClass
54094     * @default 'yui-pg-current-page'
54095     */
54096    p.setAttributeConfig('currentPageClass', {
54097        value : 'yui-pg-current-page',
54098        validator : l.isString
54099    });
54100
54101    /**
54102     * CSS class assigned to the span containing the page links.
54103     * @attribute pageLinksContainerClass
54104     * @default 'yui-pg-pages'
54105     */
54106    p.setAttributeConfig('pageLinksContainerClass', {
54107        value : 'yui-pg-pages',
54108        validator : l.isString
54109    });
54110
54111    /**
54112     * Maximum number of page links to display at one time.
54113     * @attribute pageLinks
54114     * @default 10
54115     */
54116    p.setAttributeConfig('pageLinks', {
54117        value : 10,
54118        validator : Paginator.isNumeric
54119    });
54120
54121    /**
54122     * Function used generate the innerHTML for each page link/span.  The
54123     * function receives as parameters the page number and a reference to the
54124     * paginator object.
54125     * @attribute pageLabelBuilder
54126     * @default function (page, paginator) { return page; }
54127     */
54128    p.setAttributeConfig('pageLabelBuilder', {
54129        value : function (page, paginator) { return page; },
54130        validator : l.isFunction
54131    });
54132
54133    /**
54134     * Function used generate the title for each page link.  The
54135     * function receives as parameters the page number and a reference to the
54136     * paginator object.
54137     * @attribute pageTitleBuilder
54138     * @default function (page, paginator) { return page; }
54139     */
54140    p.setAttributeConfig('pageTitleBuilder', {
54141        value : function (page, paginator) { return "Page " + page; },
54142        validator : l.isFunction
54143    });
54144};
54145
54146/**
54147 * Calculates start and end page numbers given a current page, attempting
54148 * to keep the current page in the middle
54149 * @static
54150 * @method calculateRange
54151 * @param {int} currentPage  The current page
54152 * @param {int} totalPages   (optional) Maximum number of pages
54153 * @param {int} numPages     (optional) Preferred number of pages in range
54154 * @return {Array} [start_page_number, end_page_number]
54155 */
54156Paginator.ui.PageLinks.calculateRange = function (currentPage,totalPages,numPages) {
54157    var UNLIMITED = Paginator.VALUE_UNLIMITED,
54158        start, end, delta;
54159
54160    // Either has no pages, or unlimited pages.  Show none.
54161    if (!currentPage || numPages === 0 || totalPages === 0 ||
54162        (totalPages === UNLIMITED && numPages === UNLIMITED)) {
54163        return [0,-1];
54164    }
54165
54166    // Limit requested pageLinks if there are fewer totalPages
54167    if (totalPages !== UNLIMITED) {
54168        numPages = numPages === UNLIMITED ?
54169                    totalPages :
54170                    Math.min(numPages,totalPages);
54171    }
54172
54173    // Determine start and end, trying to keep current in the middle
54174    start = Math.max(1,Math.ceil(currentPage - (numPages/2)));
54175    if (totalPages === UNLIMITED) {
54176        end = start + numPages - 1;
54177    } else {
54178        end = Math.min(totalPages, start + numPages - 1);
54179    }
54180
54181    // Adjust the start index when approaching the last page
54182    delta = numPages - (end - start + 1);
54183    start = Math.max(1, start - delta);
54184
54185    return [start,end];
54186};
54187
54188
54189Paginator.ui.PageLinks.prototype = {
54190
54191    /**
54192     * Current page
54193     * @property current
54194     * @type number
54195     * @private
54196     */
54197    current     : 0,
54198
54199    /**
54200     * Span node containing the page links
54201     * @property container
54202     * @type HTMLElement
54203     * @private
54204     */
54205    container   : null,
54206
54207
54208    /**
54209     * Generate the nodes and return the container node containing page links
54210     * appropriate to the current pagination state.
54211     * @method render
54212     * @param id_base {string} used to create unique ids for generated nodes
54213     * @return {HTMLElement}
54214     */
54215    render : function (id_base) {
54216        var p = this.paginator;
54217
54218        // Set up container
54219        this.container = document.createElement('span');
54220        setId(this.container, id_base + '-pages');
54221        this.container.className = p.get('pageLinksContainerClass');
54222        YAHOO.util.Event.on(this.container,'click',this.onClick,this,true);
54223
54224        // Call update, flagging a need to rebuild
54225        this.update({newValue : null, rebuild : true});
54226
54227        return this.container;
54228    },
54229
54230    /**
54231     * Update the links if appropriate
54232     * @method update
54233     * @param e {CustomEvent} The calling change event
54234     */
54235    update : function (e) {
54236        if (e && e.prevValue === e.newValue) {
54237            return;
54238        }
54239
54240        var p           = this.paginator,
54241            currentPage = p.getCurrentPage();
54242
54243        // Replace content if there's been a change
54244        if (this.current !== currentPage || !currentPage || e.rebuild) {
54245            var labelBuilder = p.get('pageLabelBuilder'),
54246                titleBuilder = p.get('pageTitleBuilder'),
54247                range        = Paginator.ui.PageLinks.calculateRange(
54248                                currentPage,
54249                                p.getTotalPages(),
54250                                p.get('pageLinks')),
54251                start        = range[0],
54252                end          = range[1],
54253                content      = '',
54254                linkTemplate,i,spanTemplate;
54255
54256            linkTemplate = '<a href="#" class="{class}" page="{page}" title="{title}">{label}</a>';
54257            spanTemplate = '<span class="{class}">{label}</span>';
54258            for (i = start; i <= end; ++i) {
54259
54260                if (i === currentPage) {
54261                    content += l.substitute(spanTemplate, {
54262                        'class' : p.get('currentPageClass') + ' ' + p.get('pageLinkClass'),
54263                        'label' : labelBuilder(i,p)
54264                    });
54265
54266                } else {
54267                    content += l.substitute(linkTemplate, {
54268                        'class' : p.get('pageLinkClass'),
54269                        'page'  : i,
54270                        'label' : labelBuilder(i,p),
54271                        'title' : titleBuilder(i,p)
54272                    });
54273                }
54274            }
54275
54276            this.container.innerHTML = content;
54277        }
54278    },
54279
54280    /**
54281     * Force a rebuild of the page links.
54282     * @method rebuild
54283     * @param e {CustomEvent} The calling change event
54284     */
54285    rebuild     : function (e) {
54286        e.rebuild = true;
54287        this.update(e);
54288    },
54289
54290    /**
54291     * Removes the page links container node and clears event listeners
54292     * @method destroy
54293     * @private
54294     */
54295    destroy : function () {
54296        YAHOO.util.Event.purgeElement(this.container,true);
54297        this.container.parentNode.removeChild(this.container);
54298        this.container = null;
54299    },
54300
54301    /**
54302     * Listener for the container's onclick event.  Looks for qualifying link
54303     * clicks, and pulls the page number from the link's page attribute.
54304     * Sends link's page attribute to the Paginator's setPage method.
54305     * @method onClick
54306     * @param e {DOMEvent} The click event
54307     */
54308    onClick : function (e) {
54309        var t = YAHOO.util.Event.getTarget(e);
54310        if (t && YAHOO.util.Dom.hasClass(t,
54311                        this.paginator.get('pageLinkClass'))) {
54312
54313            YAHOO.util.Event.stopEvent(e);
54314
54315            this.paginator.setPage(parseInt(t.getAttribute('page'),10));
54316        }
54317    }
54318
54319};
54320
54321})();
54322(function () {
54323
54324var Paginator = YAHOO.widget.Paginator,
54325    l         = YAHOO.lang,
54326    setId     = YAHOO.util.Dom.generateId;
54327
54328/**
54329 * ui Component to generate the link to jump to the first page.
54330 *
54331 * @namespace YAHOO.widget.Paginator.ui
54332 * @class FirstPageLink
54333 * @for YAHOO.widget.Paginator
54334 *
54335 * @constructor
54336 * @param p {Pagintor} Paginator instance to attach to
54337 */
54338Paginator.ui.FirstPageLink = function (p) {
54339    this.paginator = p;
54340
54341    p.subscribe('recordOffsetChange',this.update,this,true);
54342    p.subscribe('rowsPerPageChange',this.update,this,true);
54343    p.subscribe('totalRecordsChange',this.update,this,true);
54344    p.subscribe('destroy',this.destroy,this,true);
54345
54346    // TODO: make this work
54347    p.subscribe('firstPageLinkLabelChange',this.update,this,true);
54348    p.subscribe('firstPageLinkClassChange',this.update,this,true);
54349};
54350
54351/**
54352 * Decorates Paginator instances with new attributes. Called during
54353 * Paginator instantiation.
54354 * @method init
54355 * @param p {Paginator} Paginator instance to decorate
54356 * @static
54357 */
54358Paginator.ui.FirstPageLink.init = function (p) {
54359
54360    /**
54361     * Used as innerHTML for the first page link/span.
54362     * @attribute firstPageLinkLabel
54363     * @default '&lt;&lt; first'
54364     */
54365    p.setAttributeConfig('firstPageLinkLabel', {
54366        value : '&lt;&lt; first',
54367        validator : l.isString
54368    });
54369
54370    /**
54371     * CSS class assigned to the link/span
54372     * @attribute firstPageLinkClass
54373     * @default 'yui-pg-first'
54374     */
54375    p.setAttributeConfig('firstPageLinkClass', {
54376        value : 'yui-pg-first',
54377        validator : l.isString
54378    });
54379
54380    /**
54381     * Used as title for the first page link.
54382     * @attribute firstPageLinkTitle
54383     * @default 'First Page'
54384     */
54385    p.setAttributeConfig('firstPageLinkTitle', {
54386        value : 'First Page',
54387        validator : l.isString
54388    });
54389};
54390
54391// Instance members and methods
54392Paginator.ui.FirstPageLink.prototype = {
54393
54394    /**
54395     * The currently placed HTMLElement node
54396     * @property current
54397     * @type HTMLElement
54398     * @private
54399     */
54400    current   : null,
54401
54402    /**
54403     * Link node
54404     * @property link
54405     * @type HTMLElement
54406     * @private
54407     */
54408    link      : null,
54409
54410    /**
54411     * Span node (inactive link)
54412     * @property span
54413     * @type HTMLElement
54414     * @private
54415     */
54416    span      : null,
54417
54418    /**
54419     * Generate the nodes and return the appropriate node given the current
54420     * pagination state.
54421     * @method render
54422     * @param id_base {string} used to create unique ids for generated nodes
54423     * @return {HTMLElement}
54424     */
54425    render : function (id_base) {
54426        var p     = this.paginator,
54427            c     = p.get('firstPageLinkClass'),
54428            label = p.get('firstPageLinkLabel'),
54429            title = p.get('firstPageLinkTitle');
54430
54431        this.link     = document.createElement('a');
54432        this.span     = document.createElement('span');
54433
54434        setId(this.link, id_base + '-first-link');
54435        this.link.href      = '#';
54436        this.link.className = c;
54437        this.link.innerHTML = label;
54438        this.link.title     = title;
54439        YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
54440
54441        setId(this.span, id_base + '-first-span');
54442        this.span.className = c;
54443        this.span.innerHTML = label;
54444
54445        this.current = p.getCurrentPage() > 1 ? this.link : this.span;
54446        return this.current;
54447    },
54448
54449    /**
54450     * Swap the link and span nodes if appropriate.
54451     * @method update
54452     * @param e {CustomEvent} The calling change event
54453     */
54454    update : function (e) {
54455        if (e && e.prevValue === e.newValue) {
54456            return;
54457        }
54458
54459        var par = this.current ? this.current.parentNode : null;
54460        if (this.paginator.getCurrentPage() > 1) {
54461            if (par && this.current === this.span) {
54462                par.replaceChild(this.link,this.current);
54463                this.current = this.link;
54464            }
54465        } else {
54466            if (par && this.current === this.link) {
54467                par.replaceChild(this.span,this.current);
54468                this.current = this.span;
54469            }
54470        }
54471    },
54472
54473    /**
54474     * Removes the link/span node and clears event listeners
54475     * removal.
54476     * @method destroy
54477     * @private
54478     */
54479    destroy : function () {
54480        YAHOO.util.Event.purgeElement(this.link);
54481        this.current.parentNode.removeChild(this.current);
54482        this.link = this.span = null;
54483    },
54484
54485    /**
54486     * Listener for the link's onclick event.  Pass new value to setPage method.
54487     * @method onClick
54488     * @param e {DOMEvent} The click event
54489     */
54490    onClick : function (e) {
54491        YAHOO.util.Event.stopEvent(e);
54492        this.paginator.setPage(1);
54493    }
54494};
54495
54496})();
54497(function () {
54498
54499var Paginator = YAHOO.widget.Paginator,
54500    l         = YAHOO.lang,
54501    setId     = YAHOO.util.Dom.generateId;
54502
54503/**
54504 * ui Component to generate the link to jump to the last page.
54505 *
54506 * @namespace YAHOO.widget.Paginator.ui
54507 * @class LastPageLink
54508 * @for YAHOO.widget.Paginator
54509 *
54510 * @constructor
54511 * @param p {Pagintor} Paginator instance to attach to
54512 */
54513Paginator.ui.LastPageLink = function (p) {
54514    this.paginator = p;
54515
54516    p.subscribe('recordOffsetChange',this.update,this,true);
54517    p.subscribe('rowsPerPageChange',this.update,this,true);
54518    p.subscribe('totalRecordsChange',this.update,this,true);
54519    p.subscribe('destroy',this.destroy,this,true);
54520
54521    // TODO: make this work
54522    p.subscribe('lastPageLinkLabelChange',this.update,this,true);
54523    p.subscribe('lastPageLinkClassChange', this.update,this,true);
54524};
54525
54526/**
54527 * Decorates Paginator instances with new attributes. Called during
54528 * Paginator instantiation.
54529 * @method init
54530 * @param paginator {Paginator} Paginator instance to decorate
54531 * @static
54532 */
54533Paginator.ui.LastPageLink.init = function (p) {
54534
54535    /**
54536     * Used as innerHTML for the last page link/span.
54537     * @attribute lastPageLinkLabel
54538     * @default 'last &gt;&gt;'
54539     */
54540    p.setAttributeConfig('lastPageLinkLabel', {
54541        value : 'last &gt;&gt;',
54542        validator : l.isString
54543    });
54544
54545    /**
54546     * CSS class assigned to the link/span
54547     * @attribute lastPageLinkClass
54548     * @default 'yui-pg-last'
54549     */
54550    p.setAttributeConfig('lastPageLinkClass', {
54551        value : 'yui-pg-last',
54552        validator : l.isString
54553    });
54554
54555   /**
54556     * Used as title for the last page link.
54557     * @attribute lastPageLinkTitle
54558     * @default 'Last Page'
54559     */
54560    p.setAttributeConfig('lastPageLinkTitle', {
54561        value : 'Last Page',
54562        validator : l.isString
54563    });
54564
54565};
54566
54567Paginator.ui.LastPageLink.prototype = {
54568
54569    /**
54570     * Currently placed HTMLElement node
54571     * @property current
54572     * @type HTMLElement
54573     * @private
54574     */
54575    current   : null,
54576
54577    /**
54578     * Link HTMLElement node
54579     * @property link
54580     * @type HTMLElement
54581     * @private
54582     */
54583    link      : null,
54584
54585    /**
54586     * Span node (inactive link)
54587     * @property span
54588     * @type HTMLElement
54589     * @private
54590     */
54591    span      : null,
54592
54593    /**
54594     * Empty place holder node for when the last page link is inappropriate to
54595     * display in any form (unlimited paging).
54596     * @property na
54597     * @type HTMLElement
54598     * @private
54599     */
54600    na        : null,
54601
54602
54603    /**
54604     * Generate the nodes and return the appropriate node given the current
54605     * pagination state.
54606     * @method render
54607     * @param id_base {string} used to create unique ids for generated nodes
54608     * @return {HTMLElement}
54609     */
54610    render : function (id_base) {
54611        var p     = this.paginator,
54612            c     = p.get('lastPageLinkClass'),
54613            label = p.get('lastPageLinkLabel'),
54614            last  = p.getTotalPages(),
54615            title = p.get('lastPageLinkTitle');
54616
54617        this.link = document.createElement('a');
54618        this.span = document.createElement('span');
54619        this.na   = this.span.cloneNode(false);
54620
54621        setId(this.link, id_base + '-last-link');
54622        this.link.href      = '#';
54623        this.link.className = c;
54624        this.link.innerHTML = label;
54625        this.link.title     = title;
54626        YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
54627
54628        setId(this.span, id_base + '-last-span');
54629        this.span.className = c;
54630        this.span.innerHTML = label;
54631
54632        setId(this.na, id_base + '-last-na');
54633
54634        switch (last) {
54635            case Paginator.VALUE_UNLIMITED :
54636                    this.current = this.na; break;
54637            case p.getCurrentPage() :
54638                    this.current = this.span; break;
54639            default :
54640                    this.current = this.link;
54641        }
54642
54643        return this.current;
54644    },
54645
54646    /**
54647     * Swap the link, span, and na nodes if appropriate.
54648     * @method update
54649     * @param e {CustomEvent} The calling change event (ignored)
54650     */
54651    update : function (e) {
54652        if (e && e.prevValue === e.newValue) {
54653            return;
54654        }
54655
54656        var par   = this.current ? this.current.parentNode : null,
54657            after = this.link;
54658
54659        if (par) {
54660            switch (this.paginator.getTotalPages()) {
54661                case Paginator.VALUE_UNLIMITED :
54662                        after = this.na; break;
54663                case this.paginator.getCurrentPage() :
54664                        after = this.span; break;
54665            }
54666
54667            if (this.current !== after) {
54668                par.replaceChild(after,this.current);
54669                this.current = after;
54670            }
54671        }
54672    },
54673
54674    /**
54675     * Removes the link/span node and clears event listeners
54676     * @method destroy
54677     * @private
54678     */
54679    destroy : function () {
54680        YAHOO.util.Event.purgeElement(this.link);
54681        this.current.parentNode.removeChild(this.current);
54682        this.link = this.span = null;
54683    },
54684
54685    /**
54686     * Listener for the link's onclick event.  Passes to setPage method.
54687     * @method onClick
54688     * @param e {DOMEvent} The click event
54689     */
54690    onClick : function (e) {
54691        YAHOO.util.Event.stopEvent(e);
54692        this.paginator.setPage(this.paginator.getTotalPages());
54693    }
54694};
54695
54696})();
54697(function () {
54698
54699var Paginator = YAHOO.widget.Paginator,
54700    l         = YAHOO.lang,
54701    setId     = YAHOO.util.Dom.generateId;
54702
54703/**
54704 * ui Component to generate the link to jump to the next page.
54705 *
54706 * @namespace YAHOO.widget.Paginator.ui
54707 * @class NextPageLink
54708 * @for YAHOO.widget.Paginator
54709 *
54710 * @constructor
54711 * @param p {Pagintor} Paginator instance to attach to
54712 */
54713Paginator.ui.NextPageLink = function (p) {
54714    this.paginator = p;
54715
54716    p.subscribe('recordOffsetChange', this.update,this,true);
54717    p.subscribe('rowsPerPageChange', this.update,this,true);
54718    p.subscribe('totalRecordsChange', this.update,this,true);
54719    p.subscribe('destroy',this.destroy,this,true);
54720
54721    // TODO: make this work
54722    p.subscribe('nextPageLinkLabelChange', this.update,this,true);
54723    p.subscribe('nextPageLinkClassChange', this.update,this,true);
54724};
54725
54726/**
54727 * Decorates Paginator instances with new attributes. Called during
54728 * Paginator instantiation.
54729 * @method init
54730 * @param p {Paginator} Paginator instance to decorate
54731 * @static
54732 */
54733Paginator.ui.NextPageLink.init = function (p) {
54734
54735    /**
54736     * Used as innerHTML for the next page link/span.
54737     * @attribute nextPageLinkLabel
54738     * @default 'next &gt;'
54739     */
54740    p.setAttributeConfig('nextPageLinkLabel', {
54741        value : 'next &gt;',
54742        validator : l.isString
54743    });
54744
54745    /**
54746     * CSS class assigned to the link/span
54747     * @attribute nextPageLinkClass
54748     * @default 'yui-pg-next'
54749     */
54750    p.setAttributeConfig('nextPageLinkClass', {
54751        value : 'yui-pg-next',
54752        validator : l.isString
54753    });
54754
54755    /**
54756     * Used as title for the next page link.
54757     * @attribute nextPageLinkTitle
54758     * @default 'Next Page'
54759     */
54760    p.setAttributeConfig('nextPageLinkTitle', {
54761        value : 'Next Page',
54762        validator : l.isString
54763    });
54764
54765};
54766
54767Paginator.ui.NextPageLink.prototype = {
54768
54769    /**
54770     * Currently placed HTMLElement node
54771     * @property current
54772     * @type HTMLElement
54773     * @private
54774     */
54775    current   : null,
54776
54777    /**
54778     * Link node
54779     * @property link
54780     * @type HTMLElement
54781     * @private
54782     */
54783    link      : null,
54784
54785    /**
54786     * Span node (inactive link)
54787     * @property span
54788     * @type HTMLElement
54789     * @private
54790     */
54791    span      : null,
54792
54793
54794    /**
54795     * Generate the nodes and return the appropriate node given the current
54796     * pagination state.
54797     * @method render
54798     * @param id_base {string} used to create unique ids for generated nodes
54799     * @return {HTMLElement}
54800     */
54801    render : function (id_base) {
54802        var p     = this.paginator,
54803            c     = p.get('nextPageLinkClass'),
54804            label = p.get('nextPageLinkLabel'),
54805            last  = p.getTotalPages(),
54806            title = p.get('nextPageLinkTitle');
54807
54808        this.link     = document.createElement('a');
54809        this.span     = document.createElement('span');
54810
54811        setId(this.link, id_base + '-next-link');
54812        this.link.href      = '#';
54813        this.link.className = c;
54814        this.link.innerHTML = label;
54815        this.link.title     = title;
54816        YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
54817
54818        setId(this.span, id_base + '-next-span');
54819        this.span.className = c;
54820        this.span.innerHTML = label;
54821
54822        this.current = p.getCurrentPage() === last ? this.span : this.link;
54823
54824        return this.current;
54825    },
54826
54827    /**
54828     * Swap the link and span nodes if appropriate.
54829     * @method update
54830     * @param e {CustomEvent} The calling change event
54831     */
54832    update : function (e) {
54833        if (e && e.prevValue === e.newValue) {
54834            return;
54835        }
54836
54837        var last = this.paginator.getTotalPages(),
54838            par  = this.current ? this.current.parentNode : null;
54839
54840        if (this.paginator.getCurrentPage() !== last) {
54841            if (par && this.current === this.span) {
54842                par.replaceChild(this.link,this.current);
54843                this.current = this.link;
54844            }
54845        } else if (this.current === this.link) {
54846            if (par) {
54847                par.replaceChild(this.span,this.current);
54848                this.current = this.span;
54849            }
54850        }
54851    },
54852
54853    /**
54854     * Removes the link/span node and clears event listeners
54855     * @method destroy
54856     * @private
54857     */
54858    destroy : function () {
54859        YAHOO.util.Event.purgeElement(this.link);
54860        this.current.parentNode.removeChild(this.current);
54861        this.link = this.span = null;
54862    },
54863
54864    /**
54865     * Listener for the link's onclick event.  Passes to setPage method.
54866     * @method onClick
54867     * @param e {DOMEvent} The click event
54868     */
54869    onClick : function (e) {
54870        YAHOO.util.Event.stopEvent(e);
54871        this.paginator.setPage(this.paginator.getNextPage());
54872    }
54873};
54874
54875})();
54876(function () {
54877
54878var Paginator = YAHOO.widget.Paginator,
54879    l         = YAHOO.lang,
54880    setId     = YAHOO.util.Dom.generateId;
54881
54882/**
54883 * ui Component to generate the link to jump to the previous page.
54884 *
54885 * @namespace YAHOO.widget.Paginator.ui
54886 * @class PreviousPageLink
54887 * @for YAHOO.widget.Paginator
54888 *
54889 * @constructor
54890 * @param p {Pagintor} Paginator instance to attach to
54891 */
54892Paginator.ui.PreviousPageLink = function (p) {
54893    this.paginator = p;
54894
54895    p.subscribe('recordOffsetChange',this.update,this,true);
54896    p.subscribe('rowsPerPageChange',this.update,this,true);
54897    p.subscribe('totalRecordsChange',this.update,this,true);
54898    p.subscribe('destroy',this.destroy,this,true);
54899
54900    // TODO: make this work
54901    p.subscribe('previousPageLinkLabelChange',this.update,this,true);
54902    p.subscribe('previousPageLinkClassChange',this.update,this,true);
54903};
54904
54905/**
54906 * Decorates Paginator instances with new attributes. Called during
54907 * Paginator instantiation.
54908 * @method init
54909 * @param p {Paginator} Paginator instance to decorate
54910 * @static
54911 */
54912Paginator.ui.PreviousPageLink.init = function (p) {
54913
54914    /**
54915     * Used as innerHTML for the previous page link/span.
54916     * @attribute previousPageLinkLabel
54917     * @default '&lt; prev'
54918     */
54919    p.setAttributeConfig('previousPageLinkLabel', {
54920        value : '&lt; prev',
54921        validator : l.isString
54922    });
54923
54924    /**
54925     * CSS class assigned to the link/span
54926     * @attribute previousPageLinkClass
54927     * @default 'yui-pg-previous'
54928     */
54929    p.setAttributeConfig('previousPageLinkClass', {
54930        value : 'yui-pg-previous',
54931        validator : l.isString
54932    });
54933
54934    /**
54935     * Used as title for the previous page link.
54936     * @attribute previousPageLinkTitle
54937     * @default 'Previous Page'
54938     */
54939    p.setAttributeConfig('previousPageLinkTitle', {
54940        value : 'Previous Page',
54941        validator : l.isString
54942    });
54943
54944};
54945
54946Paginator.ui.PreviousPageLink.prototype = {
54947
54948    /**
54949     * Currently placed HTMLElement node
54950     * @property current
54951     * @type HTMLElement
54952     * @private
54953     */
54954    current   : null,
54955
54956    /**
54957     * Link node
54958     * @property link
54959     * @type HTMLElement
54960     * @private
54961     */
54962    link      : null,
54963
54964    /**
54965     * Span node (inactive link)
54966     * @property span
54967     * @type HTMLElement
54968     * @private
54969     */
54970    span      : null,
54971
54972
54973    /**
54974     * Generate the nodes and return the appropriate node given the current
54975     * pagination state.
54976     * @method render
54977     * @param id_base {string} used to create unique ids for generated nodes
54978     * @return {HTMLElement}
54979     */
54980    render : function (id_base) {
54981        var p     = this.paginator,
54982            c     = p.get('previousPageLinkClass'),
54983            label = p.get('previousPageLinkLabel'),
54984            title = p.get('previousPageLinkTitle');
54985
54986        this.link     = document.createElement('a');
54987        this.span     = document.createElement('span');
54988
54989        setId(this.link, id_base + '-prev-link');
54990        this.link.href      = '#';
54991        this.link.className = c;
54992        this.link.innerHTML = label;
54993        this.link.title     = title;
54994        YAHOO.util.Event.on(this.link,'click',this.onClick,this,true);
54995
54996        setId(this.span, id_base + '-prev-span');
54997        this.span.className = c;
54998        this.span.innerHTML = label;
54999
55000        this.current = p.getCurrentPage() > 1 ? this.link : this.span;
55001        return this.current;
55002    },
55003
55004    /**
55005     * Swap the link and span nodes if appropriate.
55006     * @method update
55007     * @param e {CustomEvent} The calling change event
55008     */
55009    update : function (e) {
55010        if (e && e.prevValue === e.newValue) {
55011            return;
55012        }
55013
55014        var par = this.current ? this.current.parentNode : null;
55015        if (this.paginator.getCurrentPage() > 1) {
55016            if (par && this.current === this.span) {
55017                par.replaceChild(this.link,this.current);
55018                this.current = this.link;
55019            }
55020        } else {
55021            if (par && this.current === this.link) {
55022                par.replaceChild(this.span,this.current);
55023                this.current = this.span;
55024            }
55025        }
55026    },
55027
55028    /**
55029     * Removes the link/span node and clears event listeners
55030     * @method destroy
55031     * @private
55032     */
55033    destroy : function () {
55034        YAHOO.util.Event.purgeElement(this.link);
55035        this.current.parentNode.removeChild(this.current);
55036        this.link = this.span = null;
55037    },
55038
55039    /**
55040     * Listener for the link's onclick event.  Passes to setPage method.
55041     * @method onClick
55042     * @param e {DOMEvent} The click event
55043     */
55044    onClick : function (e) {
55045        YAHOO.util.Event.stopEvent(e);
55046        this.paginator.setPage(this.paginator.getPreviousPage());
55047    }
55048};
55049
55050})();
55051(function () {
55052
55053var Paginator = YAHOO.widget.Paginator,
55054    l         = YAHOO.lang,
55055    setId     = YAHOO.util.Dom.generateId;
55056
55057/**
55058 * ui Component to generate the rows-per-page dropdown
55059 *
55060 * @namespace YAHOO.widget.Paginator.ui
55061 * @class RowsPerPageDropdown
55062 * @for YAHOO.widget.Paginator
55063 *
55064 * @constructor
55065 * @param p {Pagintor} Paginator instance to attach to
55066 */
55067Paginator.ui.RowsPerPageDropdown = function (p) {
55068    this.paginator = p;
55069
55070    p.subscribe('rowsPerPageChange',this.update,this,true);
55071    p.subscribe('rowsPerPageOptionsChange',this.rebuild,this,true);
55072    p.subscribe('totalRecordsChange',this._handleTotalRecordsChange,this,true);
55073    p.subscribe('destroy',this.destroy,this,true);
55074
55075    // TODO: make this work
55076    p.subscribe('rowsPerPageDropdownClassChange',this.rebuild,this,true);
55077};
55078
55079/**
55080 * Decorates Paginator instances with new attributes. Called during
55081 * Paginator instantiation.
55082 * @method init
55083 * @param p {Paginator} Paginator instance to decorate
55084 * @static
55085 */
55086Paginator.ui.RowsPerPageDropdown.init = function (p) {
55087
55088    /**
55089     * Array of available rows-per-page sizes.  Converted into select options.
55090     * Array values may be positive integers or object literals in the form<br>
55091     * { value : NUMBER, text : STRING }
55092     * @attribute rowsPerPageOptions
55093     * @default []
55094     */
55095    p.setAttributeConfig('rowsPerPageOptions', {
55096        value : [],
55097        validator : l.isArray
55098    });
55099
55100    /**
55101     * CSS class assigned to the select node
55102     * @attribute rowsPerPageDropdownClass
55103     * @default 'yui-pg-rpp-options'
55104     */
55105    p.setAttributeConfig('rowsPerPageDropdownClass', {
55106        value : 'yui-pg-rpp-options',
55107        validator : l.isString
55108    });
55109};
55110
55111Paginator.ui.RowsPerPageDropdown.prototype = {
55112
55113    /**
55114     * select node
55115     * @property select
55116     * @type HTMLElement
55117     * @private
55118     */
55119    select  : null,
55120
55121
55122    /**
55123     * option node for the optional All value
55124     *
55125     * @property all
55126     * @type HTMLElement
55127     * @protected
55128     */
55129    all : null,
55130
55131    /**
55132     * Generate the select and option nodes and returns the select node.
55133     * @method render
55134     * @param id_base {string} used to create unique ids for generated nodes
55135     * @return {HTMLElement}
55136     */
55137    render : function (id_base) {
55138        this.select = document.createElement('select');
55139        setId(this.select, id_base + '-rpp');
55140        this.select.className = this.paginator.get('rowsPerPageDropdownClass');
55141        this.select.title = 'Rows per page';
55142
55143        YAHOO.util.Event.on(this.select,'change',this.onChange,this,true);
55144
55145        this.rebuild();
55146
55147        return this.select;
55148    },
55149
55150    /**
55151     * (Re)generate the select options.
55152     * @method rebuild
55153     */
55154    rebuild : function (e) {
55155        var p       = this.paginator,
55156            sel     = this.select,
55157            options = p.get('rowsPerPageOptions'),
55158            opt,cfg,val,i,len;
55159
55160        this.all = null;
55161
55162        for (i = 0, len = options.length; i < len; ++i) {
55163            cfg = options[i];
55164            opt = sel.options[i] ||
55165                  sel.appendChild(document.createElement('option'));
55166            val = l.isValue(cfg.value) ? cfg.value : cfg;
55167            opt.text = l.isValue(cfg.text) ? cfg.text : cfg;
55168
55169            if (l.isString(val) && val.toLowerCase() === 'all') {
55170                this.all  = opt;
55171                opt.value = p.get('totalRecords');
55172            } else{
55173                opt.value = val;
55174            }
55175
55176        }
55177
55178        while (sel.options.length > options.length) {
55179            sel.removeChild(sel.firstChild);
55180        }
55181
55182        this.update();
55183    },
55184
55185    /**
55186     * Select the appropriate option if changed.
55187     * @method update
55188     * @param e {CustomEvent} The calling change event
55189     */
55190    update : function (e) {
55191        if (e && e.prevValue === e.newValue) {
55192            return;
55193        }
55194
55195        var rpp     = this.paginator.get('rowsPerPage')+'',
55196            options = this.select.options,
55197            i,len;
55198
55199        for (i = 0, len = options.length; i < len; ++i) {
55200            if (options[i].value === rpp) {
55201                options[i].selected = true;
55202                break;
55203            }
55204        }
55205    },
55206
55207    /**
55208     * Listener for the select's onchange event.  Sent to setRowsPerPage method.
55209     * @method onChange
55210     * @param e {DOMEvent} The change event
55211     */
55212    onChange : function (e) {
55213        this.paginator.setRowsPerPage(
55214                parseInt(this.select.options[this.select.selectedIndex].value,10));
55215    },
55216
55217    /**
55218     * Updates the all option value (and Paginator's rowsPerPage attribute if
55219     * necessary) in response to a change in the Paginator's totalRecords.
55220     *
55221     * @method _handleTotalRecordsChange
55222     * @param e {Event} attribute change event
55223     * @protected
55224     */
55225    _handleTotalRecordsChange : function (e) {
55226        if (!this.all || (e && e.prevValue === e.newValue)) {
55227            return;
55228        }
55229
55230        this.all.value = e.newValue;
55231        if (this.all.selected) {
55232            this.paginator.set('rowsPerPage',e.newValue);
55233        }
55234    },
55235
55236    /**
55237     * Removes the select node and clears event listeners
55238     * @method destroy
55239     * @private
55240     */
55241    destroy : function () {
55242        YAHOO.util.Event.purgeElement(this.select);
55243        this.select.parentNode.removeChild(this.select);
55244        this.select = null;
55245    }
55246};
55247
55248})();
55249(function () {
55250
55251var Paginator = YAHOO.widget.Paginator,
55252    l         = YAHOO.lang,
55253    setId     = YAHOO.util.Dom.generateId;
55254
55255/**
55256 * ui Component to generate the jump-to-page dropdown
55257 *
55258 * @namespace YAHOO.widget.Paginator.ui
55259 * @class JumpToPageDropdown
55260 * @for YAHOO.widget.Paginator
55261 *
55262 * @constructor
55263 * @param p {Pagintor} Paginator instance to attach to
55264 */
55265Paginator.ui.JumpToPageDropdown = function (p) {
55266    this.paginator = p;
55267
55268    p.subscribe('rowsPerPageChange',this.rebuild,this,true);
55269    p.subscribe('rowsPerPageOptionsChange',this.rebuild,this,true);
55270    p.subscribe('pageChange',this.update,this,true);
55271    p.subscribe('totalRecordsChange',this.rebuild,this,true);
55272    p.subscribe('destroy',this.destroy,this,true);
55273
55274};
55275
55276/**
55277 * Decorates Paginator instances with new attributes. Called during
55278 * Paginator instantiation.
55279 * @method init
55280 * @param p {Paginator} Paginator instance to decorate
55281 * @static
55282 */
55283Paginator.ui.JumpToPageDropdown.init = function (p) {
55284
55285
55286
55287    /**
55288     * CSS class assigned to the select node
55289     * @attribute jumpToPageDropdownClass
55290     * @default 'yui-pg-jtp-options'
55291     */
55292    p.setAttributeConfig('jumpToPageDropdownClass', {
55293        value : 'yui-pg-jtp-options',
55294        validator : l.isString
55295    });
55296};
55297
55298Paginator.ui.JumpToPageDropdown.prototype = {
55299
55300    /**
55301     * select node
55302     * @property select
55303     * @type HTMLElement
55304     * @private
55305     */
55306    select  : null,
55307
55308
55309
55310    /**
55311     * Generate the select and option nodes and returns the select node.
55312     * @method render
55313     * @param id_base {string} used to create unique ids for generated nodes
55314     * @return {HTMLElement}
55315     */
55316    render : function (id_base) {
55317        this.select = document.createElement('select');
55318        setId(this.select, id_base + '-jtp');
55319        this.select.className = this.paginator.get('jumpToPageDropdownClass');
55320        this.select.title = 'Jump to page';
55321
55322        YAHOO.util.Event.on(this.select,'change',this.onChange,this,true);
55323
55324        this.rebuild();
55325
55326        return this.select;
55327    },
55328
55329    /**
55330     * (Re)generate the select options.
55331     * @method rebuild
55332     */
55333    rebuild : function (e) {
55334        var p       = this.paginator,
55335            sel     = this.select,
55336            numPages = p.getTotalPages(),
55337            opt,i,len;
55338
55339        this.all = null;
55340
55341        for (i = 0, len = numPages; i < len; ++i ) {
55342            opt = sel.options[i] ||
55343                  sel.appendChild(document.createElement('option'));
55344
55345            opt.innerHTML = i + 1;
55346
55347            opt.value = i + 1;
55348
55349
55350        }
55351
55352        for ( i = numPages, len = sel.options.length ; i < len ; i++ ) {
55353            sel.removeChild(sel.lastChild);
55354        }
55355
55356        this.update();
55357    },
55358
55359    /**
55360     * Select the appropriate option if changed.
55361     * @method update
55362     * @param e {CustomEvent} The calling change event
55363     */
55364    update : function (e) {
55365
55366        if (e && e.prevValue === e.newValue) {
55367            return;
55368        }
55369
55370        var cp      = this.paginator.getCurrentPage()+'',
55371            options = this.select.options,
55372            i,len;
55373
55374        for (i = 0, len = options.length; i < len; ++i) {
55375            if (options[i].value === cp) {
55376                options[i].selected = true;
55377                break;
55378            }
55379        }
55380    },
55381
55382    /**
55383     * Listener for the select's onchange event.  Sent to setPage method.
55384     * @method onChange
55385     * @param e {DOMEvent} The change event
55386     */
55387    onChange : function (e) {
55388        this.paginator.setPage(
55389                parseInt(this.select.options[this.select.selectedIndex].value,false));
55390    },
55391
55392
55393
55394    /**
55395     * Removes the select node and clears event listeners
55396     * @method destroy
55397     * @private
55398     */
55399    destroy : function () {
55400        YAHOO.util.Event.purgeElement(this.select);
55401        this.select.parentNode.removeChild(this.select);
55402        this.select = null;
55403    }
55404};
55405
55406})();
55407YAHOO.register("paginator", YAHOO.widget.Paginator, {version: "2.9.0", build: "2800"});
55408/*
55409Copyright (c) 2011, Yahoo! Inc. All rights reserved.
55410Code licensed under the BSD License:
55411http://developer.yahoo.com/yui/license.html
55412version: 2.9.0
55413*/
55414/**
55415 * Mechanism to execute a series of callbacks in a non-blocking queue.  Each callback is executed via setTimout unless configured with a negative timeout, in which case it is run in blocking mode in the same execution thread as the previous callback.  Callbacks can be function references or object literals with the following keys:
55416 * <ul>
55417 *    <li><code>method</code> - {Function} REQUIRED the callback function.</li>
55418 *    <li><code>scope</code> - {Object} the scope from which to execute the callback.  Default is the global window scope.</li>
55419 *    <li><code>argument</code> - {Array} parameters to be passed to method as individual arguments.</li>
55420 *    <li><code>timeout</code> - {number} millisecond delay to wait after previous callback completion before executing this callback.  Negative values cause immediate blocking execution.  Default 0.</li>
55421 *    <li><code>until</code> - {Function} boolean function executed before each iteration.  Return true to indicate completion and proceed to the next callback.</li>
55422 *    <li><code>iterations</code> - {Number} number of times to execute the callback before proceeding to the next callback in the chain. Incompatible with <code>until</code>.</li>
55423 * </ul>
55424 *
55425 * @namespace YAHOO.util
55426 * @class Chain
55427 * @constructor
55428 * @param callback* {Function|Object} Any number of callbacks to initialize the queue
55429*/
55430YAHOO.util.Chain = function () {
55431    /**
55432     * The callback queue
55433     * @property q
55434     * @type {Array}
55435     * @private
55436     */
55437    this.q = [].slice.call(arguments);
55438
55439    /**
55440     * Event fired when the callback queue is emptied via execution (not via
55441     * a call to chain.stop().
55442     * @event end
55443     */
55444    this.createEvent('end');
55445};
55446
55447YAHOO.util.Chain.prototype = {
55448    /**
55449     * Timeout id used to pause or stop execution and indicate the execution state of the Chain.  0 indicates paused or stopped, -1 indicates blocking execution, and any positive number indicates non-blocking execution.
55450     * @property id
55451     * @type {number}
55452     * @private
55453     */
55454    id   : 0,
55455
55456    /**
55457     * Begin executing the chain, or resume execution from the last paused position.
55458     * @method run
55459     * @return {Chain} the Chain instance
55460     */
55461    run : function () {
55462        // Grab the first callback in the queue
55463        var c  = this.q[0],
55464            fn;
55465
55466        // If there is no callback in the queue or the Chain is currently
55467        // in an execution mode, return
55468        if (!c) {
55469            this.fireEvent('end');
55470            return this;
55471        } else if (this.id) {
55472            return this;
55473        }
55474
55475        fn = c.method || c;
55476
55477        if (typeof fn === 'function') {
55478            var o    = c.scope || {},
55479                args = c.argument || [],
55480                ms   = c.timeout || 0,
55481                me   = this;
55482                
55483            if (!(args instanceof Array)) {
55484                args = [args];
55485            }
55486
55487            // Execute immediately if the callback timeout is negative.
55488            if (ms < 0) {
55489                this.id = ms;
55490                if (c.until) {
55491                    for (;!c.until();) {
55492                        // Execute the callback from scope, with argument
55493                        fn.apply(o,args);
55494                    }
55495                } else if (c.iterations) {
55496                    for (;c.iterations-- > 0;) {
55497                        fn.apply(o,args);
55498                    }
55499                } else {
55500                    fn.apply(o,args);
55501                }
55502                this.q.shift();
55503                this.id = 0;
55504                return this.run();
55505            } else {
55506                // If the until condition is set, check if we're done
55507                if (c.until) {
55508                    if (c.until()) {
55509                        // Shift this callback from the queue and execute the next
55510                        // callback
55511                        this.q.shift();
55512                        return this.run();
55513                    }
55514                // Otherwise if either iterations is not set or we're
55515                // executing the last iteration, shift callback from the queue
55516                } else if (!c.iterations || !--c.iterations) {
55517                    this.q.shift();
55518                }
55519
55520                // Otherwise set to execute after the configured timeout
55521                this.id = setTimeout(function () {
55522                    // Execute the callback from scope, with argument
55523                    fn.apply(o,args);
55524                    // Check if the Chain was not paused from inside the callback
55525                    if (me.id) {
55526                        // Indicate ready to run state
55527                        me.id = 0;
55528                        // Start the fun all over again
55529                        me.run();
55530                    }
55531                },ms);
55532            }
55533        }
55534
55535        return this;
55536    },
55537    
55538    /**
55539     * Add a callback to the end of the queue
55540     * @method add
55541     * @param c {Function|Object} the callback function ref or object literal
55542     * @return {Chain} the Chain instance
55543     */
55544    add  : function (c) {
55545        this.q.push(c);
55546        return this;
55547    },
55548
55549    /**
55550     * Pause the execution of the Chain after the current execution of the
55551     * current callback completes.  If called interstitially, clears the
55552     * timeout for the pending callback. Paused Chains can be restarted with
55553     * chain.run()
55554     * @method pause
55555     * @return {Chain} the Chain instance
55556     */
55557    pause: function () {
55558        // Conditional added for Caja compatibility
55559        if (this.id > 0) {
55560            clearTimeout(this.id);
55561        }
55562        this.id = 0;
55563        return this;
55564    },
55565
55566    /**
55567     * Stop and clear the Chain's queue after the current execution of the
55568     * current callback completes.
55569     * @method stop
55570     * @return {Chain} the Chain instance
55571     */
55572    stop : function () { 
55573        this.pause();
55574        this.q = [];
55575        return this;
55576    }
55577};
55578YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);
55579
55580/**
55581 * Augments the Event Utility with a <code>delegate</code> method that 
55582 * facilitates easy creation of delegated event listeners.  (Note: Using CSS 
55583 * selectors as the filtering criteria for delegated event listeners requires 
55584 * inclusion of the Selector Utility.)
55585 *
55586 * @module event-delegate
55587 * @title Event Utility Event Delegation Module
55588 * @namespace YAHOO.util
55589 * @requires event
55590 */
55591
55592(function () {
55593
55594    var Event = YAHOO.util.Event,
55595        Lang = YAHOO.lang,
55596        delegates = [],
55597
55598
55599        getMatch = function(el, selector, container) {
55600        
55601            var returnVal;
55602        
55603            if (!el || el === container) {
55604                returnVal = false;
55605            }
55606            else {
55607                returnVal = YAHOO.util.Selector.test(el, selector) ? el: getMatch(el.parentNode, selector, container);
55608            }
55609        
55610            return returnVal;
55611        
55612        };
55613
55614
55615    Lang.augmentObject(Event, {
55616
55617        /**
55618         * Creates a delegate function used to call event listeners specified 
55619         * via the <code>YAHOO.util.Event.delegate</code> method.
55620         *
55621         * @method _createDelegate
55622         *
55623         * @param {Function} fn        The method (event listener) to call.
55624         * @param {Function|string} filter Function or CSS selector used to 
55625         * determine for what element(s) the event listener should be called.        
55626         * @param {Object}   obj    An arbitrary object that will be 
55627         *                             passed as a parameter to the listener.
55628         * @param {Boolean|object}  overrideContext  If true, the value of the 
55629         *                             obj parameter becomes the execution context
55630         *                          of the listener. If an object, this object
55631         *                          becomes the execution context.
55632         * @return {Function} Function that will call the event listener 
55633         * specified by the <code>YAHOO.util.Event.delegate</code> method.
55634         * @private
55635         * @for Event
55636         * @static
55637         */
55638        _createDelegate: function (fn, filter, obj, overrideContext) {
55639
55640            return function (event) {
55641
55642                var container = this,
55643                    target = Event.getTarget(event),
55644                    selector = filter,
55645
55646                    //    The user might have specified the document object 
55647                    //    as the delegation container, in which case it is not 
55648                    //    nessary to scope the provided CSS selector(s) to the 
55649                    //    delegation container
55650                    bDocument = (container.nodeType === 9),
55651
55652                    matchedEl,
55653                    context,
55654                    sID,
55655                    sIDSelector;
55656
55657
55658                if (Lang.isFunction(filter)) {
55659                    matchedEl = filter(target);
55660                }
55661                else if (Lang.isString(filter)) {
55662
55663                    if (!bDocument) {
55664
55665                        sID = container.id;
55666
55667                        if (!sID) {
55668                            sID = Event.generateId(container);
55669                        }                        
55670
55671                        //    Scope all selectors to the container
55672                        sIDSelector = ("#" + sID + " ");
55673                        selector = (sIDSelector + filter).replace(/,/gi, ("," + sIDSelector));
55674
55675                    }
55676
55677
55678                    if (YAHOO.util.Selector.test(target, selector)) {
55679                        matchedEl = target;
55680                    }
55681                    else if (YAHOO.util.Selector.test(target, ((selector.replace(/,/gi, " *,")) + " *"))) {
55682
55683                        //    The target is a descendant of an element matching 
55684                        //    the selector, so crawl up to find the ancestor that 
55685                        //    matches the selector
55686
55687                        matchedEl = getMatch(target, selector, container);
55688
55689                    }
55690
55691                }
55692
55693
55694                if (matchedEl) {
55695
55696                    //    The default context for delegated listeners is the 
55697                    //    element that matched the filter.
55698
55699                    context = matchedEl;
55700
55701                    if (overrideContext) {
55702                        if (overrideContext === true) {
55703                            context = obj;
55704                        } else {
55705                            context = overrideContext;
55706                        }
55707                    }
55708
55709                    //    Call the listener passing in the container and the 
55710                    //    element that matched the filter in case the user 
55711                    //    needs those.
55712
55713                    return fn.call(context, event, matchedEl, container, obj);
55714
55715                }
55716
55717            };
55718
55719        },
55720
55721
55722        /**
55723         * Appends a delegated event listener.  Delegated event listeners 
55724         * receive three arguments by default: the DOM event, the element  
55725         * specified by the filtering function or CSS selector, and the 
55726         * container element (the element to which the event listener is 
55727         * bound).  (Note: Using the delegate method requires the event-delegate 
55728         * module.  Using CSS selectors as the filtering criteria for delegated 
55729         * event listeners requires inclusion of the Selector Utility.)
55730         *
55731         * @method delegate
55732         *
55733         * @param {String|HTMLElement|Array|NodeList} container An id, an element 
55734         *  reference, or a collection of ids and/or elements to assign the 
55735         *  listener to.
55736         * @param {String}   type     The type of event listener to append
55737         * @param {Function} fn        The method the event invokes
55738         * @param {Function|string} filter Function or CSS selector used to 
55739         * determine for what element(s) the event listener should be called. 
55740         * When a function is specified, the function should return an 
55741         * HTML element.  Using a CSS Selector requires the inclusion of the 
55742         * CSS Selector Utility.
55743         * @param {Object}   obj    An arbitrary object that will be 
55744         *                             passed as a parameter to the listener
55745         * @param {Boolean|object}  overrideContext  If true, the value of the obj parameter becomes
55746         *                             the execution context of the listener. If an
55747         *                             object, this object becomes the execution
55748         *                             context.
55749         * @return {Boolean} Returns true if the action was successful or defered,
55750         *                   false if one or more of the elements 
55751         *                   could not have the listener attached,
55752         *                   or if the operation throws an exception.
55753         * @static
55754         * @for Event
55755         */
55756        delegate: function (container, type, fn, filter, obj, overrideContext) {
55757
55758            var sType = type,
55759                fnMouseDelegate,
55760                fnDelegate;
55761
55762
55763            if (Lang.isString(filter) && !YAHOO.util.Selector) {
55764                YAHOO.log("Using a CSS selector to define the filtering criteria for a delegated listener requires the Selector Utility.", "error", "Event");
55765                return false;
55766            }
55767
55768
55769            if (type == "mouseenter" || type == "mouseleave") {
55770
55771                if (!Event._createMouseDelegate) {
55772                    YAHOO.log("Delegating a " + type + " event requires the event-mouseenter module.", "error", "Event");
55773                    return false;
55774                }
55775
55776                //    Look up the real event--either mouseover or mouseout
55777                sType = Event._getType(type);
55778
55779                fnMouseDelegate = Event._createMouseDelegate(fn, obj, overrideContext);
55780
55781                fnDelegate = Event._createDelegate(function (event, matchedEl, container) {
55782
55783                    return fnMouseDelegate.call(matchedEl, event, container);
55784
55785                }, filter, obj, overrideContext);
55786
55787            }
55788            else {
55789
55790                fnDelegate = Event._createDelegate(fn, filter, obj, overrideContext);
55791
55792            }
55793
55794            delegates.push([container, sType, fn, fnDelegate]);
55795            
55796            return Event.on(container, sType, fnDelegate);
55797
55798        },
55799
55800
55801        /**
55802         * Removes a delegated event listener.
55803         *
55804         * @method removeDelegate
55805         *
55806         * @param {String|HTMLElement|Array|NodeList} container An id, an element 
55807         *  reference, or a collection of ids and/or elements to remove
55808         *  the listener from.
55809         * @param {String} type The type of event to remove.
55810         * @param {Function} fn The method the event invokes.  If fn is
55811         *  undefined, then all event listeners for the type of event are 
55812         *  removed.
55813         * @return {boolean} Returns true if the unbind was successful, false 
55814         *  otherwise.
55815         * @static
55816         * @for Event
55817         */
55818        removeDelegate: function (container, type, fn) {
55819
55820            var sType = type,
55821                returnVal = false,
55822                index,
55823                cacheItem;
55824
55825            //    Look up the real event--either mouseover or mouseout
55826            if (type == "mouseenter" || type == "mouseleave") {
55827                sType = Event._getType(type);
55828            }
55829
55830            index = Event._getCacheIndex(delegates, container, sType, fn);
55831
55832            if (index >= 0) {
55833                cacheItem = delegates[index];
55834            }
55835
55836
55837            if (container && cacheItem) {
55838
55839                returnVal = Event.removeListener(cacheItem[0], cacheItem[1], cacheItem[3]);
55840
55841                if (returnVal) {
55842                    delete delegates[index][2];
55843                    delete delegates[index][3];
55844                    delegates.splice(index, 1);
55845                }        
55846        
55847            }
55848
55849            return returnVal;
55850
55851        }
55852        
55853    });
55854
55855}());
55856
55857
55858/**
55859 * Augments the Event Utility with support for the mouseenter and mouseleave
55860 * events:  A mouseenter event fires the first time the mouse enters an
55861 * element; a mouseleave event first the first time the mouse leaves an
55862 * element.
55863 *
55864 * @module event-mouseenter
55865 * @title Event Utility mouseenter and mouseout Module
55866 * @namespace YAHOO.util
55867 * @requires event
55868 */
55869
55870(function () {
55871
55872    var Event = YAHOO.util.Event,
55873        Lang = YAHOO.lang,
55874
55875        addListener = Event.addListener,
55876        removeListener = Event.removeListener,
55877        getListeners = Event.getListeners,
55878
55879        delegates = [],
55880
55881        specialTypes = {
55882            mouseenter: "mouseover",
55883            mouseleave: "mouseout"
55884        },
55885
55886        remove = function(el, type, fn) {
55887
55888            var index = Event._getCacheIndex(delegates, el, type, fn),
55889                cacheItem,
55890                returnVal;
55891
55892            if (index >= 0) {
55893                cacheItem = delegates[index];
55894            }
55895
55896            if (el && cacheItem) {
55897
55898                //    removeListener will translate the value of type
55899                returnVal = removeListener.call(Event, cacheItem[0], type, cacheItem[3]);
55900
55901                if (returnVal) {
55902                    delete delegates[index][2];
55903                    delete delegates[index][3];
55904                    delegates.splice(index, 1);
55905                }
55906
55907            }
55908
55909            return returnVal;
55910
55911        };
55912
55913
55914    Lang.augmentObject(Event._specialTypes, specialTypes);
55915
55916    Lang.augmentObject(Event, {
55917
55918        /**
55919         * Creates a delegate function used to call mouseover and mouseleave
55920         * event listeners specified via the
55921         * <code>YAHOO.util.Event.addListener</code>
55922         * or <code>YAHOO.util.Event.on</code> method.
55923         *
55924         * @method _createMouseDelegate
55925         *
55926         * @param {Function} fn        The method (event listener) to call
55927         * @param {Object}   obj    An arbitrary object that will be
55928         *                             passed as a parameter to the listener
55929         * @param {Boolean|object}  overrideContext  If true, the value of the
55930         *                             obj parameter becomes the execution context
55931         *                          of the listener. If an object, this object
55932         *                          becomes the execution context.
55933         * @return {Function} Function that will call the event listener
55934         * specified by either the <code>YAHOO.util.Event.addListener</code>
55935         * or <code>YAHOO.util.Event.on</code> method.
55936         * @private
55937         * @static
55938         * @for Event
55939         */
55940        _createMouseDelegate: function (fn, obj, overrideContext) {
55941
55942            return function (event, container) {
55943
55944                var el = this,
55945                    relatedTarget = Event.getRelatedTarget(event),
55946                    context,
55947                    args;
55948
55949                if (el != relatedTarget && !YAHOO.util.Dom.isAncestor(el, relatedTarget)) {
55950
55951                    context = el;
55952
55953                    if (overrideContext) {
55954                        if (overrideContext === true) {
55955                            context = obj;
55956                        } else {
55957                            context = overrideContext;
55958                        }
55959                    }
55960
55961                    // The default args passed back to a mouseenter or
55962                    // mouseleave listener are: the event, and any object
55963                    // the user passed when subscribing
55964
55965                    args = [event, obj];
55966
55967                    // Add the element and delegation container as arguments
55968                    // when delegating mouseenter and mouseleave
55969
55970                    if (container) {
55971                        args.splice(1, 0, el, container);
55972                    }
55973
55974                    return fn.apply(context, args);
55975
55976                }
55977
55978            };
55979
55980        },
55981
55982        addListener: function (el, type, fn, obj, overrideContext) {
55983
55984            var fnDelegate,
55985                returnVal;
55986
55987            if (specialTypes[type]) {
55988
55989                fnDelegate = Event._createMouseDelegate(fn, obj, overrideContext);
55990
55991                fnDelegate.mouseDelegate = true;
55992
55993                delegates.push([el, type, fn, fnDelegate]);
55994
55995                //    addListener will translate the value of type
55996                returnVal = addListener.call(Event, el, type, fnDelegate);
55997
55998            }
55999            else {
56000                returnVal = addListener.apply(Event, arguments);
56001            }
56002
56003            return returnVal;
56004
56005        },
56006
56007        removeListener: function (el, type, fn) {
56008
56009            var returnVal;
56010
56011            if (specialTypes[type]) {
56012                returnVal = remove.apply(Event, arguments);
56013            }
56014            else {
56015                returnVal = removeListener.apply(Event, arguments);
56016            }
56017
56018            return returnVal;
56019
56020        },
56021
56022        getListeners: function (el, type) {
56023
56024            //    If the user specified the type as mouseover or mouseout,
56025            //    need to filter out those used by mouseenter and mouseleave.
56026            //    If the user specified the type as mouseenter or mouseleave,
56027            //    need to filter out the true mouseover and mouseout listeners.
56028
56029            var listeners = [],
56030                elListeners,
56031                bMouseOverOrOut = (type === "mouseover" || type === "mouseout"),
56032                bMouseDelegate,
56033                i,
56034                l;
56035
56036            if (type && (bMouseOverOrOut || specialTypes[type])) {
56037
56038                elListeners = getListeners.call(Event, el, this._getType(type));
56039
56040                if (elListeners) {
56041
56042                    for (i=elListeners.length-1; i>-1; i--) {
56043
56044                        l = elListeners[i];
56045                        bMouseDelegate = l.fn.mouseDelegate;
56046
56047                        if ((specialTypes[type] && bMouseDelegate) || (bMouseOverOrOut && !bMouseDelegate)) {
56048                            listeners.push(l);
56049                        }
56050
56051                    }
56052
56053                }
56054
56055            }
56056            else {
56057                listeners = getListeners.apply(Event, arguments);
56058            }
56059
56060            return (listeners && listeners.length) ? listeners : null;
56061
56062        }
56063
56064    }, true);
56065
56066    Event.on = Event.addListener;
56067
56068}());
56069YAHOO.register("event-mouseenter", YAHOO.util.Event, {version: "2.9.0", build: "2800"});
56070
56071var Y = YAHOO,
56072    Y_DOM = YAHOO.util.Dom,
56073    EMPTY_ARRAY = [],
56074    Y_UA = Y.env.ua,
56075    Y_Lang = Y.lang,
56076    Y_DOC = document,
56077    Y_DOCUMENT_ELEMENT = Y_DOC.documentElement,
56078
56079    Y_DOM_inDoc = Y_DOM.inDocument,
56080    Y_mix = Y_Lang.augmentObject,
56081    Y_guid = Y_DOM.generateId,
56082
56083    Y_getDoc = function(element) {
56084        var doc = Y_DOC;
56085        if (element) {
56086            doc = (element.nodeType === 9) ? element : // element === document
56087                element.ownerDocument || // element === DOM node
56088                element.document || // element === window
56089                Y_DOC; // default
56090        }
56091
56092        return doc;
56093    },
56094
56095    Y_Array = function(o, startIdx) {
56096        var l, a, start = startIdx || 0;
56097
56098        // IE errors when trying to slice HTMLElement collections
56099        try {
56100            return Array.prototype.slice.call(o, start);
56101        } catch (e) {
56102            a = [];
56103            l = o.length;
56104            for (; start < l; start++) {
56105                a.push(o[start]);
56106            }
56107            return a;
56108        }
56109    },
56110
56111    Y_DOM_allById = function(id, root) {
56112        root = root || Y_DOC;
56113        var nodes = [],
56114            ret = [],
56115            i,
56116            node;
56117
56118        if (root.querySelectorAll) {
56119            ret = root.querySelectorAll('[id="' + id + '"]');
56120        } else if (root.all) {
56121            nodes = root.all(id);
56122
56123            if (nodes) {
56124                // root.all may return HTMLElement or HTMLCollection.
56125                // some elements are also HTMLCollection (FORM, SELECT).
56126                if (nodes.nodeName) {
56127                    if (nodes.id === id) { // avoid false positive on name
56128                        ret.push(nodes);
56129                        nodes = EMPTY_ARRAY; // done, no need to filter
56130                    } else { //  prep for filtering
56131                        nodes = [nodes];
56132                    }
56133                }
56134
56135                if (nodes.length) {
56136                    // filter out matches on node.name
56137                    // and element.id as reference to element with id === 'id'
56138                    for (i = 0; node = nodes[i++];) {
56139                        if (node.id === id  ||
56140                                (node.attributes && node.attributes.id &&
56141                                node.attributes.id.value === id)) {
56142                            ret.push(node);
56143                        }
56144                    }
56145                }
56146            }
56147        } else {
56148            ret = [Y_getDoc(root).getElementById(id)];
56149        }
56150
56151        return ret;
56152    };
56153
56154/**
56155 * The selector-native module provides support for native querySelector
56156 * @module dom
56157 * @submodule selector-native
56158 * @for Selector
56159 */
56160
56161/**
56162 * Provides support for using CSS selectors to query the DOM
56163 * @class Selector
56164 * @static
56165 * @for Selector
56166 */
56167
56168var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
56169    OWNER_DOCUMENT = 'ownerDocument',
56170
56171Selector = {
56172    _foundCache: [],
56173
56174    useNative: true,
56175
56176    _compare: ('sourceIndex' in Y_DOCUMENT_ELEMENT) ?
56177        function(nodeA, nodeB) {
56178            var a = nodeA.sourceIndex,
56179                b = nodeB.sourceIndex;
56180
56181            if (a === b) {
56182                return 0;
56183            } else if (a > b) {
56184                return 1;
56185            }
56186
56187            return -1;
56188
56189        } : (Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION] ?
56190        function(nodeA, nodeB) {
56191            if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
56192                return -1;
56193            } else {
56194                return 1;
56195            }
56196        } :
56197        function(nodeA, nodeB) {
56198            var rangeA, rangeB, compare;
56199            if (nodeA && nodeB) {
56200                rangeA = nodeA[OWNER_DOCUMENT].createRange();
56201                rangeA.setStart(nodeA, 0);
56202                rangeB = nodeB[OWNER_DOCUMENT].createRange();
56203                rangeB.setStart(nodeB, 0);
56204                compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
56205            }
56206
56207            return compare;
56208
56209    }),
56210
56211    _sort: function(nodes) {
56212        if (nodes) {
56213            nodes = Y_Array(nodes, 0, true);
56214            if (nodes.sort) {
56215                nodes.sort(Selector._compare);
56216            }
56217        }
56218
56219        return nodes;
56220    },
56221
56222    _deDupe: function(nodes) {
56223        var ret = [],
56224            i, node;
56225
56226        for (i = 0; (node = nodes[i++]);) {
56227            if (!node._found) {
56228                ret[ret.length] = node;
56229                node._found = true;
56230            }
56231        }
56232
56233        for (i = 0; (node = ret[i++]);) {
56234            node._found = null;
56235            node.removeAttribute('_found');
56236        }
56237
56238        return ret;
56239    },
56240
56241    /**
56242     * Retrieves a set of nodes based on a given CSS selector.
56243     * @method query
56244     *
56245     * @param {string} selector The CSS Selector to test the node against.
56246     * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
56247     * @param {Boolean} firstOnly optional Whether or not to return only the first match.
56248     * @return {Array} An array of nodes that match the given selector.
56249     * @static
56250     */
56251    query: function(selector, root, firstOnly, skipNative) {
56252        if (typeof root == 'string') {
56253            root = Y_DOM.get(root);
56254            if (!root) {
56255                return (firstOnly) ? null : [];
56256            }
56257        } else {
56258            root = root || Y_DOC;
56259        }
56260
56261        var ret = [],
56262            useNative = (Selector.useNative && Y_DOC.querySelector && !skipNative),
56263            queries = [[selector, root]],
56264            query,
56265            result,
56266            i,
56267            fn = (useNative) ? Selector._nativeQuery : Selector._bruteQuery;
56268
56269        if (selector && fn) {
56270            // split group into seperate queries
56271            if (!skipNative && // already done if skipping
56272                    (!useNative || root.tagName)) { // split native when element scoping is needed
56273                queries = Selector._splitQueries(selector, root);
56274            }
56275
56276            for (i = 0; (query = queries[i++]);) {
56277                result = fn(query[0], query[1], firstOnly);
56278                if (!firstOnly) { // coerce DOM Collection to Array
56279                    result = Y_Array(result, 0, true);
56280                }
56281                if (result) {
56282                    ret = ret.concat(result);
56283                }
56284            }
56285
56286            if (queries.length > 1) { // remove dupes and sort by doc order
56287                ret = Selector._sort(Selector._deDupe(ret));
56288            }
56289        }
56290
56291        Y.log('query: ' + selector + ' returning: ' + ret.length, 'info', 'Selector');
56292        return (firstOnly) ? (ret[0] || null) : ret;
56293
56294    },
56295
56296    // allows element scoped queries to begin with combinator
56297    // e.g. query('> p', document.body) === query('body > p')
56298    _splitQueries: function(selector, node) {
56299        var groups = selector.split(','),
56300            queries = [],
56301            prefix = '',
56302            i, len;
56303
56304        if (node) {
56305            // enforce for element scoping
56306            if (node.tagName) {
56307                node.id = node.id || Y_guid();
56308                prefix = '[id="' + node.id + '"] ';
56309            }
56310
56311            for (i = 0, len = groups.length; i < len; ++i) {
56312                selector =  prefix + groups[i];
56313                queries.push([selector, node]);
56314            }
56315        }
56316
56317        return queries;
56318    },
56319
56320    _nativeQuery: function(selector, root, one) {
56321        if (Y_UA.webkit && selector.indexOf(':checked') > -1 &&
56322                (Selector.pseudos && Selector.pseudos.checked)) { // webkit (chrome, safari) fails to find "selected"
56323            return Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
56324        }
56325        try {
56326            //Y.log('trying native query with: ' + selector, 'info', 'selector-native');
56327            return root['querySelector' + (one ? '' : 'All')](selector);
56328        } catch(e) { // fallback to brute if available
56329            //Y.log('native query error; reverting to brute query with: ' + selector, 'info', 'selector-native');
56330            return Selector.query(selector, root, one, true); // redo with skipNative true
56331        }
56332    },
56333
56334    filter: function(nodes, selector) {
56335        var ret = [],
56336            i, node;
56337
56338        if (nodes && selector) {
56339            for (i = 0; (node = nodes[i++]);) {
56340                if (Selector.test(node, selector)) {
56341                    ret[ret.length] = node;
56342                }
56343            }
56344        } else {
56345            Y.log('invalid filter input (nodes: ' + nodes +
56346                    ', selector: ' + selector + ')', 'warn', 'Selector');
56347        }
56348
56349        return ret;
56350    },
56351
56352    test: function(node, selector, root) {
56353        var ret = false,
56354            groups = selector.split(','),
56355            useFrag = false,
56356            parent,
56357            item,
56358            items,
56359            frag,
56360            i, j, group;
56361
56362        if (node && node.tagName) { // only test HTMLElements
56363
56364            // we need a root if off-doc
56365            if (!root && !Y_DOM_inDoc(node)) {
56366                parent = node.parentNode;
56367                if (parent) {
56368                    root = parent;
56369                } else { // only use frag when no parent to query
56370                    frag = node[OWNER_DOCUMENT].createDocumentFragment();
56371                    frag.appendChild(node);
56372                    root = frag;
56373                    useFrag = true;
56374                }
56375            }
56376            root = root || node[OWNER_DOCUMENT];
56377
56378            if (!node.id) {
56379                node.id = Y_guid();
56380            }
56381            for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
56382                group += '[id="' + node.id + '"]';
56383                items = Selector.query(group, root);
56384
56385                for (j = 0; item = items[j++];) {
56386                    if (item === node) {
56387                        ret = true;
56388                        break;
56389                    }
56390                }
56391                if (ret) {
56392                    break;
56393                }
56394            }
56395
56396            if (useFrag) { // cleanup
56397                frag.removeChild(node);
56398            }
56399        }
56400
56401        return ret;
56402    }
56403
56404};
56405
56406YAHOO.util.Selector = Selector;
56407/**
56408 * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
56409 * @module dom
56410 * @submodule selector-css2
56411 * @for Selector
56412 */
56413
56414/**
56415 * Provides helper methods for collecting and filtering DOM elements.
56416 */
56417
56418var PARENT_NODE = 'parentNode',
56419    TAG_NAME = 'tagName',
56420    ATTRIBUTES = 'attributes',
56421    COMBINATOR = 'combinator',
56422    PSEUDOS = 'pseudos',
56423
56424    SelectorCSS2 = {
56425        _reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/, // TODO: move?
56426        SORT_RESULTS: true,
56427        _children: function(node, tag) {
56428            var ret = node.children,
56429                i,
56430                children = [],
56431                childNodes,
56432                child;
56433
56434            if (node.children && tag && node.children.tags) {
56435                children = node.children.tags(tag);
56436            } else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children
56437                childNodes = ret || node.childNodes;
56438                ret = [];
56439                for (i = 0; (child = childNodes[i++]);) {
56440                    if (child.tagName) {
56441                        if (!tag || tag === child.tagName) {
56442                            ret.push(child);
56443                        }
56444                    }
56445                }
56446            }
56447
56448            return ret || [];
56449        },
56450
56451        _re: {
56452            //attr: /(\[.*\])/g,
56453            attr: /(\[[^\]]*\])/g,
56454            //esc: /\\[:\[][\w\d\]]*/gi,
56455            esc: /\\[:\[\]\(\)#\.\'\>+~"]/gi,
56456            //pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\))*)/i
56457            pseudos: /(\([^\)]*\))/g
56458        },
56459
56460        /**
56461         * Mapping of shorthand tokens to corresponding attribute selector
56462         * @property shorthand
56463         * @type object
56464         */
56465        shorthand: {
56466            //'\\#([^\\s\\\\(\\[:]*)': '[id=$1]',
56467            '\\#(-?[_a-z]+[-\\w\\uE000]*)': '[id=$1]',
56468            //'\\#([^\\s\\\.:\\[\\]]*)': '[id=$1]',
56469            //'\\.([^\\s\\\\(\\[:]*)': '[className=$1]'
56470            '\\.(-?[_a-z]+[-\\w\\uE000]*)': '[className~=$1]'
56471        },
56472
56473        /**
56474         * List of operators and corresponding boolean functions.
56475         * These functions are passed the attribute and the current node's value of the attribute.
56476         * @property operators
56477         * @type object
56478         */
56479        operators: {
56480            '': function(node, attr) { return !!node.getAttribute(attr); }, // Just test for existence of attribute
56481            //'': '.+',
56482            //'=': '^{val}$', // equality
56483            '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
56484            '|=': '^{val}(?:-|$)' // optional hyphen-delimited
56485        },
56486
56487        pseudos: {
56488           'first-child': function(node) {
56489                return Selector._children(node[PARENT_NODE])[0] === node;
56490            }
56491        },
56492
56493        _bruteQuery: function(selector, root, firstOnly) {
56494            var ret = [],
56495                nodes = [],
56496                tokens = Selector._tokenize(selector),
56497                token = tokens[tokens.length - 1],
56498                rootDoc = Y_getDoc(root),
56499                child,
56500                id,
56501                className,
56502                tagName;
56503
56504
56505            // if we have an initial ID, set to root when in document
56506            /*
56507            if (tokens[0] && rootDoc === root &&
56508                    (id = tokens[0].id) &&
56509                    rootDoc.getElementById(id)) {
56510                root = rootDoc.getElementById(id);
56511            }
56512            */
56513
56514            if (token) {
56515                // prefilter nodes
56516                id = token.id;
56517                className = token.className;
56518                tagName = token.tagName || '*';
56519
56520                if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags
56521                    // try ID first, unless no root.all && root not in document
56522                    // (root.all works off document, but not getElementById)
56523                    // TODO: move to allById?
56524                    if (id && (root.all || (root.nodeType === 9 || Y_DOM_inDoc(root)))) {
56525                        nodes = Y_DOM_allById(id, root);
56526                    // try className
56527                    } else if (className) {
56528                        nodes = root.getElementsByClassName(className);
56529                    } else { // default to tagName
56530                        nodes = root.getElementsByTagName(tagName);
56531                    }
56532
56533                } else { // brute getElementsByTagName('*')
56534                    child = root.firstChild;
56535                    while (child) {
56536                        if (child.tagName) { // only collect HTMLElements
56537                            nodes.push(child);
56538                        }
56539                        child = child.nextSilbing || child.firstChild;
56540                    }
56541                }
56542                if (nodes.length) {
56543                    ret = Selector._filterNodes(nodes, tokens, firstOnly);
56544                }
56545            }
56546
56547            return ret;
56548        },
56549
56550        _filterNodes: function(nodes, tokens, firstOnly) {
56551            var i = 0,
56552                j,
56553                len = tokens.length,
56554                n = len - 1,
56555                result = [],
56556                node = nodes[0],
56557                tmpNode = node,
56558                getters = Selector.getters,
56559                operator,
56560                combinator,
56561                token,
56562                path,
56563                pass,
56564                //FUNCTION = 'function',
56565                value,
56566                tests,
56567                test;
56568
56569            //do {
56570            for (i = 0; (tmpNode = node = nodes[i++]);) {
56571                n = len - 1;
56572                path = null;
56573
56574                testLoop:
56575                while (tmpNode && tmpNode.tagName) {
56576                    token = tokens[n];
56577                    tests = token.tests;
56578                    j = tests.length;
56579                    if (j && !pass) {
56580                        while ((test = tests[--j])) {
56581                            operator = test[1];
56582                            if (getters[test[0]]) {
56583                                value = getters[test[0]](tmpNode, test[0]);
56584                            } else {
56585                                value = tmpNode[test[0]];
56586                                // use getAttribute for non-standard attributes
56587                                if (value === undefined && tmpNode.getAttribute) {
56588                                    value = tmpNode.getAttribute(test[0]);
56589                                }
56590                            }
56591
56592                            if ((operator === '=' && value !== test[2]) ||  // fast path for equality
56593                                (typeof operator !== 'string' && // protect against String.test monkey-patch (Moo)
56594                                operator.test && !operator.test(value)) ||  // regex test
56595                                (!operator.test && // protect against RegExp as function (webkit)
56596                                        typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test
56597
56598                                // skip non element nodes or non-matching tags
56599                                if ((tmpNode = tmpNode[path])) {
56600                                    while (tmpNode &&
56601                                        (!tmpNode.tagName ||
56602                                            (token.tagName && token.tagName !== tmpNode.tagName))
56603                                    ) {
56604                                        tmpNode = tmpNode[path];
56605                                    }
56606                                }
56607                                continue testLoop;
56608                            }
56609                        }
56610                    }
56611
56612                    n--; // move to next token
56613                    // now that we've passed the test, move up the tree by combinator
56614                    if (!pass && (combinator = token.combinator)) {
56615                        path = combinator.axis;
56616                        tmpNode = tmpNode[path];
56617
56618                        // skip non element nodes
56619                        while (tmpNode && !tmpNode.tagName) {
56620                            tmpNode = tmpNode[path];
56621                        }
56622
56623                        if (combinator.direct) { // one pass only
56624                            path = null;
56625                        }
56626
56627                    } else { // success if we made it this far
56628                        result.push(node);
56629                        if (firstOnly) {
56630                            return result;
56631                        }
56632                        break;
56633                    }
56634                }
56635            }// while (tmpNode = node = nodes[++i]);
56636            node = tmpNode = null;
56637            return result;
56638        },
56639
56640        combinators: {
56641            ' ': {
56642                axis: 'parentNode'
56643            },
56644
56645            '>': {
56646                axis: 'parentNode',
56647                direct: true
56648            },
56649
56650
56651            '+': {
56652                axis: 'previousSibling',
56653                direct: true
56654            }
56655        },
56656
56657        _parsers: [
56658            {
56659                name: ATTRIBUTES,
56660                //re: /^\[(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
56661                re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,
56662                fn: function(match, token) {
56663                    var operator = match[2] || '',
56664                        operators = Selector.operators,
56665                        escVal = (match[3]) ? match[3].replace(/\\/g, '') : '',
56666                        test;
56667
56668                    // add prefiltering for ID and CLASS
56669                    if ((match[1] === 'id' && operator === '=') ||
56670                            (match[1] === 'className' &&
56671                            Y_DOCUMENT_ELEMENT.getElementsByClassName &&
56672                            (operator === '~=' || operator === '='))) {
56673                        token.prefilter = match[1];
56674
56675
56676                        match[3] = escVal;
56677
56678                        // escape all but ID for prefilter, which may run through QSA (via Dom.allById)
56679                        token[match[1]] = (match[1] === 'id') ? match[3] : escVal;
56680
56681                    }
56682
56683                    // add tests
56684                    if (operator in operators) {
56685                        test = operators[operator];
56686                        if (typeof test === 'string') {
56687                            match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1');
56688                            test = new RegExp(test.replace('{val}', match[3]));
56689                        }
56690                        match[2] = test;
56691                    }
56692                    if (!token.last || token.prefilter !== match[1]) {
56693                        return match.slice(1);
56694                    }
56695                }
56696
56697            },
56698            {
56699                name: TAG_NAME,
56700                re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
56701                fn: function(match, token) {
56702                    var tag = match[1].toUpperCase();
56703                    token.tagName = tag;
56704
56705                    if (tag !== '*' && (!token.last || token.prefilter)) {
56706                        return [TAG_NAME, '=', tag];
56707                    }
56708                    if (!token.prefilter) {
56709                        token.prefilter = 'tagName';
56710                    }
56711                }
56712            },
56713            {
56714                name: COMBINATOR,
56715                re: /^\s*([>+~]|\s)\s*/,
56716                fn: function(match, token) {
56717                }
56718            },
56719            {
56720                name: PSEUDOS,
56721                re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,
56722                fn: function(match, token) {
56723                    var test = Selector[PSEUDOS][match[1]];
56724                    if (test) { // reorder match array and unescape special chars for tests
56725                        if (match[2]) {
56726                            match[2] = match[2].replace(/\\/g, '');
56727                        }
56728                        return [match[2], test];
56729                    } else { // selector token not supported (possibly missing CSS3 module)
56730                        return false;
56731                    }
56732                }
56733            }
56734            ],
56735
56736        _getToken: function(token) {
56737            return {
56738                tagName: null,
56739                id: null,
56740                className: null,
56741                attributes: {},
56742                combinator: null,
56743                tests: []
56744            };
56745        },
56746
56747        /**
56748            Break selector into token units per simple selector.
56749            Combinator is attached to the previous token.
56750         */
56751        _tokenize: function(selector) {
56752            selector = selector || '';
56753            selector = Selector._replaceShorthand(Y_Lang.trim(selector));
56754            var token = Selector._getToken(),     // one token per simple selector (left selector holds combinator)
56755                query = selector, // original query for debug report
56756                tokens = [],    // array of tokens
56757                found = false,  // whether or not any matches were found this pass
56758                match,         // the regex match
56759                test,
56760                i, parser;
56761
56762            /*
56763                Search for selector patterns, store, and strip them from the selector string
56764                until no patterns match (invalid selector) or we run out of chars.
56765
56766                Multiple attributes and pseudos are allowed, in any order.
56767                for example:
56768                    'form:first-child[type=button]:not(button)[lang|=en]'
56769            */
56770
56771            outer:
56772            do {
56773                found = false; // reset after full pass
56774
56775                for (i = 0; (parser = Selector._parsers[i++]);) {
56776                    if ( (match = parser.re.exec(selector)) ) { // note assignment
56777                        if (parser.name !== COMBINATOR ) {
56778                            token.selector = selector;
56779                        }
56780                        selector = selector.replace(match[0], ''); // strip current match from selector
56781                        if (!selector.length) {
56782                            token.last = true;
56783                        }
56784
56785                        if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
56786                            match[1] = Selector._attrFilters[match[1]];
56787                        }
56788
56789                        test = parser.fn(match, token);
56790                        if (test === false) { // selector not supported
56791                            found = false;
56792                            break outer;
56793                        } else if (test) {
56794                            token.tests.push(test);
56795                        }
56796
56797                        if (!selector.length || parser.name === COMBINATOR) {
56798                            tokens.push(token);
56799                            token = Selector._getToken(token);
56800                            if (parser.name === COMBINATOR) {
56801                                token.combinator = Selector.combinators[match[1]];
56802                            }
56803                        }
56804                        found = true;
56805
56806
56807                    }
56808                }
56809            } while (found && selector.length);
56810
56811            if (!found || selector.length) { // not fully parsed
56812                Y.log('query: ' + query + ' contains unsupported token in: ' + selector, 'warn', 'Selector');
56813                tokens = [];
56814            }
56815            return tokens;
56816        },
56817
56818        _replaceShorthand: function(selector) {
56819            var shorthand = Selector.shorthand,
56820                esc = selector.match(Selector._re.esc), // pull escaped colon, brackets, etc.
56821                attrs,
56822                pseudos,
56823                re, i, len;
56824
56825            if (esc) {
56826                selector = selector.replace(Selector._re.esc, '\uE000');
56827            }
56828
56829            attrs = selector.match(Selector._re.attr);
56830            pseudos = selector.match(Selector._re.pseudos);
56831
56832            if (attrs) {
56833                selector = selector.replace(Selector._re.attr, '\uE001');
56834            }
56835
56836            if (pseudos) {
56837                selector = selector.replace(Selector._re.pseudos, '\uE002');
56838            }
56839
56840
56841            for (re in shorthand) {
56842                if (shorthand.hasOwnProperty(re)) {
56843                    selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]);
56844                }
56845            }
56846
56847            if (attrs) {
56848                for (i = 0, len = attrs.length; i < len; ++i) {
56849                    selector = selector.replace(/\uE001/, attrs[i]);
56850                }
56851            }
56852
56853            if (pseudos) {
56854                for (i = 0, len = pseudos.length; i < len; ++i) {
56855                    selector = selector.replace(/\uE002/, pseudos[i]);
56856                }
56857            }
56858
56859            selector = selector.replace(/\[/g, '\uE003');
56860            selector = selector.replace(/\]/g, '\uE004');
56861
56862            selector = selector.replace(/\(/g, '\uE005');
56863            selector = selector.replace(/\)/g, '\uE006');
56864
56865            if (esc) {
56866                for (i = 0, len = esc.length; i < len; ++i) {
56867                    selector = selector.replace('\uE000', esc[i]);
56868                }
56869            }
56870
56871            return selector;
56872        },
56873
56874        _attrFilters: {
56875            'class': 'className',
56876            'for': 'htmlFor'
56877        },
56878
56879        getters: {
56880            href: function(node, attr) {
56881                return Y_DOM.getAttribute(node, attr);
56882            }
56883        }
56884    };
56885
56886Y_mix(Selector, SelectorCSS2, true);
56887Selector.getters.src = Selector.getters.rel = Selector.getters.href;
56888
56889// IE wants class with native queries
56890if (Selector.useNative && Y_DOC.querySelector) {
56891    Selector.shorthand['\\.([^\\s\\\\(\\[:]*)'] = '[class~=$1]';
56892}
56893
56894/**
56895 * The selector css3 module provides support for css3 selectors.
56896 * @module dom
56897 * @submodule selector-css3
56898 * @for Selector
56899 */
56900
56901/*
56902    an+b = get every _a_th node starting at the _b_th
56903    0n+b = no repeat ("0" and "n" may both be omitted (together) , e.g. "0n+1" or "1", not "0+1"), return only the _b_th element
56904    1n+b =  get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n")
56905    an+0 = get every _a_th element, "0" may be omitted
56906*/
56907
56908Selector._reNth = /^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;
56909
56910Selector._getNth = function(node, expr, tag, reverse) {
56911    Selector._reNth.test(expr);
56912    var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_)
56913        n = RegExp.$2, // "n"
56914        oddeven = RegExp.$3, // "odd" or "even"
56915        b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_
56916        result = [],
56917        siblings = Selector._children(node.parentNode, tag),
56918        op;
56919
56920    if (oddeven) {
56921        a = 2; // always every other
56922        op = '+';
56923        n = 'n';
56924        b = (oddeven === 'odd') ? 1 : 0;
56925    } else if ( isNaN(a) ) {
56926        a = (n) ? 1 : 0; // start from the first or no repeat
56927    }
56928
56929    if (a === 0) { // just the first
56930        if (reverse) {
56931            b = siblings.length - b + 1;
56932        }
56933
56934        if (siblings[b - 1] === node) {
56935            return true;
56936        } else {
56937            return false;
56938        }
56939
56940    } else if (a < 0) {
56941        reverse = !!reverse;
56942        a = Math.abs(a);
56943    }
56944
56945    if (!reverse) {
56946        for (var i = b - 1, len = siblings.length; i < len; i += a) {
56947            if ( i >= 0 && siblings[i] === node ) {
56948                return true;
56949            }
56950        }
56951    } else {
56952        for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) {
56953            if ( i < len && siblings[i] === node ) {
56954                return true;
56955            }
56956        }
56957    }
56958    return false;
56959};
56960
56961Y_mix(Selector.pseudos, {
56962    'root': function(node) {
56963        return node === node.ownerDocument.documentElement;
56964    },
56965
56966    'nth-child': function(node, expr) {
56967        return Selector._getNth(node, expr);
56968    },
56969
56970    'nth-last-child': function(node, expr) {
56971        return Selector._getNth(node, expr, null, true);
56972    },
56973
56974    'nth-of-type': function(node, expr) {
56975        return Selector._getNth(node, expr, node.tagName);
56976    },
56977
56978    'nth-last-of-type': function(node, expr) {
56979        return Selector._getNth(node, expr, node.tagName, true);
56980    },
56981
56982    'last-child': function(node) {
56983        var children = Selector._children(node.parentNode);
56984        return children[children.length - 1] === node;
56985    },
56986
56987    'first-of-type': function(node) {
56988        return Selector._children(node.parentNode, node.tagName)[0] === node;
56989    },
56990
56991    'last-of-type': function(node) {
56992        var children = Selector._children(node.parentNode, node.tagName);
56993        return children[children.length - 1] === node;
56994    },
56995
56996    'only-child': function(node) {
56997        var children = Selector._children(node.parentNode);
56998        return children.length === 1 && children[0] === node;
56999    },
57000
57001    'only-of-type': function(node) {
57002        var children = Selector._children(node.parentNode, node.tagName);
57003        return children.length === 1 && children[0] === node;
57004    },
57005
57006    'empty': function(node) {
57007        return node.childNodes.length === 0;
57008    },
57009
57010    'not': function(node, expr) {
57011        return !Selector.test(node, expr);
57012    },
57013
57014    'contains': function(node, expr) {
57015        var text = node.innerText || node.textContent || '';
57016        return text.indexOf(expr) > -1;
57017    },
57018
57019    'checked': function(node) {
57020        return (node.checked === true || node.selected === true);
57021    },
57022
57023    enabled: function(node) {
57024        return (node.disabled !== undefined && !node.disabled);
57025    },
57026
57027    disabled: function(node) {
57028        return (node.disabled);
57029    }
57030});
57031
57032Y_mix(Selector.operators, {
57033    '^=': '^{val}', // Match starts with value
57034    '!=': function(node, attr, val) { return node[attr] !== val; }, // Match starts with value
57035    '$=': '{val}$', // Match ends with value
57036    '*=': '{val}' // Match contains value as substring
57037});
57038
57039Selector.combinators['~'] = {
57040    axis: 'previousSibling'
57041};
57042YAHOO.register("selector", YAHOO.util.Selector, {version: "2.9.0", build: "2800"});
57043
57044
57045
57046/****************************************************************************/
57047/****************************************************************************/
57048/****************************************************************************/
57049
57050var Dom = YAHOO.util.Dom;
57051
57052/**
57053 * The ColumnSet class defines and manages a DataTable's Columns,
57054 * including nested hierarchies and access to individual Column instances.
57055 *
57056 * @namespace YAHOO.widget
57057 * @class ColumnSet
57058 * @uses YAHOO.util.EventProvider
57059 * @constructor
57060 * @param aDefinitions {Object[]} Array of object literals that define cells in
57061 * the THEAD.
57062 */
57063YAHOO.widget.ColumnSet = function(aDefinitions) {
57064    this._sId = Dom.generateId(null, "yui-cs"); // "yui-cs" + YAHOO.widget.ColumnSet._nCount;
57065
57066    // First clone the defs
57067    aDefinitions = YAHOO.widget.DataTable._cloneObject(aDefinitions);
57068    this._init(aDefinitions);
57069
57070    YAHOO.widget.ColumnSet._nCount++;
57071    YAHOO.log("ColumnSet initialized", "info", this.toString());
57072};
57073
57074/////////////////////////////////////////////////////////////////////////////
57075//
57076// Private member variables
57077//
57078/////////////////////////////////////////////////////////////////////////////
57079
57080/**
57081 * Internal class variable to index multiple ColumnSet instances.
57082 *
57083 * @property ColumnSet._nCount
57084 * @type Number
57085 * @private
57086 * @static
57087 */
57088YAHOO.widget.ColumnSet._nCount = 0;
57089
57090YAHOO.widget.ColumnSet.prototype = {
57091    /**
57092     * Unique instance name.
57093     *
57094     * @property _sId
57095     * @type String
57096     * @private
57097     */
57098    _sId : null,
57099
57100    /**
57101     * Array of object literal Column definitions passed to the constructor.
57102     *
57103     * @property _aDefinitions
57104     * @type Object[]
57105     * @private
57106     */
57107    _aDefinitions : null,
57108
57109    /////////////////////////////////////////////////////////////////////////////
57110    //
57111    // Public member variables
57112    //
57113    /////////////////////////////////////////////////////////////////////////////
57114
57115    /**
57116     * Top-down tree representation of Column hierarchy.
57117     *
57118     * @property tree
57119     * @type YAHOO.widget.Column[]
57120     */
57121    tree : null,
57122
57123    /**
57124     * Flattened representation of all Columns.
57125     *
57126     * @property flat
57127     * @type YAHOO.widget.Column[]
57128     * @default []
57129     */
57130    flat : null,
57131
57132    /**
57133     * Array of Columns that map one-to-one to a table column.
57134     *
57135     * @property keys
57136     * @type YAHOO.widget.Column[]
57137     * @default []
57138     */
57139    keys : null,
57140
57141    /**
57142     * ID index of nested parent hierarchies for HEADERS accessibility attribute.
57143     *
57144     * @property headers
57145     * @type String[]
57146     * @default []
57147     */
57148    headers : null,
57149
57150    /////////////////////////////////////////////////////////////////////////////
57151    //
57152    // Private methods
57153    //
57154    /////////////////////////////////////////////////////////////////////////////
57155
57156    /**
57157     * Initializes ColumnSet instance with data from Column definitions.
57158     *
57159     * @method _init
57160     * @param aDefinitions {Object[]} Array of object literals that define cells in
57161     * the THEAD .
57162     * @private
57163     */
57164
57165    _init : function(aDefinitions) {        
57166        // DOM tree representation of all Columns
57167        var tree = [];
57168        // Flat representation of all Columns
57169        var flat = [];
57170        // Flat representation of only Columns that are meant to display data
57171        var keys = [];
57172        // Array of HEADERS attribute values for all keys in the "keys" array
57173        var headers = [];
57174
57175        // Tracks current node list depth being tracked
57176        var nodeDepth = -1;
57177
57178        // Internal recursive function to define Column instances
57179        var parseColumns = function(nodeList, parent) {
57180            // One level down
57181            nodeDepth++;
57182
57183            // Create corresponding tree node if not already there for this depth
57184            if(!tree[nodeDepth]) {
57185                tree[nodeDepth] = [];
57186            }
57187
57188
57189            // Parse each node at this depth for attributes and any children
57190            for(var j=0; j<nodeList.length; j++) {
57191                var currentNode = nodeList[j];
57192
57193                // Instantiate a new Column for each node
57194                var oColumn = new YAHOO.widget.Column(currentNode);
57195                
57196                // Cross-reference Column ID back to the original object literal definition
57197                currentNode.yuiColumnId = oColumn._sId;
57198                
57199                // Add the new Column to the flat list
57200                flat.push(oColumn);
57201
57202                // Assign its parent as an attribute, if applicable
57203                if(parent) {
57204                    oColumn._oParent = parent;
57205                }
57206
57207                // The Column has descendants
57208                if(YAHOO.lang.isArray(currentNode.children)) {
57209                    oColumn.children = currentNode.children;
57210
57211                    // Determine COLSPAN value for this Column
57212                    var terminalChildNodes = 0;
57213                    var countTerminalChildNodes = function(ancestor) {
57214                        var descendants = ancestor.children;
57215                        // Drill down each branch and count terminal nodes
57216                        for(var k=0; k<descendants.length; k++) {
57217                            // Keep drilling down
57218                            if(YAHOO.lang.isArray(descendants[k].children)) {
57219                                countTerminalChildNodes(descendants[k]);
57220                            }
57221                            // Reached branch terminus
57222                            else {
57223                                terminalChildNodes++;
57224                            }
57225                        }
57226                    };
57227                    countTerminalChildNodes(currentNode);
57228                    oColumn._nColspan = terminalChildNodes;
57229
57230                    // Cascade certain properties to children if not defined on their own
57231                    var currentChildren = currentNode.children;
57232                    for(var k=0; k<currentChildren.length; k++) {
57233                        var child = currentChildren[k];
57234                        if(oColumn.className && (child.className === undefined)) {
57235                            child.className = oColumn.className;
57236                        }
57237                        if(oColumn.editor && (child.editor === undefined)) {
57238                            child.editor = oColumn.editor;
57239                        }
57240                        //TODO: Deprecated
57241                        if(oColumn.editorOptions && (child.editorOptions === undefined)) {
57242                            child.editorOptions = oColumn.editorOptions;
57243                        }
57244                        if(oColumn.formatter && (child.formatter === undefined)) {
57245                            child.formatter = oColumn.formatter;
57246                        }
57247                        if(oColumn.resizeable && (child.resizeable === undefined)) {
57248                            child.resizeable = oColumn.resizeable;
57249                        }
57250                        if(oColumn.sortable && (child.sortable === undefined)) {
57251                            child.sortable = oColumn.sortable;
57252                        }
57253                        if(oColumn.hidden) {
57254                            child.hidden = true;
57255                        }
57256                        if(oColumn.width && (child.width === undefined)) {
57257                            child.width = oColumn.width;
57258                        }
57259                        if(oColumn.minWidth && (child.minWidth === undefined)) {
57260                            child.minWidth = oColumn.minWidth;
57261                        }
57262                        if(oColumn.maxAutoWidth && (child.maxAutoWidth === undefined)) {
57263                            child.maxAutoWidth = oColumn.maxAutoWidth;
57264                        }
57265                        // Backward compatibility
57266                        if(oColumn.type && (child.type === undefined)) {
57267                            child.type = oColumn.type;
57268                        }
57269                        if(oColumn.type && !oColumn.formatter) {
57270                            YAHOO.log("The property type has been" +
57271                            " deprecated in favor of formatter", "warn", oColumn.toString());
57272                            oColumn.formatter = oColumn.type;
57273                        }
57274                        if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) {
57275                            YAHOO.log("The property text has been" +
57276                            " deprecated in favor of label", "warn", oColumn.toString());
57277                            oColumn.label = oColumn.text;
57278                        }
57279                        if(oColumn.parser) {
57280                            YAHOO.log("The property parser is no longer supported",
57281                            "warn", this.toString());
57282                        }
57283                        if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) ||
57284                                (oColumn.sortOptions.descFunction))) {
57285                            YAHOO.log("The properties sortOptions.ascFunction and " +
57286                            " sortOptions.descFunction have been deprecated in favor " +
57287                            " of sortOptions.sortFunction", "warn", oColumn.toString());
57288                        }
57289                    }
57290
57291                    // The children themselves must also be parsed for Column instances
57292                    if(!tree[nodeDepth+1]) {
57293                        tree[nodeDepth+1] = [];
57294                    }
57295                    parseColumns(currentChildren, oColumn);
57296                }
57297                // This Column does not have any children
57298                else {
57299                    oColumn._nKeyIndex = keys.length;
57300                    oColumn._nColspan = 1;
57301                    keys.push(oColumn);
57302                }
57303
57304                // Add the Column to the top-down tree
57305                tree[nodeDepth].push(oColumn);
57306            }
57307            nodeDepth--;
57308        };
57309
57310        // Parse out Column instances from the array of object literals
57311        if(YAHOO.lang.isArray(aDefinitions)) {
57312            parseColumns(aDefinitions);
57313
57314            // Store the array
57315            this._aDefinitions = aDefinitions;
57316        }
57317        else {
57318            YAHOO.log("Could not initialize ColumnSet due to invalid definitions","error");
57319            return null;
57320        }
57321
57322        var i;
57323
57324        // Determine ROWSPAN value for each Column in the tree
57325        var parseTreeForRowspan = function(tree) {
57326            var maxRowDepth = 1;
57327            var currentRow;
57328            var currentColumn;
57329
57330            // Calculate the max depth of descendants for this row
57331            var countMaxRowDepth = function(row, tmpRowDepth) {
57332                tmpRowDepth = tmpRowDepth || 1;
57333
57334                for(var n=0; n<row.length; n++) {
57335                    var col = row[n];
57336                    // Column has children, so keep counting
57337                    if(YAHOO.lang.isArray(col.children)) {
57338                        tmpRowDepth++;
57339                        countMaxRowDepth(col.children, tmpRowDepth);
57340                        tmpRowDepth--;
57341                    }
57342                    // No children, is it the max depth?
57343                    else {
57344                        if(tmpRowDepth > maxRowDepth) {
57345                            maxRowDepth = tmpRowDepth;
57346                        }
57347                    }
57348
57349                }
57350            };
57351
57352            // Count max row depth for each row
57353            for(var m=0; m<tree.length; m++) {
57354                currentRow = tree[m];
57355                countMaxRowDepth(currentRow);
57356
57357                // Assign the right ROWSPAN values to each Column in the row
57358                for(var p=0; p<currentRow.length; p++) {
57359                    currentColumn = currentRow[p];
57360                    if(!YAHOO.lang.isArray(currentColumn.children)) {
57361                        currentColumn._nRowspan = maxRowDepth;
57362                    }
57363                    else {
57364                        currentColumn._nRowspan = 1;
57365                    }
57366                }
57367
57368                // Reset counter for next row
57369                maxRowDepth = 1;
57370            }
57371        };
57372        parseTreeForRowspan(tree);
57373
57374        // Store tree index values
57375        for(i=0; i<tree[0].length; i++) {
57376            tree[0][i]._nTreeIndex = i;
57377        }
57378
57379        // Store header relationships in an array for HEADERS attribute
57380        var recurseAncestorsForHeaders = function(i, oColumn) {
57381            headers[i].push(oColumn.getSanitizedKey());
57382            if(oColumn._oParent) {
57383                recurseAncestorsForHeaders(i, oColumn._oParent);
57384            }
57385        };
57386        for(i=0; i<keys.length; i++) {
57387            headers[i] = [];
57388            recurseAncestorsForHeaders(i, keys[i]);
57389            headers[i] = headers[i].reverse();
57390        }
57391
57392        // Save to the ColumnSet instance
57393        this.tree = tree;
57394        this.flat = flat;
57395        this.keys = keys;
57396        this.headers = headers;
57397    },
57398
57399    /////////////////////////////////////////////////////////////////////////////
57400    //
57401    // Public methods
57402    //
57403    /////////////////////////////////////////////////////////////////////////////
57404
57405    /**
57406     * Returns unique name of the ColumnSet instance.
57407     *
57408     * @method getId
57409     * @return {String} Unique name of the ColumnSet instance.
57410     */
57411
57412    getId : function() {
57413        return this._sId;
57414    },
57415
57416    /**
57417     * ColumnSet instance name, for logging.
57418     *
57419     * @method toString
57420     * @return {String} Unique name of the ColumnSet instance.
57421     */
57422
57423    toString : function() {
57424        return "ColumnSet instance " + this._sId;
57425    },
57426
57427    /**
57428     * Public accessor to the definitions array.
57429     *
57430     * @method getDefinitions
57431     * @return {Object[]} Array of object literal Column definitions.
57432     */
57433
57434    getDefinitions : function() {
57435        var aDefinitions = this._aDefinitions;
57436        
57437        // Internal recursive function to define Column instances
57438        var parseColumns = function(nodeList, oSelf) {
57439            // Parse each node at this depth for attributes and any children
57440            for(var j=0; j<nodeList.length; j++) {
57441                var currentNode = nodeList[j];
57442                
57443                // Get the Column for each node
57444                var oColumn = oSelf.getColumnById(currentNode.yuiColumnId);
57445                
57446                if(oColumn) {    
57447                    // Update the current values
57448                    var oDefinition = oColumn.getDefinition();
57449                    for(var name in oDefinition) {
57450                        if(YAHOO.lang.hasOwnProperty(oDefinition, name)) {
57451                            currentNode[name] = oDefinition[name];
57452                        }
57453                    }
57454                }
57455                            
57456                // The Column has descendants
57457                if(YAHOO.lang.isArray(currentNode.children)) {
57458                    // The children themselves must also be parsed for Column instances
57459                    parseColumns(currentNode.children, oSelf);
57460                }
57461            }
57462        };
57463
57464        parseColumns(aDefinitions, this);
57465        this._aDefinitions = aDefinitions;
57466        return aDefinitions;
57467    },
57468
57469    /**
57470     * Returns Column instance with given ID.
57471     *
57472     * @method getColumnById
57473     * @param column {String} Column ID.
57474     * @return {YAHOO.widget.Column} Column instance.
57475     */
57476
57477    getColumnById : function(column) {
57478        if(YAHOO.lang.isString(column)) {
57479            var allColumns = this.flat;
57480            for(var i=allColumns.length-1; i>-1; i--) {
57481                if(allColumns[i]._sId === column) {
57482                    return allColumns[i];
57483                }
57484            }
57485        }
57486        return null;
57487    },
57488
57489    /**
57490     * Returns Column instance with given key or ColumnSet key index.
57491     *
57492     * @method getColumn
57493     * @param column {String | Number} Column key or ColumnSet key index.
57494     * @return {YAHOO.widget.Column} Column instance.
57495     */
57496
57497    getColumn : function(column) {
57498        if(YAHOO.lang.isNumber(column) && this.keys[column]) {
57499            return this.keys[column];
57500        }
57501        else if(YAHOO.lang.isString(column)) {
57502            var allColumns = this.flat;
57503            var aColumns = [];
57504            for(var i=0; i<allColumns.length; i++) {
57505                if(allColumns[i].key === column) {
57506                    aColumns.push(allColumns[i]);
57507                }
57508            }
57509            if(aColumns.length === 1) {
57510                return aColumns[0];
57511            }
57512            else if(aColumns.length > 1) {
57513                return aColumns;
57514            }
57515        }
57516        return null;
57517    },
57518
57519    /**
57520     * Public accessor returns array of given Column's desendants (if any), including itself.
57521     *
57522     * @method getDescendants
57523     * @parem {YAHOO.widget.Column} Column instance.
57524     * @return {Array} Array including the Column itself and all descendants (if any).
57525     */
57526    getDescendants : function(oColumn) {
57527        var oSelf = this;
57528        var allDescendants = [];
57529        var i;
57530
57531        // Recursive function to loop thru all children
57532        var parse = function(oParent) {
57533            allDescendants.push(oParent);
57534            // This Column has children
57535            if(oParent.children) {
57536                for(i=0; i<oParent.children.length; i++) {
57537                    parse(oSelf.getColumn(oParent.children[i].key));
57538                }
57539            }
57540        };
57541        parse(oColumn);
57542
57543        return allDescendants;
57544    }
57545};
57546
57547/****************************************************************************/
57548/****************************************************************************/
57549/****************************************************************************/
57550
57551/**
57552 * The Column class defines and manages attributes of DataTable Columns
57553 *
57554 * @namespace YAHOO.widget
57555 * @class Column
57556 * @constructor
57557 * @param oConfigs {Object} Object literal of definitions.
57558 */
57559YAHOO.widget.Column = function(oConfigs) {
57560    this._sId = Dom.generateId(null, "yui-col"); // "yui-col" + YAHOO.widget.Column._nCount;
57561    
57562    // Object literal defines Column attributes
57563    if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
57564        for(var sConfig in oConfigs) {
57565            if(sConfig) {
57566                this[sConfig] = oConfigs[sConfig];
57567            }
57568        }
57569    }
57570
57571    // Assign a key if not found
57572    if(!YAHOO.lang.isValue(this.key)) {
57573        this.key = Dom.generateId(null, "yui-dt-col"); //"yui-dt-col" + YAHOO.widget.Column._nCount;
57574    }
57575    
57576    // Assign a field if not found, defaults to key
57577    if(!YAHOO.lang.isValue(this.field)) {
57578        this.field = this.key;
57579    }
57580
57581    // Increment counter
57582    YAHOO.widget.Column._nCount++;
57583
57584    // Backward compatibility
57585    if(this.width && !YAHOO.lang.isNumber(this.width)) {
57586        this.width = null;
57587        YAHOO.log("The Column property width must be a number", "warn", this.toString());
57588    }
57589    if(this.editor && YAHOO.lang.isString(this.editor)) {
57590        this.editor = new YAHOO.widget.CellEditor(this.editor, this.editorOptions);
57591        YAHOO.log("The Column property editor must be an instance of YAHOO.widget.CellEditor", "warn", this.toString());
57592    }
57593};
57594
57595/////////////////////////////////////////////////////////////////////////////
57596//
57597// Private member variables
57598//
57599/////////////////////////////////////////////////////////////////////////////
57600
57601YAHOO.lang.augmentObject(YAHOO.widget.Column, {
57602    /**
57603     * Internal class variable to index multiple Column instances.
57604     *
57605     * @property Column._nCount
57606     * @type Number
57607     * @private
57608     * @static
57609     */
57610    _nCount : 0,
57611
57612    formatCheckbox : function(elCell, oRecord, oColumn, oData) {
57613        YAHOO.log("The method YAHOO.widget.Column.formatCheckbox() has been" +
57614        " deprecated in favor of YAHOO.widget.DataTable.formatCheckbox()", "warn",
57615        "YAHOO.widget.Column.formatCheckbox");
57616        YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData);
57617    },
57618
57619    formatCurrency : function(elCell, oRecord, oColumn, oData) {
57620        YAHOO.log("The method YAHOO.widget.Column.formatCurrency() has been" +
57621        " deprecated in favor of YAHOO.widget.DataTable.formatCurrency()", "warn",
57622        "YAHOO.widget.Column.formatCurrency");
57623        YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData);
57624    },
57625
57626    formatDate : function(elCell, oRecord, oColumn, oData) {
57627        YAHOO.log("The method YAHOO.widget.Column.formatDate() has been" +
57628        " deprecated in favor of YAHOO.widget.DataTable.formatDate()", "warn",
57629        "YAHOO.widget.Column.formatDate");
57630        YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData);
57631    },
57632
57633    formatEmail : function(elCell, oRecord, oColumn, oData) {
57634        YAHOO.log("The method YAHOO.widget.Column.formatEmail() has been" +
57635        " deprecated in favor of YAHOO.widget.DataTable.formatEmail()", "warn",
57636        "YAHOO.widget.Column.formatEmail");
57637        YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData);
57638    },
57639
57640    formatLink : function(elCell, oRecord, oColumn, oData) {
57641        YAHOO.log("The method YAHOO.widget.Column.formatLink() has been" +
57642        " deprecated in favor of YAHOO.widget.DataTable.formatLink()", "warn",
57643        "YAHOO.widget.Column.formatLink");
57644        YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData);
57645    },
57646
57647    formatNumber : function(elCell, oRecord, oColumn, oData) {
57648        YAHOO.log("The method YAHOO.widget.Column.formatNumber() has been" +
57649        " deprecated in favor of YAHOO.widget.DataTable.formatNumber()", "warn",
57650        "YAHOO.widget.Column.formatNumber");
57651        YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData);
57652    },
57653
57654    formatSelect : function(elCell, oRecord, oColumn, oData) {
57655        YAHOO.log("The method YAHOO.widget.Column.formatSelect() has been" +
57656        " deprecated in favor of YAHOO.widget.DataTable.formatDropdown()", "warn",
57657        "YAHOO.widget.Column.formatSelect");
57658        YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData);
57659    }
57660});
57661
57662YAHOO.widget.Column.prototype = {
57663    /**
57664     * Unique String identifier assigned at instantiation.
57665     *
57666     * @property _sId
57667     * @type String
57668     * @private
57669     */
57670    _sId : null,
57671
57672    /**
57673     * Reference to Column's current position index within its ColumnSet's keys
57674     * array, if applicable. This property only applies to non-nested and bottom-
57675     * level child Columns.
57676     *
57677     * @property _nKeyIndex
57678     * @type Number
57679     * @private
57680     */
57681    _nKeyIndex : null,
57682
57683    /**
57684     * Reference to Column's current position index within its ColumnSet's tree
57685     * array, if applicable. This property only applies to non-nested and top-
57686     * level parent Columns.
57687     *
57688     * @property _nTreeIndex
57689     * @type Number
57690     * @private
57691     */
57692    _nTreeIndex : null,
57693
57694    /**
57695     * Number of table cells the Column spans.
57696     *
57697     * @property _nColspan
57698     * @type Number
57699     * @private
57700     */
57701    _nColspan : 1,
57702
57703    /**
57704     * Number of table rows the Column spans.
57705     *
57706     * @property _nRowspan
57707     * @type Number
57708     * @private
57709     */
57710    _nRowspan : 1,
57711
57712    /**
57713     * Column's parent Column instance, or null.
57714     *
57715     * @property _oParent
57716     * @type YAHOO.widget.Column
57717     * @private
57718     */
57719    _oParent : null,
57720
57721    /**
57722     * The DOM reference to the associated TH element.
57723     *
57724     * @property _elTh
57725     * @type HTMLElement
57726     * @private
57727     */
57728    _elTh : null,
57729
57730    /**
57731     * The DOM reference to the associated TH element's liner DIV element.
57732     *
57733     * @property _elThLiner
57734     * @type HTMLElement
57735     * @private
57736     */
57737    _elThLiner : null,
57738
57739    /**
57740     * The DOM reference to the associated TH element's label SPAN element.
57741     *
57742     * @property _elThLabel
57743     * @type HTMLElement
57744     * @private
57745     */
57746    _elThLabel : null,
57747
57748    /**
57749     * The DOM reference to the associated resizerelement (if any).
57750     *
57751     * @property _elResizer
57752     * @type HTMLElement
57753     * @private
57754     */
57755    _elResizer : null,
57756
57757    /**
57758     * Internal width tracker.
57759     *
57760     * @property _nWidth
57761     * @type Number
57762     * @private
57763     */
57764    _nWidth : null,
57765
57766    /**
57767     * For unreg() purposes, a reference to the Column's DragDrop instance.
57768     *
57769     * @property _dd
57770     * @type YAHOO.util.DragDrop
57771     * @private
57772     */
57773    _dd : null,
57774
57775    /**
57776     * For unreg() purposes, a reference to the Column resizer's DragDrop instance.
57777     *
57778     * @property _ddResizer
57779     * @type YAHOO.util.DragDrop
57780     * @private
57781     */
57782    _ddResizer : null,
57783
57784    /////////////////////////////////////////////////////////////////////////////
57785    //
57786    // Public member variables
57787    //
57788    /////////////////////////////////////////////////////////////////////////////
57789
57790    /**
57791     * Unique name, required. If "label" property is not provided, the "key"
57792     * value will be treated as markup and inserted into the DOM as innerHTML.
57793     *
57794     * @property key
57795     * @type String|HTML
57796     */
57797    key : null,
57798
57799    /**
57800     * Associated database field, or null.
57801     *
57802     * @property field
57803     * @type String
57804     */
57805    field : null,
57806
57807    /**
57808     * Value displayed as Column header in the TH element. String value is
57809     * treated as markup and inserted into the DOM as innerHTML.
57810     *
57811     * @property label
57812     * @type HTML
57813     */
57814    label : null,
57815
57816    /**
57817     * Column head cell ABBR for accessibility.
57818     *
57819     * @property abbr
57820     * @type String
57821     */
57822    abbr : null,
57823
57824    /**
57825     * Array of object literals that define children (nested headers) of a Column.
57826     *
57827     * @property children
57828     * @type Object[]
57829     */
57830    children : null,
57831
57832    /**
57833     * Column width (in pixels).
57834     *
57835     * @property width
57836     * @type Number
57837     */
57838    width : null,
57839
57840    /**
57841     * Minimum Column width (in pixels).
57842     *
57843     * @property minWidth
57844     * @type Number
57845     * @default null
57846     */
57847    minWidth : null,
57848
57849    /**
57850     * When a width is not defined for a Column, maxAutoWidth defines an upper
57851     * limit that the Column should be auto-sized to. If resizeable is enabled, 
57852     * users may still resize to a greater width. Most useful for Columns intended
57853     * to hold long unbroken, unwrapped Strings, such as URLs, to prevent very
57854     * wide Columns from disrupting visual readability by inducing truncation.
57855     *
57856     * @property maxAutoWidth
57857     * @type Number
57858     * @default null
57859     */
57860    maxAutoWidth : null,
57861
57862    /**
57863     * True if Column is in hidden state.
57864     *
57865     * @property hidden
57866     * @type Boolean
57867     * @default false     
57868     */
57869    hidden : false,
57870
57871    /**
57872     * True if Column is in selected state.
57873     *
57874     * @property selected
57875     * @type Boolean
57876     * @default false     
57877     */
57878    selected : false,
57879
57880    /**
57881     * Custom CSS class or array of classes to be applied to every cell in the Column.
57882     *
57883     * @property className
57884     * @type String || String[]
57885     */
57886    className : null,
57887
57888    /**
57889     * Cell formatter function, or a shortcut pointer to a function in the
57890     * DataTable.Formatter object. The function, called from the DataTable's
57891     * formatCell method, renders markup into the cell liner
57892     * element and accepts the following arguments:
57893     * <dl>
57894     *    <dt>elLiner</dt>
57895     *    <dd>The element to write innerHTML to.</dd>
57896     *    <dt>oRecord</dt>
57897     *    <dd>The associated Record for the row.</dd>
57898     *    <dt>oColumn</dt>
57899     *    <dd>The Column instance for the cell.</dd>
57900     *    <dt>oData</dt>
57901     *    <dd>The data value for the cell.</dd>
57902     * </dl>
57903     *
57904     * @property formatter
57905     * @type String || HTMLFunction
57906     */
57907    formatter : null,
57908    
57909    /**
57910     * Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
57911     *
57912     * @property currencyOptions
57913     * @type Object
57914     * @default null
57915     */
57916    currencyOptions : null,
57917
57918    /**
57919     * Config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
57920     *
57921     * @property dateOptions
57922     * @type Object
57923     * @default null
57924     */
57925    dateOptions : null,
57926
57927    /**
57928     * Array of dropdown values for formatter:"dropdown" cases. Can either be a
57929     * simple array (e.g., ["Alabama","Alaska","Arizona","Arkansas"]) or a an
57930     * array of objects (e.g., [{label:"Alabama", value:"AL"},
57931     * {label:"Alaska", value:"AK"}, {label:"Arizona", value:"AZ"},
57932     * {label:"Arkansas", value:"AR"}]). String values are treated as markup and
57933     * inserted into the DOM as innerHTML.
57934     *
57935     * @property dropdownOptions
57936     * @type HTML[] | Object[]
57937     */
57938    dropdownOptions : null,
57939     
57940    /**
57941     * A CellEditor instance, otherwise Column is not editable.     
57942     *
57943     * @property editor
57944     * @type YAHOO.widget.CellEditor
57945     */
57946    editor : null,
57947
57948    /**
57949     * True if Column is resizeable, false otherwise. The Drag & Drop Utility is
57950     * required to enable this feature. Only bottom-level and non-nested Columns are
57951     * resizeble. 
57952     *
57953     * @property resizeable
57954     * @type Boolean
57955     * @default false
57956     */
57957    resizeable : false,
57958
57959    /**
57960     * True if Column is sortable, false otherwise.
57961     *
57962     * @property sortable
57963     * @type Boolean
57964     * @default false
57965     */
57966    sortable : false,
57967
57968    /**
57969     * @property sortOptions.defaultOrder
57970     * @deprecated Use sortOptions.defaultDir.
57971     */
57972    /**
57973     * Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC.
57974     *
57975     * @property sortOptions.defaultDir
57976     * @type String
57977     * @default null
57978     */
57979    /**
57980     * Custom field to sort on.
57981     *
57982     * @property sortOptions.field
57983     * @type String
57984     * @default null
57985     */
57986    /**
57987     * Custom sort handler. Signature: sortFunction(a, b, desc, field) where field is the sortOptions.field value
57988     *
57989     * @property sortOptions.sortFunction
57990     * @type Function
57991     * @default null
57992     */
57993    sortOptions : null,
57994
57995
57996
57997
57998
57999
58000
58001
58002
58003
58004
58005
58006
58007
58008
58009    /////////////////////////////////////////////////////////////////////////////
58010    //
58011    // Public methods
58012    //
58013    /////////////////////////////////////////////////////////////////////////////
58014
58015    /**
58016     * Returns unique ID string.
58017     *
58018     * @method getId
58019     * @return {String} Unique ID string.
58020     */
58021    getId : function() {
58022        return this._sId;
58023    },
58024
58025    /**
58026     * Column instance name, for logging.
58027     *
58028     * @method toString
58029     * @return {String} Column's unique name.
58030     */
58031    toString : function() {
58032        return "Column instance " + this._sId;
58033    },
58034
58035    /**
58036     * Returns object literal definition.
58037     *
58038     * @method getDefinition
58039     * @return {Object} Object literal definition.
58040     */
58041    getDefinition : function() {
58042        var oDefinition = {};
58043        
58044        // Update the definition
58045        oDefinition.abbr = this.abbr;
58046        oDefinition.className = this.className;
58047        oDefinition.editor = this.editor;
58048        oDefinition.editorOptions = this.editorOptions; //TODO: deprecated
58049        oDefinition.field = this.field;
58050        oDefinition.formatter = this.formatter;
58051        oDefinition.hidden = this.hidden;
58052        oDefinition.key = this.key;
58053        oDefinition.label = this.label;
58054        oDefinition.minWidth = this.minWidth;
58055        oDefinition.maxAutoWidth = this.maxAutoWidth;
58056        oDefinition.resizeable = this.resizeable;
58057        oDefinition.selected = this.selected;
58058        oDefinition.sortable = this.sortable;
58059        oDefinition.sortOptions = this.sortOptions;
58060        oDefinition.width = this.width;
58061        
58062        // Bug 2529147
58063        oDefinition._calculatedWidth = this._calculatedWidth;
58064
58065        return oDefinition;
58066    },
58067
58068    /**
58069     * Returns unique Column key.
58070     *
58071     * @method getKey
58072     * @return {String} Column key.
58073     */
58074    getKey : function() {
58075        return this.key;
58076    },
58077    
58078    /**
58079     * Returns field.
58080     *
58081     * @method getField
58082     * @return {String} Column field.
58083     */
58084    getField : function() {
58085        return this.field;
58086    },
58087    
58088    /**
58089     * Returns Column key which has been sanitized for DOM (class and ID) usage
58090     * starts with letter, contains only letters, numbers, hyphen, or period.
58091     *
58092     * @method getSanitizedKey
58093     * @return {String} Sanitized Column key.
58094     */
58095    getSanitizedKey : function() {
58096        return this.getKey().replace(/[^\w\-]/g,"");
58097    },
58098
58099    /**
58100     * Public accessor returns Column's current position index within its
58101     * ColumnSet's keys array, if applicable. Only non-nested and bottom-level
58102     * child Columns will return a value.
58103     *
58104     * @method getKeyIndex
58105     * @return {Number} Position index, or null.
58106     */
58107    getKeyIndex : function() {
58108        return this._nKeyIndex;
58109    },
58110
58111    /**
58112     * Public accessor returns Column's current position index within its
58113     * ColumnSet's tree array, if applicable. Only non-nested and top-level parent
58114     * Columns will return a value;
58115     *
58116     * @method getTreeIndex
58117     * @return {Number} Position index, or null.
58118     */
58119    getTreeIndex : function() {
58120        return this._nTreeIndex;
58121    },
58122
58123    /**
58124     * Public accessor returns Column's parent instance if any, or null otherwise.
58125     *
58126     * @method getParent
58127     * @return {YAHOO.widget.Column} Column's parent instance.
58128     */
58129    getParent : function() {
58130        return this._oParent;
58131    },
58132
58133    /**
58134     * Public accessor returns Column's calculated COLSPAN value.
58135     *
58136     * @method getColspan
58137     * @return {Number} Column's COLSPAN value.
58138     */
58139    getColspan : function() {
58140        return this._nColspan;
58141    },
58142    // Backward compatibility
58143    getColSpan : function() {
58144        YAHOO.log("The method getColSpan() has been" +
58145        " deprecated in favor of getColspan()", "warn", this.toString());
58146        return this.getColspan();
58147    },
58148
58149    /**
58150     * Public accessor returns Column's calculated ROWSPAN value.
58151     *
58152     * @method getRowspan
58153     * @return {Number} Column's ROWSPAN value.
58154     */
58155    getRowspan : function() {
58156        return this._nRowspan;
58157    },
58158
58159    /**
58160     * Returns DOM reference to the key TH element.
58161     *
58162     * @method getThEl
58163     * @return {HTMLElement} TH element.
58164     */
58165    getThEl : function() {
58166        return this._elTh;
58167    },
58168
58169    /**
58170     * Returns DOM reference to the TH's liner DIV element. Introduced since
58171     * resizeable Columns may have an extra resizer liner, making the DIV liner
58172     * not reliably the TH element's first child.               
58173     *
58174     * @method getThLInerEl
58175     * @return {HTMLElement} TH element.
58176     */
58177    getThLinerEl : function() {
58178        return this._elThLiner;
58179    },
58180    
58181    /**
58182     * Returns DOM reference to the resizer element, or null.
58183     *
58184     * @method getResizerEl
58185     * @return {HTMLElement} DIV element.
58186     */
58187    getResizerEl : function() {
58188        return this._elResizer;
58189    },
58190
58191    // Backward compatibility
58192    /**
58193     * @method getColEl
58194     * @deprecated Use getThEl
58195     */
58196    getColEl : function() {
58197        YAHOO.log("The method getColEl() has been" +
58198        " deprecated in favor of getThEl()", "warn",
58199        this.toString());
58200        return this.getThEl();
58201    },
58202    getIndex : function() {
58203        YAHOO.log("The method getIndex() has been" +
58204        " deprecated in favor of getKeyIndex()", "warn",
58205        this.toString());
58206        return this.getKeyIndex();
58207    },
58208    format : function() {
58209        YAHOO.log("The method format() has been deprecated in favor of the " +
58210        "DataTable method formatCell()", "error", this.toString());
58211    }
58212};
58213
58214/****************************************************************************/
58215/****************************************************************************/
58216/****************************************************************************/
58217
58218/**
58219 * Sort static utility to support Column sorting.
58220 *
58221 * @namespace YAHOO.util
58222 * @class Sort
58223 * @static
58224 */
58225YAHOO.util.Sort = {
58226    /////////////////////////////////////////////////////////////////////////////
58227    //
58228    // Public methods
58229    //
58230    /////////////////////////////////////////////////////////////////////////////
58231
58232    /**
58233     * Comparator function for simple case-insensitive string sorting.
58234     *
58235     * @method compare
58236     * @param a {Object} First sort argument.
58237     * @param b {Object} Second sort argument.
58238     * @param desc {Boolean} True if sort direction is descending, false if
58239     * sort direction is ascending.
58240     * @return {Boolean} Return -1 when a < b. Return 0 when a = b.
58241     * Return 1 when a > b.
58242     */
58243    compare: function(a, b, desc) {
58244        if((a === null) || (typeof a == "undefined")) {
58245            if((b === null) || (typeof b == "undefined")) {
58246                return 0;
58247            }
58248            else {
58249                return 1;
58250            }
58251        }
58252        else if((b === null) || (typeof b == "undefined")) {
58253            return -1;
58254        }
58255
58256        if(a.constructor == String) {
58257            a = a.toLowerCase();
58258        }
58259        if(b.constructor == String) {
58260            b = b.toLowerCase();
58261        }
58262        if(a < b) {
58263            return (desc) ? 1 : -1;
58264        }
58265        else if (a > b) {
58266            return (desc) ? -1 : 1;
58267        }
58268        else {
58269            return 0;
58270        }
58271    }
58272};
58273
58274/****************************************************************************/
58275/****************************************************************************/
58276/****************************************************************************/
58277
58278/**
58279 * ColumnDD subclasses DragDrop to support rearrangeable Columns.
58280 *
58281 * @namespace YAHOO.util
58282 * @class ColumnDD
58283 * @extends YAHOO.util.DDProxy
58284 * @constructor
58285 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
58286 * @param oColumn {YAHOO.widget.Column} Column instance.
58287 * @param elTh {HTMLElement} TH element reference.
58288 * @param elTarget {HTMLElement} Drag target element.
58289 */
58290YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) {
58291    if(oDataTable && oColumn && elTh && elTarget) {
58292        this.datatable = oDataTable;
58293        this.table = oDataTable.getTableEl();
58294        this.column = oColumn;
58295        this.headCell = elTh;
58296        this.pointer = elTarget;
58297        this.newIndex = null;
58298        this.init(elTh);
58299        this.initFrame(); // Needed for DDProxy
58300        this.invalidHandleTypes = {};
58301
58302        // Set top/bottom padding to account for children of nested columns
58303        this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0);
58304
58305        YAHOO.util.Event.on(window, 'resize', function() {
58306            this.initConstraints();
58307        }, this, true);
58308    }
58309    else {
58310        YAHOO.log("Column dragdrop could not be created","warn",oDataTable.toString());
58311    }
58312};
58313
58314if(YAHOO.util.DDProxy) {
58315    YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, {
58316        initConstraints: function() {
58317            //Get the top, right, bottom and left positions
58318            var region = YAHOO.util.Dom.getRegion(this.table),
58319                //Get the element we are working on
58320                el = this.getEl(),
58321                //Get the xy position of it
58322                xy = YAHOO.util.Dom.getXY(el),
58323                //Get the width and height
58324                width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10),
58325                height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10),
58326                //Set left to x minus left
58327                left = ((xy[0] - region.left) + 15), //Buffer of 15px
58328                //Set right to right minus x minus width
58329                right = ((region.right - xy[0] - width) + 15);
58330    
58331            //Set the constraints based on the above calculations
58332            this.setXConstraint(left, right);
58333            this.setYConstraint(10, 10);            
58334        },
58335        _resizeProxy: function() {
58336            YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this, arguments);
58337            var dragEl = this.getDragEl(),
58338                el = this.getEl();
58339
58340            YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px');
58341            YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block');
58342            var xy = YAHOO.util.Dom.getXY(el);
58343            YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
58344            
58345            YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px");
58346            YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
58347            YAHOO.util.Dom.setXY(this.dragEl, xy);
58348        },
58349        onMouseDown: function() {
58350                this.initConstraints();
58351                this.resetConstraints();
58352        },
58353        clickValidator: function(e) {
58354            if(!this.column.hidden) {
58355                var target = YAHOO.util.Event.getTarget(e);
58356                return ( this.isValidHandleChild(target) &&
58357                            (this.id == this.handleElId ||
58358                                this.DDM.handleWasClicked(target, this.id)) );
58359            }
58360        },
58361        onDragOver: function(ev, id) {
58362            // Validate target as a Column
58363            var target = this.datatable.getColumn(id);
58364            if(target) {                
58365                // Validate target as a top-level parent
58366                var targetIndex = target.getTreeIndex();
58367                while((targetIndex === null) && target.getParent()) {
58368                    target = target.getParent();
58369                    targetIndex = target.getTreeIndex();
58370                }
58371                if(targetIndex !== null) {
58372                    // Are we placing to left or right of target?
58373                    var elTarget = target.getThEl();
58374                    var newIndex = targetIndex;
58375                    var mouseX = YAHOO.util.Event.getPageX(ev),
58376                        targetX = YAHOO.util.Dom.getX(elTarget),
58377                        midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2),
58378                        currentIndex =  this.column.getTreeIndex();
58379                    
58380                    if (mouseX < midX) {
58381                       YAHOO.util.Dom.setX(this.pointer, targetX);
58382                    } else {
58383                        var targetWidth = parseInt(elTarget.offsetWidth, 10);
58384                        YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth));
58385                        newIndex++;
58386                    }
58387                    if (targetIndex > currentIndex) {
58388                        newIndex--;
58389                    }
58390                    if(newIndex < 0) {
58391                        newIndex = 0;
58392                    }
58393                    else if(newIndex > this.datatable.getColumnSet().tree[0].length) {
58394                        newIndex = this.datatable.getColumnSet().tree[0].length;
58395                    }
58396                    this.newIndex = newIndex;
58397                }
58398            }
58399        },
58400        onDragDrop: function() {
58401            this.datatable.reorderColumn(this.column, this.newIndex);
58402        },
58403        endDrag: function() {
58404            this.newIndex = null;
58405            YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none');
58406        }
58407    });
58408}
58409
58410/****************************************************************************/
58411/****************************************************************************/
58412/****************************************************************************/
58413
58414/**
58415 * ColumnResizer subclasses DragDrop to support resizeable Columns.
58416 *
58417 * @namespace YAHOO.util
58418 * @class ColumnResizer
58419 * @extends YAHOO.util.DDProxy
58420 * @constructor
58421 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
58422 * @param oColumn {YAHOO.widget.Column} Column instance.
58423 * @param elTh {HTMLElement} TH element reference.
58424 * @param sHandleElId {String} DOM ID of the handle element that causes the resize.
58425 * @param elProxy {HTMLElement} Resizer proxy element.
58426 */
58427YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) {
58428    if(oDataTable && oColumn && elTh && sHandleId) {
58429        this.datatable = oDataTable;
58430        this.column = oColumn;
58431        this.headCell = elTh;
58432        this.headCellLiner = oColumn.getThLinerEl();
58433        this.resizerLiner = elTh.firstChild;
58434        this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id});
58435        this.initFrame(); // Needed for proxy
58436        this.resetResizerEl(); // Needed when rowspan > 0
58437
58438        // Set right padding for bug 1858462
58439        this.setPadding(0, 1, 0, 0);
58440    }
58441    else {
58442        YAHOO.log("Column resizer could not be created","warn",oDataTable.toString());
58443    }
58444};
58445
58446if(YAHOO.util.DD) {
58447    YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, {
58448        /////////////////////////////////////////////////////////////////////////////
58449        //
58450        // Public methods
58451        //
58452        /////////////////////////////////////////////////////////////////////////////
58453        /**
58454         * Resets resizer element.
58455         *
58456         * @method resetResizerEl
58457         */
58458        resetResizerEl : function() {
58459            var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style;
58460            resizerStyle.left = "auto";
58461            resizerStyle.right = 0;
58462            resizerStyle.top = "auto";
58463            resizerStyle.bottom = 0;
58464            resizerStyle.height = this.headCell.offsetHeight+"px";
58465        },
58466    
58467        /////////////////////////////////////////////////////////////////////////////
58468        //
58469        // Public DOM event handlers
58470        //
58471        /////////////////////////////////////////////////////////////////////////////
58472    
58473        /**
58474         * Handles mouseup events on the Column resizer.
58475         *
58476         * @method onMouseUp
58477         * @param e {string} The mouseup event
58478         */
58479        onMouseUp : function(e) {
58480            // Reset height of all resizer els in case TH's have changed height
58481            var allKeys = this.datatable.getColumnSet().keys,
58482                col;
58483            for(var i=0, len=allKeys.length; i<len; i++) {
58484                col = allKeys[i];
58485                if(col._ddResizer) {
58486                    col._ddResizer.resetResizerEl();
58487                }
58488            }
58489            this.resetResizerEl();
58490            
58491            var el = this.headCellLiner;
58492            var newWidth = el.offsetWidth -
58493                (parseInt(YAHOO.util.Dom.getStyle(el,"paddingLeft"),10)|0) -
58494                (parseInt(YAHOO.util.Dom.getStyle(el,"paddingRight"),10)|0);
58495
58496            this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.headCell,width:newWidth});
58497        },
58498    
58499        /**
58500         * Handles mousedown events on the Column resizer.
58501         *
58502         * @method onMouseDown
58503         * @param e {string} The mousedown event
58504         */
58505        onMouseDown : function(e) {
58506            this.startWidth = this.headCellLiner.offsetWidth;
58507            this.startX = YAHOO.util.Event.getXY(e)[0];
58508            this.nLinerPadding = (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0) +
58509                    (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
58510        },
58511    
58512        /**
58513         * Custom clickValidator to ensure Column is not in hidden state.
58514         *
58515         * @method clickValidator
58516         * @param {Event} e
58517         * @private
58518         */
58519        clickValidator : function(e) {
58520            if(!this.column.hidden) {
58521                var target = YAHOO.util.Event.getTarget(e);
58522                return ( this.isValidHandleChild(target) &&
58523                            (this.id == this.handleElId ||
58524                                this.DDM.handleWasClicked(target, this.id)) );
58525            }
58526        },
58527    
58528        /**
58529         * Handles start drag on the Column resizer.
58530         *
58531         * @method startDrag
58532         * @param e {string} The drag event
58533         */
58534        startDrag : function() {
58535            // Shrinks height of all resizer els to not hold open TH els
58536            var allKeys = this.datatable.getColumnSet().keys,
58537                thisKey = this.column.getKeyIndex(),
58538                col;
58539            for(var i=0, len=allKeys.length; i<len; i++) {
58540                col = allKeys[i];
58541                if(col._ddResizer) {
58542                    YAHOO.util.Dom.get(col._ddResizer.handleElId).style.height = "1em";
58543                }
58544            }
58545        },
58546
58547        /**
58548         * Handles drag events on the Column resizer.
58549         *
58550         * @method onDrag
58551         * @param e {string} The drag event
58552         */
58553        onDrag : function(e) {
58554            var newX = YAHOO.util.Event.getXY(e)[0];
58555            if(newX > YAHOO.util.Dom.getX(this.headCellLiner)) {
58556                var offsetX = newX - this.startX;
58557                var newWidth = this.startWidth + offsetX - this.nLinerPadding;
58558                if(newWidth > 0) {
58559                    this.datatable.setColumnWidth(this.column, newWidth);
58560                }
58561            }
58562        }
58563    });
58564}
58565
58566/////////////////////////////////////////////////////////////////////////////
58567//
58568// Deprecated
58569//
58570/////////////////////////////////////////////////////////////////////////////
58571
58572/**
58573 * @property editorOptions
58574 * @deprecated Pass configs directly to CellEditor constructor. 
58575 */
58576
58577
58578(function () {
58579
58580var lang   = YAHOO.lang,
58581    util   = YAHOO.util,
58582    widget = YAHOO.widget,
58583    
58584    Dom    = util.Dom,
58585    Ev     = util.Event,
58586    DT     = widget.DataTable;
58587
58588/****************************************************************************/
58589/****************************************************************************/
58590/****************************************************************************/
58591
58592/**
58593 * A RecordSet defines and manages a set of Records.
58594 *
58595 * @namespace YAHOO.widget
58596 * @class RecordSet
58597 * @param data {Object || Object[]} An object literal or an array of data.
58598 * @constructor
58599 */
58600YAHOO.widget.RecordSet = function(data) {
58601    this._init(data);
58602};
58603
58604var RS = widget.RecordSet;
58605
58606/**
58607 * Internal class variable to name multiple Recordset instances.
58608 *
58609 * @property RecordSet._nCount
58610 * @type Number
58611 * @private
58612 * @static
58613 */
58614RS._nCount = 0;
58615
58616RS.prototype = {
58617
58618    /////////////////////////////////////////////////////////////////////////////
58619    //
58620    // Private member variables
58621    //
58622    /////////////////////////////////////////////////////////////////////////////
58623    /**
58624     * Unique String identifier assigned at instantiation.
58625     *
58626     * @property _sId
58627     * @type String
58628     * @private
58629     */
58630    _sId : null,
58631
58632    /**
58633     * Internal counter of how many Records are in the RecordSet.
58634     *
58635     * @property _length
58636     * @type Number
58637     * @private
58638     * @deprecated No longer used
58639     */
58640    //_length : null,
58641
58642    /////////////////////////////////////////////////////////////////////////////
58643    //
58644    // Private methods
58645    //
58646    /////////////////////////////////////////////////////////////////////////////
58647    
58648    /**
58649     * Initializer.
58650     *
58651     * @method _init
58652     * @param data {Object || Object[]} An object literal or an array of data.
58653     * @private
58654     */
58655    _init : function(data) {
58656        // Internal variables
58657        this._sId = Dom.generateId(null, "yui-rs");// "yui-rs" + widget.RecordSet._nCount;
58658        widget.RecordSet._nCount++;
58659        this._records = [];
58660        //this._length = 0;
58661
58662        this._initEvents();
58663
58664        if(data) {
58665            if(lang.isArray(data)) {
58666                this.addRecords(data);
58667            }
58668            else if(lang.isObject(data)) {
58669                this.addRecord(data);
58670            }
58671        }
58672
58673        YAHOO.log("RecordSet initialized", "info", this.toString());
58674    },
58675    
58676    /**
58677     * Initializes custom events.
58678     *
58679     * @method _initEvents
58680     * @private
58681     */
58682    _initEvents : function() {
58683        this.createEvent("recordAddEvent");
58684        this.createEvent("recordsAddEvent");
58685        this.createEvent("recordSetEvent");
58686        this.createEvent("recordsSetEvent");
58687        this.createEvent("recordUpdateEvent");
58688        this.createEvent("recordDeleteEvent");
58689        this.createEvent("recordsDeleteEvent");
58690        this.createEvent("resetEvent");
58691        this.createEvent("recordValueUpdateEvent");
58692    },
58693
58694    /**
58695     * Adds one Record to the RecordSet at the given index. If index is null,
58696     * then adds the Record to the end of the RecordSet.
58697     *
58698     * @method _addRecord
58699     * @param oData {Object} An object literal of data.
58700     * @param index {Number} (optional) Position index.
58701     * @return {YAHOO.widget.Record} A Record instance.
58702     * @private
58703     */
58704    _addRecord : function(oData, index) {
58705        var oRecord = new YAHOO.widget.Record(oData);
58706        
58707        if(YAHOO.lang.isNumber(index) && (index > -1)) {
58708            this._records.splice(index,0,oRecord);
58709        }
58710        else {
58711            //index = this.getLength();
58712            //this._records[index] = oRecord;
58713            this._records[this._records.length] = oRecord;
58714        }
58715        //this._length++;
58716        return oRecord;
58717    },
58718
58719    /**
58720     * Sets/replaces one Record to the RecordSet at the given index.  Existing
58721     * Records with higher indexes are not shifted.  If no index specified, the
58722     * Record is added to the end of the RecordSet.
58723     *
58724     * @method _setRecord
58725     * @param oData {Object} An object literal of data.
58726     * @param index {Number} (optional) Position index.
58727     * @return {YAHOO.widget.Record} A Record instance.
58728     * @private
58729     */
58730    _setRecord : function(oData, index) {
58731        if (!lang.isNumber(index) || index < 0) {
58732            index = this._records.length;
58733        }
58734        return (this._records[index] = new widget.Record(oData));
58735        /*
58736        if(lang.isNumber(index) && (index > -1)) {
58737            this._records[index] = oRecord;
58738            if((index+1) > this.getLength()) {
58739                this._length = index+1;
58740            }
58741        }
58742        else {
58743            this._records[this.getLength()] = oRecord;
58744            this._length++;
58745        }
58746        return oRecord;
58747        */
58748    },
58749
58750    /**
58751     * Deletes Records from the RecordSet at the given index. If range is null,
58752     * then only one Record is deleted.
58753     *
58754     * @method _deleteRecord
58755     * @param index {Number} Position index.
58756     * @param range {Number} (optional) How many Records to delete
58757     * @private
58758     */
58759    _deleteRecord : function(index, range) {
58760        if(!lang.isNumber(range) || (range < 0)) {
58761            range = 1;
58762        }
58763        this._records.splice(index, range);
58764        //this._length = this._length - range;
58765    },
58766
58767    /////////////////////////////////////////////////////////////////////////////
58768    //
58769    // Public methods
58770    //
58771    /////////////////////////////////////////////////////////////////////////////
58772
58773    /**
58774     * Returns unique name of the RecordSet instance.
58775     *
58776     * @method getId
58777     * @return {String} Unique name of the RecordSet instance.
58778     */
58779    getId : function() {
58780        return this._sId;
58781    },
58782
58783    /**
58784     * Public accessor to the unique name of the RecordSet instance.
58785     *
58786     * @method toString
58787     * @return {String} Unique name of the RecordSet instance.
58788     */
58789    toString : function() {
58790        return "RecordSet instance " + this._sId;
58791    },
58792
58793    /**
58794     * Returns the number of Records held in the RecordSet.
58795     *
58796     * @method getLength
58797     * @return {Number} Number of records in the RecordSet.
58798     */
58799    getLength : function() {
58800            //return this._length;
58801            return this._records.length;
58802    },
58803
58804    /**
58805     * Returns Record by ID or RecordSet position index.
58806     *
58807     * @method getRecord
58808     * @param record {YAHOO.widget.Record | Number | String} Record instance,
58809     * RecordSet position index, or Record ID.
58810     * @return {YAHOO.widget.Record} Record object.
58811     */
58812    getRecord : function(record) {
58813        var i;
58814        if(record instanceof widget.Record) {
58815            for(i=0; i<this._records.length; i++) {
58816                if(this._records[i] && (this._records[i]._sId === record._sId)) {
58817                    return record;
58818                }
58819            }
58820        }
58821        else if(lang.isNumber(record)) {
58822            if((record > -1) && (record < this.getLength())) {
58823                return this._records[record];
58824            }
58825        }
58826        else if(lang.isString(record)) {
58827            for(i=0; i<this._records.length; i++) {
58828                if(this._records[i] && (this._records[i]._sId === record)) {
58829                    return this._records[i];
58830                }
58831            }
58832        }
58833        // Not a valid Record for this RecordSet
58834        return null;
58835
58836    },
58837
58838    /**
58839     * Returns an array of Records from the RecordSet.
58840     *
58841     * @method getRecords
58842     * @param index {Number} (optional) Recordset position index of which Record to
58843     * start at.
58844     * @param range {Number} (optional) Number of Records to get.
58845     * @return {YAHOO.widget.Record[]} Array of Records starting at given index and
58846     * length equal to given range. If index is not given, all Records are returned.
58847     */
58848    getRecords : function(index, range) {
58849        if(!lang.isNumber(index)) {
58850            return this._records;
58851        }
58852        if(!lang.isNumber(range)) {
58853            return this._records.slice(index);
58854        }
58855        return this._records.slice(index, index+range);
58856    },
58857
58858    /**
58859     * Returns a boolean indicating whether Records exist in the RecordSet at the
58860     * specified index range.  Returns true if and only if a Record exists at each
58861     * index in the range.
58862     * @method hasRecords
58863     * @param index
58864     * @param range
58865     * @return {Boolean} true if all indices are populated in the RecordSet
58866     */
58867    hasRecords : function (index, range) {
58868        var recs = this.getRecords(index,range);
58869        for (var i = 0; i < range;
58869 ++i) {
58870            if (typeof recs[i] === 'undefined') {
58871                return false;
58872            }
58873        }
58874        return true;
58875    },
58876
58877    /**
58878     * Returns current position index for the given Record.
58879     *
58880     * @method getRecordIndex
58881     * @param oRecord {YAHOO.widget.Record} Record instance.
58882     * @return {Number} Record's RecordSet position index.
58883     */
58884
58885    getRecordIndex : function(oRecord) {
58886        if(oRecord) {
58887            for(var i=this._records.length-1; i>-1; i--) {
58888                if(this._records[i] && oRecord.getId() === this._records[i].getId()) {
58889                    return i;
58890                }
58891            }
58892        }
58893        return null;
58894
58895    },
58896
58897    /**
58898     * Adds one Record to the RecordSet at the given index. If index is null,
58899     * then adds the Record to the end of the RecordSet.
58900     *
58901     * @method addRecord
58902     * @param oData {Object} An object literal of data.
58903     * @param index {Number} (optional) Position index.
58904     * @return {YAHOO.widget.Record} A Record instance.
58905     */
58906    addRecord : function(oData, index) {
58907        if(lang.isObject(oData)) {
58908            var oRecord = this._addRecord(oData, index);
58909            this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
58910            YAHOO.log("Added Record at index " + index +
58911                    " with data " + lang.dump(oData), "info", this.toString());
58912            return oRecord;
58913        }
58914        else {
58915            YAHOO.log("Could not add Record with data" +
58916                    lang.dump(oData), "info", this.toString());
58917            return null;
58918        }
58919    },
58920
58921    /**
58922     * Adds multiple Records at once to the RecordSet at the given index with the
58923     * given object literal data. If index is null, then the new Records are
58924     * added to the end of the RecordSet.
58925     *
58926     * @method addRecords
58927     * @param aData {Object[]} An object literal data or an array of data object literals.
58928     * @param index {Number} (optional) Position index.
58929     * @return {YAHOO.widget.Record[]} An array of Record instances.
58930     */
58931    addRecords : function(aData, index) {
58932        if(lang.isArray(aData)) {
58933            var newRecords = [],
58934                idx,i,len;
58935
58936            index = lang.isNumber(index) ? index : this._records.length;
58937            idx = index;
58938
58939            // Can't go backwards bc we need to preserve order
58940            for(i=0,len=aData.length; i<len; ++i) {
58941                if(lang.isObject(aData[i])) {
58942                    var record = this._addRecord(aData[i], idx++);
58943                    newRecords.push(record);
58944                }
58945           }
58946            this.fireEvent("recordsAddEvent",{records:newRecords,data:aData});
58947            YAHOO.log("Added " + newRecords.length + " Record(s) at index " + index +
58948                    " with data " + lang.dump(aData), "info", this.toString());
58949           return newRecords;
58950        }
58951        else if(lang.isObject(aData)) {
58952            var oRecord = this._addRecord(aData);
58953            this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData});
58954            YAHOO.log("Added 1 Record at index " + index +
58955                    " with data " + lang.dump(aData), "info", this.toString());
58956            return oRecord;
58957        }
58958        else {
58959            YAHOO.log("Could not add Records with data " +
58960                    lang.dump(aData), "info", this.toString());
58961            return null;
58962        }
58963    },
58964
58965    /**
58966     * Sets or replaces one Record to the RecordSet at the given index. Unlike
58967     * addRecord, an existing Record at that index is not shifted to preserve it.
58968     * If no index is specified, it adds the Record to the end of the RecordSet.
58969     *
58970     * @method setRecord
58971     * @param oData {Object} An object literal of data.
58972     * @param index {Number} (optional) Position index.
58973     * @return {YAHOO.widget.Record} A Record instance.
58974     */
58975    setRecord : function(oData, index) {
58976        if(lang.isObject(oData)) {
58977            var oRecord = this._setRecord(oData, index);
58978            this.fireEvent("recordSetEvent",{record:oRecord,data:oData});
58979            YAHOO.log("Set Record at index " + index +
58980                    " with data " + lang.dump(oData), "info", this.toString());
58981            return oRecord;
58982        }
58983        else {
58984            YAHOO.log("Could not set Record with data" +
58985                    lang.dump(oData), "info", this.toString());
58986            return null;
58987        }
58988    },
58989
58990    /**
58991     * Sets or replaces multiple Records at once to the RecordSet with the given
58992     * data, starting at the given index. If index is not specified, then the new
58993     * Records are added to the end of the RecordSet.
58994     *
58995     * @method setRecords
58996     * @param aData {Object[]} An array of object literal data.
58997     * @param index {Number} (optional) Position index.
58998     * @return {YAHOO.widget.Record[]} An array of Record instances.
58999     */
59000    setRecords : function(aData, index) {
59001        var Rec   = widget.Record,
59002            a     = lang.isArray(aData) ? aData : [aData],
59003            added = [],
59004            i = 0, l = a.length, j = 0;
59005
59006        index = parseInt(index,10)|0;
59007
59008        for(; i < l; ++i) {
59009            if (typeof a[i] === 'object' && a[i]) {
59010                added[j++] = this._records[index + i] = new Rec(a[i]);
59011            }
59012        }
59013
59014        this.fireEvent("recordsSetEvent",{records:added,data:aData});
59015        // Backward compatibility for bug 1918245
59016        this.fireEvent("recordsSet",{records:added,data:aData});
59017        YAHOO.log("Set "+j+" Record(s) at index "+index, "info",
59018                  this.toString());
59019
59020        if (a.length && !added.length) {
59021            YAHOO.log("Could not set Records with data " +
59022                    lang.dump(aData), "info", this.toString());
59023        }
59024
59025        return added;
59026    },
59027
59028    /**
59029     * Updates given Record with given data.
59030     *
59031     * @method updateRecord
59032     * @param record {YAHOO.widget.Record | Number | String} A Record instance,
59033     * a RecordSet position index, or a Record ID.
59034     * @param oData {Object} Object literal of new data.
59035     * @return {YAHOO.widget.Record} Updated Record, or null.
59036     */
59037    updateRecord : function(record, oData) {
59038        var oRecord = this.getRecord(record);
59039        if(oRecord && lang.isObject(oData)) {
59040            // Copy data from the Record for the event that gets fired later
59041            var oldData = {};
59042            for(var key in oRecord._oData) {
59043                if(lang.hasOwnProperty(oRecord._oData, key)) {
59044                    oldData[key] = oRecord._oData[key];
59045                }
59046            }
59047            oRecord._oData = oData;
59048            this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
59049            YAHOO.log("Record at index " + this.getRecordIndex(oRecord) +
59050                    " updated with data " + lang.dump(oData), "info", this.toString());
59051            return oRecord;
59052        }
59053        else {
59054            YAHOO.log("Could not update Record " + record, "error", this.toString());
59055            return null;
59056        }
59057    },
59058
59059    /**
59060     * @method updateKey
59061     * @deprecated Use updateRecordValue
59062     */
59063    updateKey : function(record, sKey, oData) {
59064        this.updateRecordValue(record, sKey, oData);
59065    },
59066    /**
59067     * Sets given Record at given key to given data.
59068     *
59069     * @method updateRecordValue
59070     * @param record {YAHOO.widget.Record | Number | String} A Record instance,
59071     * a RecordSet position index, or a Record ID.
59072     * @param sKey {String} Key name.
59073     * @param oData {Object} New data.
59074     */
59075    updateRecordValue : function(record, sKey, oData) {
59076        var oRecord = this.getRecord(record);
59077        if(oRecord) {
59078            var oldData = null;
59079            var keyValue = oRecord._oData[sKey];
59080            // Copy data from the Record for the event that gets fired later
59081            if(keyValue && lang.isObject(keyValue)) {
59082                oldData = {};
59083                for(var key in keyValue)  {
59084                    if(lang.hasOwnProperty(keyValue, key)) {
59085                        oldData[key] = keyValue[key];
59086                    }
59087                }
59088            }
59089            // Copy by value
59090            else {
59091                oldData = keyValue;
59092            }
59093
59094            oRecord._oData[sKey] = oData;
59095            this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData}
59095);
59096            this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
59097            YAHOO.log("Key \"" + sKey +
59098                    "\" for Record at index " + this.getRecordIndex(oRecord) +
59099                    " updated to \"" + lang.dump(oData) + "\"", "info", this.toString());
59100        }
59101        else {
59102            YAHOO.log("Could not update key " + sKey + " for Record " + record, "error", this.toString());
59103        }
59104    },
59105
59106    /**
59107     * Replaces all Records in RecordSet with new object literal data.
59108     *
59109     * @method replaceRecords
59110     * @param data {Object || Object[]} An object literal of data or an array of
59111     * data object literals.
59112     * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
59113     * an array of Records.
59114     */
59115    replaceRecords : function(data) {
59116        this.reset();
59117        return this.addRecords(data);
59118    },
59119
59120    /**
59121     * Sorts all Records by given function. Records keep their unique IDs but will
59122     * have new RecordSet position indexes.
59123     *
59124     * @method sortRecords
59125     * @param fnSort {Function} Reference to a sort function.
59126     * @param desc {Boolean} True if sort direction is descending, false if sort
59127     * direction is ascending.
59128     * @param field {String} The field to sort by, from sortOptions.field
59129     * @return {YAHOO.widget.Record[]} Sorted array of Records.
59130     */
59131    sortRecords : function(fnSort, desc, field) {
59132        return this._records.sort(function(a, b) {return fnSort(a, b, desc, field);});
59133    },
59134
59135    /**
59136     * Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"].
59137     *
59138     * @method reverseRecords
59139     * @return {YAHOO.widget.Record[]} Reverse-sorted array of Records.
59140     */
59141    reverseRecords : function() {
59142        return this._records.reverse();
59143    },
59144
59145    /**
59146     * Removes the Record at the given position index from the RecordSet. If a range
59147     * is also provided, removes that many Records, starting from the index. Length
59148     * of RecordSet is correspondingly shortened.
59149     *
59150     * @method deleteRecord
59151     * @param index {Number} Record's RecordSet position index.
59152     * @return {Object} A copy of the data held by the deleted Record.
59153     */
59154    deleteRecord : function(index) {
59155        if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
59156            var oData = this.getRecord(index).getData();
59157            
59158            this._deleteRecord(index);
59159            this.fireEvent("recordDeleteEvent",{data:oData,index:index});
59160            YAHOO.log("Record deleted at index " + index +
59161                    " and containing data " + lang.dump(oData), "info", this.toString());
59162            return oData;
59163        }
59164        else {
59165            YAHOO.log("Could not delete Record at index " + index, "error", this.toString());
59166            return null;
59167        }
59168    },
59169
59170    /**
59171     * Removes the Record at the given position index from the RecordSet. If a range
59172     * is also provided, removes that many Records, starting from the index. Length
59173     * of RecordSet is correspondingly shortened.
59174     *
59175     * @method deleteRecords
59176     * @param index {Number} Record's RecordSet position index.
59177     * @param range {Number} (optional) How many Records to delete.
59178     * @return {Object[]} An array of copies of the data held by the deleted Records.     
59179     */
59180    deleteRecords : function(index, range) {
59181        if(!lang.isNumber(range)) {
59182            range = 1;
59183        }
59184        if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
59185            var recordsToDelete = this.getRecords(index, range);
59186            var deletedData = [], // this mistakenly held Records, not data
59187                deletedObjects = []; // this passes data only
59188            
59189            for(var i=0; i<recordsToDelete.length; i++) {
59190                deletedData[deletedData.length] = recordsToDelete[i]; // backward compatibility
59191                deletedObjects[deletedObjects.length] = recordsToDelete[i].getData();
59192            }
59193            this._deleteRecord(index, range);
59194
59195            this.fireEvent("recordsDeleteEvent",{data:deletedData,deletedData:deletedObjects,index:index});
59196            YAHOO.log(range + "Record(s) deleted at index " + index +
59197                    " and containing data " + lang.dump(deletedObjects), "info", this.toString());
59198
59199            return deletedData;
59200        }
59201        else {
59202            YAHOO.log("Could not delete Records at index " + index, "error", this.toString());
59203            return null;
59204        }
59205    },
59206
59207    /**
59208     * Deletes all Records from the RecordSet.
59209     *
59210     * @method reset
59211     */
59212    reset : function() {
59213        this._records = [];
59214        //this._length = 0;
59215        this.fireEvent("resetEvent");
59216        YAHOO.log("All Records deleted from RecordSet", "info", this.toString());
59217    }
59218};
59219
59220/////////////////////////////////////////////////////////////////////////////
59221//
59222// Custom Events
59223//
59224/////////////////////////////////////////////////////////////////////////////
59225
59226// RecordSet uses EventProvider
59227lang.augmentProto(RS, util.EventProvider);
59228
59229/**
59230 * Fired when a new Record is added to the RecordSet.
59231 *
59232 * @event recordAddEvent
59233 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
59234 * @param oArgs.data {Object} Data added.
59235 */
59236
59237/**
59238 * Fired when multiple Records are added to the RecordSet at once.
59239 *
59240 * @event recordsAddEvent
59241 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
59242 * @param oArgs.data {Object[]} Data added.
59243 */
59244
59245/**
59246 * Fired when a Record is set in the RecordSet.
59247 *
59248 * @event recordSetEvent
59249 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
59250 * @param oArgs.data {Object} Data added.
59251 */
59252
59253/**
59254 * Fired when multiple Records are set in the RecordSet at once.
59255 *
59256 * @event recordsSetEvent
59257 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
59258 * @param oArgs.data {Object[]} Data added.
59259 */
59260
59261/**
59262 * Fired when a Record is updated with new data.
59263 *
59264 * @event recordUpdateEvent
59265 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
59266 * @param oArgs.newData {Object} New data.
59267 * @param oArgs.oldData {Object} Old data.
59268 */
59269
59270/**
59271 * Fired when a Record is deleted from the RecordSet.
59272 *
59273 * @event recordDeleteEvent
59274 * @param oArgs.data {Object} The data held by the deleted Record,
59275 * or an array of data object literals if multiple Records were deleted at once.
59276 * @param oArgs.index {Object} Index of the deleted Record.
59277 */
59278
59279/**
59280 * Fired when multiple Records are deleted from the RecordSet at once.
59281 *
59282 * @event recordsDeleteEvent
59283 * @param oArgs.data {Object[]} An array of deleted Records.
59284 * @param oArgs.deletedData {Object[]} An array of deleted data.
59285 * @param oArgs.index {Object} Index of the first deleted Record.
59286 */
59287
59288/**
59289 * Fired when all Records are deleted from the RecordSet at once.
59290 *
59291 * @event resetEvent
59292 */
59293
59294/**
59295 * @event keyUpdateEvent    
59296 * @deprecated Use recordValueUpdateEvent     
59297 */
59298
59299/**
59300 * Fired when a Record value is updated with new data.
59301 *
59302 * @event recordValueUpdateEvent
59303 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
59304 * @param oArgs.key {String} The updated key.
59305 * @param oArgs.newData {Object} New data.
59306 * @param oArgs.oldData {Object} Old data.
59307 *
59308 */
59309
59310
59311/****************************************************************************/
59312/****************************************************************************/
59313/****************************************************************************/
59314
59315/**
59316 * The Record class defines a DataTable record.
59317 *
59318 * @namespace YAHOO.widget
59319 * @class Record
59320 * @constructor
59321 * @param oConfigs {Object} (optional) Object literal of key/value pairs.
59322 */
59323YAHOO.widget.Record = function(oLiteral) {
59324    this._nCount = widget.Record._nCount;
59325    this._sId = Dom.generateId(null, "yui-rec");//"yui-rec" + this._nCount;
59326    widget.Record._nCount++;
59327    this._oData = {};
59328    if(lang.isObject(oLiteral)) {
59329        for(var sKey in oLiteral) {
59330            if(lang.hasOwnProperty(oLiteral, sKey)) {
59331                this._oData[sKey] = oLiteral[sKey];
59332            }
59333        }
59334    }
59335};
59336
59337/////////////////////////////////////////////////////////////////////////////
59338//
59339// Private member variables
59340//
59341/////////////////////////////////////////////////////////////////////////////
59342
59343/**
59344 * Internal class variable to give unique IDs to Record instances.
59345 *
59346 * @property Record._nCount
59347 * @type Number
59348 * @private
59349 */
59350YAHOO.widget.Record._nCount = 0;
59351
59352YAHOO.widget.Record.prototype = {
59353    /**
59354     * Immutable unique count assigned at instantiation. Remains constant while a
59355     * Record's position index can change from sorting.
59356     *
59357     * @property _nCount
59358     * @type Number
59359     * @private
59360     */
59361    _nCount : null,
59362
59363    /**
59364     * Immutable unique ID assigned at instantiation. Remains constant while a
59365     * Record's position index can change from sorting.
59366     *
59367     * @property _sId
59368     * @type String
59369     * @private
59370     */
59371    _sId : null,
59372
59373    /**
59374     * Holds data for the Record in an object literal.
59375     *
59376     * @property _oData
59377     * @type Object
59378     * @private
59379     */
59380    _oData : null,
59381
59382    /////////////////////////////////////////////////////////////////////////////
59383    //
59384    // Public member variables
59385    //
59386    /////////////////////////////////////////////////////////////////////////////
59387
59388    /////////////////////////////////////////////////////////////////////////////
59389    //
59390    // Public methods
59391    //
59392    /////////////////////////////////////////////////////////////////////////////
59393
59394    /**
59395     * Returns unique count assigned at instantiation.
59396     *
59397     * @method getCount
59398     * @return Number
59399     */
59400    getCount : function() {
59401        return this._nCount;
59402    },
59403
59404    /**
59405     * Returns unique ID assigned at instantiation.
59406     *
59407     * @method getId
59408     * @return String
59409     */
59410    getId : function() {
59411        return this._sId;
59412    },
59413
59414    /**
59415     * Returns data for the Record for a field if given, or the entire object
59416     * literal otherwise.
59417     *
59418     * @method getData
59419     * @param sField {String} (Optional) The field from which to retrieve data value.
59420     * @return Object
59421     */
59422    getData : function(sField) {
59423        if(lang.isString(sField)) {
59424            return this._oData[sField];
59425        }
59426        else {
59427            return this._oData;
59428        }
59429    },
59430
59431    /**
59432     * Sets given data at the given key. Use the RecordSet method updateRecordValue to trigger
59433     * events. 
59434     *
59435     * @method setData
59436     * @param sKey {String} The key of the new value.
59437     * @param oData {MIXED} The new value.
59438     */
59439    setData : function(sKey, oData) {
59440        this._oData[sKey] = oData;
59441    }
59442};
59443
59444})();
59445
59446(function () {
59447
59448var lang   = YAHOO.lang,
59449    util   = YAHOO.util,
59450    widget = YAHOO.widget,
59451    ua     = YAHOO.env.ua,
59452    
59453    Dom    = util.Dom,
59454    Ev     = util.Event,
59455    DS     = util.DataSourceBase;
59456
59457/**
59458 * The DataTable widget provides a progressively enhanced DHTML control for
59459 * displaying tabular data across A-grade browsers.
59460 *
59461 * @module datatable
59462 * @requires yahoo, dom, event, element, datasource
59463 * @optional dragdrop, dragdrop
59464 * @title DataTable Widget
59465 */
59466
59467/****************************************************************************/
59468/****************************************************************************/
59469/****************************************************************************/
59470
59471/**
59472 * DataTable class for the YUI DataTable widget.
59473 *
59474 * @namespace YAHOO.widget
59475 * @class DataTable
59476 * @extends YAHOO.util.Element
59477 * @constructor
59478 * @param elContainer {HTMLElement} Container element for the TABLE.
59479 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
59480 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
59481 * @param oConfigs {object} (optional) Object literal of configuration values.
59482 */
59483YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
59484    var DT = widget.DataTable;
59485    
59486    ////////////////////////////////////////////////////////////////////////////
59487    // Backward compatibility for SDT, but prevent infinite loops
59488    
59489    if(oConfigs && oConfigs.scrollable) {
59490        return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs);
59491    }
59492    
59493    ////////////////////////////////////////////////////////////////////////////
59494    // Initialization
59495
59496    // Internal vars
59497    this._nIndex = DT._nCount;
59498    this._sId = Dom.generateId(null, "yui-dt");// "yui-dt"+this._nIndex;
59499    this._oChainRender = new YAHOO.util.Chain();
59500    this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true);
59501
59502    // Initialize configs
59503    this._initConfigs(oConfigs);
59504
59505    // Initialize DataSource
59506    this._initDataSource(oDataSource);
59507    if(!this._oDataSource) {
59508        YAHOO.log("Could not instantiate DataTable due to an invali
59508d DataSource", "error", this.toString());
59509        return;
59510    }
59511
59512    // Initialize ColumnSet
59513    this._initColumnSet(aColumnDefs);
59514    if(!this._oColumnSet) {
59515        YAHOO.log("Could not instantiate DataTable due to an invalid ColumnSet", "error", this.toString());
59516        return;
59517    }
59518
59519    // Initialize RecordSet
59520    this._initRecordSet();
59521    if(!this._oRecordSet) {
59522    }
59523
59524    // Initialize Attributes
59525    DT.superclass.constructor.call(this, elContainer, this.configs);
59526
59527    // Initialize DOM elements
59528    var okDom = this._initDomElements(elContainer);
59529    if(!okDom) {
59530        YAHOO.log("Could not instantiate DataTable due to an invalid DOM element", "error", this.toString());
59531        return;
59532    }
59533            
59534    // Show message as soon as config is available
59535    this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
59536    
59537    ////////////////////////////////////////////////////////////////////////////
59538    // Once per instance
59539    this._initEvents();
59540
59541    DT._nCount++;
59542    DT._nCurrentCount++;
59543    
59544    ////////////////////////////////////////////////////////////////////////////
59545    // Data integration
59546
59547    // Send a simple initial request
59548    var oCallback = {
59549        success : this.onDataReturnSetRows,
59550        failure : this.onDataReturnSetRows,
59551        scope   : this,
59552        argument: this.getState()
59553    };
59554    
59555    var initialLoad = this.get("initialLoad");
59556    if(initialLoad === true) {
59557        this._oDataSource.sendRequest(this.get("initialRequest"), oCallback);
59558    }
59559    // Do not send an initial request at all
59560    else if(initialLoad === false) {
59561        this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
59562    }
59563    // Send an initial request with a custom payload
59564    else {
59565        var oCustom = initialLoad || {};
59566        oCallback.argument = oCustom.argument || {};
59567        this._oDataSource.sendRequest(oCustom.request, oCallback);
59568    }
59569};
59570
59571var DT = widget.DataTable;
59572
59573/////////////////////////////////////////////////////////////////////////////
59574//
59575// Public constants
59576//
59577/////////////////////////////////////////////////////////////////////////////
59578
59579lang.augmentObject(DT, {
59580
59581    /**
59582     * Class name assigned to outer DataTable container.
59583     *
59584     * @property DataTable.CLASS_DATATABLE
59585     * @type String
59586     * @static
59587     * @final
59588     * @default "yui-dt"
59589     */
59590    CLASS_DATATABLE : "yui-dt",
59591
59592    /**
59593     * Class name assigned to liner DIV elements.
59594     *
59595     * @property DataTable.CLASS_LINER
59596     * @type String
59597     * @static
59598     * @final
59599     * @default "yui-dt-liner"
59600     */
59601    CLASS_LINER : "yui-dt-liner",
59602
59603    /**
59604     * Class name assigned to display label elements.
59605     *
59606     * @property DataTable.CLASS_LABEL
59607     * @type String
59608     * @static
59609     * @final
59610     * @default "yui-dt-label"
59611     */
59612    CLASS_LABEL : "yui-dt-label",
59613
59614    /**
59615     * Class name assigned to messaging elements.
59616     *
59617     * @property DataTable.CLASS_MESSAGE
59618     * @type String
59619     * @static
59620     * @final
59621     * @default "yui-dt-message"
59622     */
59623    CLASS_MESSAGE : "yui-dt-message",
59624
59625    /**
59626     * Class name assigned to mask element when DataTable is disabled.
59627     *
59628     * @property DataTable.CLASS_MASK
59629     * @type String
59630     * @static
59631     * @final
59632     * @default "yui-dt-mask"
59633     */
59634    CLASS_MASK : "yui-dt-mask",
59635
59636    /**
59637     * Class name assigned to data elements.
59638     *
59639     * @property DataTable.CLASS_DATA
59640     * @type String
59641     * @static
59642     * @final
59643     * @default "yui-dt-data"
59644     */
59645    CLASS_DATA : "yui-dt-data",
59646
59647    /**
59648     * Class name assigned to Column drag target.
59649     *
59650     * @property DataTable.CLASS_COLTARGET
59651     * @type String
59652     * @static
59653     * @final
59654     * @default "yui-dt-coltarget"
59655     */
59656    CLASS_COLTARGET : "yui-dt-coltarget",
59657
59658    /**
59659     * Class name assigned to resizer handle elements.
59660     *
59661     * @property DataTable.CLASS_RESIZER
59662     * @type String
59663     * @static
59664     * @final
59665     * @default "yui-dt-resizer"
59666     */
59667    CLASS_RESIZER : "yui-dt-resizer",
59668
59669    /**
59670     * Class name assigned to resizer liner elements.
59671     *
59672     * @property DataTable.CLASS_RESIZERLINER
59673     * @type String
59674     * @static
59675     * @final
59676     * @default "yui-dt-resizerliner"
59677     */
59678    CLASS_RESIZERLINER : "yui-dt-resizerliner",
59679
59680    /**
59681     * Class name assigned to resizer proxy elements.
59682     *
59683     * @property DataTable.CLASS_RESIZERPROXY
59684     * @type String
59685     * @static
59686     * @final
59687     * @default "yui-dt-resizerproxy"
59688     */
59689    CLASS_RESIZERPROXY : "yui-dt-resizerproxy",
59690
59691    /**
59692     * Class name assigned to CellEditor container elements.
59693     *
59694     * @property DataTable.CLASS_EDITOR
59695     * @type String
59696     * @static
59697     * @final
59698     * @default "yui-dt-editor"
59699     */
59700    CLASS_EDITOR : "yui-dt-editor",
59701
59702    /**
59703     * Class name assigned to CellEditor container shim.
59704     *
59705     * @property DataTable.CLASS_EDITOR_SHIM
59706     * @type String
59707     * @static
59708     * @final
59709     * @default "yui-dt-editor-shim"
59710     */
59711    CLASS_EDITOR_SHIM : "yui-dt-editor-shim",
59712
59713    /**
59714     * Class name assigned to paginator container elements.
59715     *
59716     * @property DataTable.CLASS_PAGINATOR
59717     * @type String
59718     * @static
59719     * @final
59720     * @default "yui-dt-paginator"
59721     */
59722    CLASS_PAGINATOR : "yui-dt-paginator",
59723
59724    /**
59725     * Class name assigned to page number indicators.
59726     *
59727     * @property DataTable.CLASS_PAGE
59728     * @type String
59729     * @static
59730     * @final
59731     * @default "yui-dt-page"
59732     */
59733    CLASS_PAGE : "yui-dt-page",
59734
59735    /**
59736     * Class name assigned to default indicators.
59737     *
59738     * @property DataTable.CLASS_DEFAULT
59739     * @type String
59740     * @static
59741     * @final
59742     * @default "yui-dt-default"
59743     */
59744    CLASS_DEFAULT : "yui-dt-default",
59745
59746    /**
59747     * Class name assigned to previous indicators.
59748     *
59749     * @property DataTable.CLASS_PREVIOUS
59750     * @type String
59751     * @static
59752     * @final
59753     * @default "yui-dt-previous"
59754     */
59755    CLASS_PREVIOUS : "yui-dt-previous",
59756
59757    /**
59758     * Class name assigned next indicators.
59759     *
59760     * @property DataTable.CLASS_NEXT
59761     * @type String
59762     * @static
59763     * @final
59764     * @default "yui-dt-next"
59765     */
59766    CLASS_NEXT : "yui-dt-next",
59767
59768    /**
59769     * Class name assigned to first elements.
59770     *
59771     * @property DataTable.CLASS_FIRST
59772     * @type String
59773     * @static
59774     * @final
59775     * @default "yui-dt-first"
59776     */
59777    CLASS_FIRST : "yui-dt-first",
59778
59779    /**
59780     * Class name assigned to last elements.
59781     *
59782     * @property DataTable.CLASS_LAST
59783     * @type String
59784     * @static
59785     * @final
59786     * @default "yui-dt-last"
59787     */
59788    CLASS_LAST : "yui-dt-last",
59789
59790    /**
59791     * Class name assigned to Record elements.
59792     *
59793     * @property DataTable.CLASS_REC
59794     * @type String
59795     * @static
59796     * @final
59797     * @default "yui-dt-rec"
59798     */
59799    CLASS_REC : "yui-dt-rec",
59800
59801    /**
59802     * Class name assigned to even elements.
59803     *
59804     * @property DataTable.CLASS_EVEN
59805     * @type String
59806     * @static
59807     * @final
59808     * @default "yui-dt-even"
59809     */
59810    CLASS_EVEN : "yui-dt-even",
59811
59812    /**
59813     * Class name assigned to odd elements.
59814     *
59815     * @property DataTable.CLASS_ODD
59816     * @type String
59817     * @static
59818     * @final
59819     * @default "yui-dt-odd"
59820     */
59821    CLASS_ODD : "yui-dt-odd",
59822
59823    /**
59824     * Class name assigned to selected elements.
59825     *
59826     * @property DataTable.CLASS_SELECTED
59827     * @type String
59828     * @static
59829     * @final
59830     * @default "yui-dt-selected"
59831     */
59832    CLASS_SELECTED : "yui-dt-selected",
59833
59834    /**
59835     * Class name assigned to highlighted elements.
59836     *
59837     * @property DataTable.CLASS_HIGHLIGHTED
59838     * @type String
59839     * @static
59840     * @final
59841     * @default "yui-dt-highlighted"
59842     */
59843    CLASS_HIGHLIGHTED : "yui-dt-highlighted",
59844
59845    /**
59846     * Class name assigned to hidden elements.
59847     *
59848     * @property DataTable.CLASS_HIDDEN
59849     * @type String
59850     * @static
59851     * @final
59852     * @default "yui-dt-hidden"
59853     */
59854    CLASS_HIDDEN : "yui-dt-hidden",
59855
59856    /**
59857     * Class name assigned to disabled elements.
59858     *
59859     * @property DataTable.CLASS_DISABLED
59860     * @type String
59861     * @static
59862     * @final
59863     * @default "yui-dt-disabled"
59864     */
59865    CLASS_DISABLED : "yui-dt-disabled",
59866
59867    /**
59868     * Class name assigned to empty indicators.
59869     *
59870     * @property DataTable.CLASS_EMPTY
59871     * @type String
59872     * @static
59873     * @final
59874     * @default "yui-dt-empty"
59875     */
59876    CLASS_EMPTY : "yui-dt-empty",
59877
59878    /**
59879     * Class name assigned to loading indicatorx.
59880     *
59881     * @property DataTable.CLASS_LOADING
59882     * @type String
59883     * @static
59884     * @final
59885     * @default "yui-dt-loading"
59886     */
59887    CLASS_LOADING : "yui-dt-loading",
59888
59889    /**
59890     * Class name assigned to error indicators.
59891     *
59892     * @property DataTable.CLASS_ERROR
59893     * @type String
59894     * @static
59895     * @final
59896     * @default "yui-dt-error"
59897     */
59898    CLASS_ERROR : "yui-dt-error",
59899
59900    /**
59901     * Class name assigned to editable elements.
59902     *
59903     * @property DataTable.CLASS_EDITABLE
59904     * @type String
59905     * @static
59906     * @final
59907     * @default "yui-dt-editable"
59908     */
59909    CLASS_EDITABLE : "yui-dt-editable",
59910
59911    /**
59912     * Class name assigned to draggable elements.
59913     *
59914     * @property DataTable.CLASS_DRAGGABLE
59915     * @type String
59916     * @static
59917     * @final
59918     * @default "yui-dt-draggable"
59919     */
59920    CLASS_DRAGGABLE : "yui-dt-draggable",
59921
59922    /**
59923     * Class name assigned to resizeable elements.
59924     *
59925     * @property DataTable.CLASS_RESIZEABLE
59926     * @type String
59927     * @static
59928     * @final
59929     * @default "yui-dt-resizeable"
59930     */
59931    CLASS_RESIZEABLE : "yui-dt-resizeable",
59932
59933    /**
59934     * Class name assigned to scrollable elements.
59935     *
59936     * @property DataTable.CLASS_SCROLLABLE
59937     * @type String
59938     * @static
59939     * @final
59940     * @default "yui-dt-scrollable"
59941     */
59942    CLASS_SCROLLABLE : "yui-dt-scrollable",
59943
59944    /**
59945     * Class name assigned to sortable elements.
59946     *
59947     * @property DataTable.CLASS_SORTABLE
59948     * @type String
59949     * @static
59950     * @final
59951     * @default "yui-dt-sortable"
59952     */
59953    CLASS_SORTABLE : "yui-dt-sortable",
59954
59955    /**
59956     * Class name assigned to ascending elements.
59957     *
59958     * @property DataTable.CLASS_ASC
59959     * @type String
59960     * @static
59961     * @final
59962     * @default "yui-dt-asc"
59963     */
59964    CLASS_ASC : "yui-dt-asc",
59965
59966    /**
59967     * Class name assigned to descending elements.
59968     *
59969     * @property DataTable.CLASS_DESC
59970     * @type String
59971     * @static
59972     * @final
59973     * @default "yui-dt-desc"
59974     */
59975    CLASS_DESC : "yui-dt-desc",
59976
59977    /**
59978     * Class name assigned to BUTTON elements and/or container elements.
59979     *
59980     * @property DataTable.CLASS_BUTTON
59981     * @type String
59982     * @static
59983     * @final
59984     * @default "yui-dt-button"
59985     */
59986    CLASS_BUTTON : "yui-dt-button",
59987
59988    /**
59989     * Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements.
59990     *
59991     * @property DataTable.CLASS_CHECKBOX
59992     * @type String
59993     * @static
59994     * @final
59995     * @default "yui-dt-checkbox"
59996     */
59997    CLASS_CHECKBOX : "yui-dt-checkbox",
59998
59999    /**
60000     * Class name assigned to SELECT elements and/or container elements.
60001     *
60002     * @property DataTable.CLASS_DROPDOWN
60003     * @type String
60004     * @static
60005     * @final
60006     * @default "yui-dt-dropdown"
60007     */
60008    CLASS_DROPDOWN : "yui-dt-dropdown",
60009
60010    /**
60011     * Class name assigned to INPUT TYPE=RADIO elements and/or container elements.
60012     *
60013     * @property DataTable.CLASS_RADIO
60014     * @type String
60015     * @static
60016     * @final
60017     * @default "yui-dt-radio"
60018     */
60019    CLASS_RADIO : "yui-dt-radio",
60020
60021    /////////////////////////////////////////////////////////////////////////
60022    //
60023    // Private static properties
60024    //
60025    /////////////////////////////////////////////////////////////////////////
60026
60027    /**
60028     * Internal class variable for indexing multiple DataTable instances.
60029     *
60030     * @property DataTable._nCount
60031     * @type Number
60032     * @private
60033     * @static
60034     */
60035    _nCount : 0,
60036
60037    /**
60038     * Internal class variable tracking current number of DataTable instances,
60039     * so that certain class values can be reset when all instances are destroyed.          
60040     *
60041     * @property DataTable._nCurrentCount
60042     * @type Number
60043     * @private
60044     * @static
60045     */
60046    _nCurrentCount : 0,
60047
60048    /**
60049     * Reference to the STYLE node that is dynamically created and updated
60050     * in order to manage Column widths.
60051     *
60052     * @property DataTable._elDynStyleNode
60053     * @type HTMLElement
60054     * @private
60055     * @static     
60056     */
60057    _elDynStyleNode : null,
60058
60059    /**
60060     * Set to true if _elDynStyleNode cannot be populated due to browser incompatibility.
60061     *
60062     * @property DataTable._bDynStylesFallback
60063     * @type boolean
60064     * @private
60065     * @static     
60066     */
60067    _bDynStylesFallback : (ua.ie) ? true : false,
60068
60069    /**
60070     * Object literal hash of Columns and their dynamically create style rules.
60071     *
60072     * @property DataTable._oDynStyles
60073     * @type Object
60074     * @private
60075     * @static     
60076     */
60077    _oDynStyles : {},
60078
60079    /////////////////////////////////////////////////////////////////////////
60080    //
60081    // Private static methods
60082    //
60083    /////////////////////////////////////////////////////////////////////////
60084
60085    /**
60086     * Clones object literal or array of object literals.
60087     *
60088     * @method DataTable._cloneObject
60089     * @param o {Object} Object.
60090     * @private
60091     * @static     
60092     */
60093    _cloneObject: function(o) {
60094        if(!lang.isValue(o)) {
60095            return o;
60096        }
60097
60098        var copy = {};
60099
60100        if(o instanceof YAHOO.widget.BaseCellEditor) {
60101            copy = o;
60102        }
60103        else if(Object.prototype.toString.apply(o) === "[object RegExp]") {
60104            copy = o;
60105        }
60106        else if(lang.isFunction(o)) {
60107            copy = o;
60108        }
60109        else if(lang.isArray(o)) {
60110            var array = [];
60111            for(var i=0,len=o.length;i<len;i++) {
60112                array[i] = DT._cloneObject(o[i]);
60113            }
60114            copy = array;
60115        }
60116        else if(lang.isObject(o)) {
60117            for (var x in o){
60118                if(lang.hasOwnProperty(o, x)) {
60119                    if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) {
60120                        copy[x] = DT._cloneObject(o[x]);
60121                    }
60122                    else {
60123                        copy[x] = o[x];
60124                    }
60125                }
60126            }
60127        }
60128        else {
60129            copy = o;
60130        }
60131
60132        return copy;
60133    },
60134
60135    /**
60136     * Formats a BUTTON element.
60137     *
60138     * @method DataTable.formatButton
60139     * @param el {HTMLElement} The element to format with markup.
60140     * @param oRecord {YAHOO.widget.Record} Record instance.
60141     * @param oColumn {YAHOO.widget.Column} Column instance.
60142     * @param oData {HTML} Data value for the cell. By default, the value
60143     * is what gets written to the BUTTON. String values are treated as markup
60144     * and inserted into the DOM with innerHTML.
60145     * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
60146     * @static
60147     */
60148    formatButton : function(el, oRecord, oColumn, oData, oDataTable) {
60149        var sValue = lang.isValue(oData) ? oData : "Click";
60150        //TODO: support YAHOO.widget.Button
60151        //if(YAHOO.widget.Button) {
60152
60153        //}
60154        //else {
60155            el.innerHTML = "<button type=\"button\" class=\""+
60156                    DT.CLASS_BUTTON + "\">" + sValue + "</button>";
60157        //}
60158    },
60159
60160    /**
60161     * Formats a CHECKBOX element.
60162     *
60163     * @method DataTable.formatCheckbox
60164     * @param el {HTMLElement} The element to format with markup.
60165     * @param oRecord {YAHOO.widget.Record} Record instance.
60166     * @param oColumn {YAHOO.widget.Column} Column instance.
60167     * @param oData {Object | Boolean | HTML} Data value for the cell. Can be a simple
60168     * Boolean to indicate whether checkbox is checked or not. Can be object literal
60169     * {checked:bBoolean, label:sLabel}. String values are treated as markup
60170     * and inserted into the DOM with innerHTML.
60171     * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
60172     * @static
60173     */
60174    formatCheckbox : function(el, oRecord, oColumn, oData, oDataTable) {
60175        var bChecked = oData;
60176        bChecked = (bChecked) ? " checked=\"checked\"" : "";
60177        el.innerHTML = "<input type=\"checkbox\"" + bChecked +
60178                " class=\"" + DT.CLASS_CHECKBOX + "\" />";
60179    },
60180
60181    /**
60182     * Formats currency. Default unit is USD.
60183     *
60184     * @method DataTable.formatCurrency
60185     * @param el {HTMLElement} The element to format with markup.
60186     * @param oRecord {YAHOO.widget.Record} Record instance.
60187     * @param oColumn {YAHOO.widget.Column} Column instance.
60188     * @param oData {Number} Data value for the cell.
60189     * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
60190     * @static
60191     */
60192    formatCurrency : function(el, oRecord, oColumn, oData, oDataTable) {
60193        var oDT = oDataTable || this;
60194        el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || oDT.get("currencyOptions"));
60195    },
60196
60197    /**
60198     * Formats JavaScript Dates.
60199     *
60200     * @method DataTable.formatDate
60201     * @param el {HTMLElement} The element to format with markup.
60202     * @param oRecord {YAHOO.widget.Record} Record instance.
60203     * @param oColumn {YAHOO.widget.Column} Column instance.
60204     * @param oData {Object} Data value for the cell, or null. String values are
60205     * treated as markup and inserted into the DOM with innerHTML.
60206     * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
60207     * @static
60208     */
60209    formatDate : function(el, oRecord, oColumn, oData, oDataTable) {
60210        var oDT = oDataTable || this,
60211            oConfig = oColumn.dateOptions || oDT.get("dateOptions");
60212        el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale);
60213    },
60214
60215    /**
60216     * Formats SELECT elements.
60217     *
60218     * @method DataTable.formatDropdown
60219     * @param el {HTMLElement} The element to format with markup.
60220     * @param oRecord {YAHOO.widget.Record} Record instance.
60221     * @param oColumn {YAHOO.widget.Column} Column instance.
60222     * @param oData {Object} Data value for the cell, or null. String values may
60223     * be treated as markup and inserted into the DOM with innerHTML as element
60224     * label.
60225     * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
60226     * @static
60227     */
60228    formatDropdown : function(el, oRecord, oColumn, oData, oDataTable) {
60229        var oDT = oDataTable || this,
60230            selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field),
60231            options = (lang.isArray(oColumn.dropdownOptions)) ?
60232                oColumn.dropdownOptions : null,
60233
60234            selectEl,
60235            collection = el.getElementsByTagName("select");
60236
60237        // Create the form element only once, so we can attach the onChange listener
60238        if(collection.length === 0) {
60239            // Create SELECT element
60240            selectEl = document.createElement("select");
60241            selectEl.className = DT.CLASS_DROPDOWN;
60242            selectEl = el.appendChild(selectEl);
60243
60244            // Add event listener
60245            Ev.addListener(selectEl,"change",oDT._onDropdownChange,oDT);
60246        }
60247
60248        selectEl = collection[0];
60249
60250        // Update the form element
60251        if(selectEl) {
60252            // Clear out previous options
60253            selectEl.innerHTML = "";
60254
60255            // We have options to populate
60256            if(options) {
60257                // Create OPTION elements
60258                for(var i=0; i<options.length; i++) {
60259                    var option = options[i];
60260                    var optionEl = document.createElement("option");
60261                    optionEl.value = (lang.isValue(option.value)) ?
60262                            option.value : option;
60263                    // Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor
60264                    optionEl.innerHTML = (lang.isValue(option.text)) ?
60265                            option.text : (lang.isValue(option.label)) ? option.label : option;
60266                    optionEl = selectEl.appendChild(optionEl);
60267                    if (optionEl.value == selectedValue) {
60268                        optionEl.selected = true;
60269                    }
60270                }
60271            }
60272            // Selected value is our only option
60273            else {
60274                selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>";
60275            }
60276        }
60277        else {
60278            el.innerHTML = lang.isValue(oData) ? oData : "";
60279        }
60280    },
60281
60282    /**
60283     * Formats emails.
60284     *
60285     * @method DataTable.formatEmail
60286     * @param el {HTMLElement} The element to format with markup.
60287     * @param oRecord {YAHOO.widget.Record} Record instance.
60288     * @param oColumn {YAHOO.widget.Column} Column instance.
60289     * @param oData {String} Data value for the cell, or null. Values are
60290     * HTML-escaped.
60291     * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
60292     * @static
60293     */
60294    formatEmail : function(el, oRecord, oColumn, oData, oDataTable) {
60295        if(lang.isString(oData)) {
60296            oData = lang.escapeHTML(oData);
60297            el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
60298        }
60299        else {
60300            el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : "";
60301        }
60302    },
60303
60304    /**
60305     * Formats links.
60306     *
60307     * @method DataTable.formatLink
60308     * @param el {HTMLElement} The element to format with markup.
60309     * @param oRecord {YAHOO.widget.Record} Record instance.
60310     * @param oColumn {YAHOO.widget.Column} Column instance.
60311     * @param oData {String} Data value for the cell, or null. Values are
60312     * HTML-escaped
60313     * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
60314     * @static
60315     */
60316    formatLink : function(el, oRecord, oColumn, oData, oDataTable) {
60317        if(lang.isString(oData)) {
60318            oData = lang.escapeHTML(oData);
60319            el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>";
60320        }
60321        else {
60322            el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : "";
60323        }
60324    },
60325
60326    /**
60327     * Formats numbers.
60328     *
60329     * @method DataTable.formatNumber
60330     * @param el {HTMLElement} The element to format with markup.
60331     * @param oRecord {YAHOO.widget.Record} Record instance.
60332     * @param oColumn {YAHOO.widget.Column} Column instance.
60333     * @param oData {Object} Data value for the cell, or null.
60334     * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
60335     * @static
60336     */
60337    formatNumber : function(el, oRecord, oColumn, oData, oDataTable) {
60338        var oDT = oDataTable || this;
60339        el.innerHTML = util.Number.format(oData, oColumn.numberOptions || oDT.get("numberOptions"));
60340    },
60341
60342    /**
60343     * Formats INPUT TYPE=RADIO elements.
60344     *
60345     * @method DataTable.formatRadio
60346     * @param el {HTMLElement} The element to format with markup.
60347     * @param oRecord {YAHOO.widget.Record} Record instance.
60348     * @param oColumn {YAHOO.widget.Column} Column instance.
60349     * @param oData {Object} (Optional) Data value for the cell.
60350     * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
60351     * @static
60352     */
60353    formatRadio : function(el, oRecord, oColumn, oData, oDataTable) {
60354        var oDT = oDataTable || this,
60355            bChecked = oData;
60356        bChecked = (bChecked) ? " checked=\"checked\"" : "";
60357        el.innerHTML = "<input type=\"radio\"" + bChecked +
60358                " name=\""+oDT.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" +
60359                " class=\"" + DT.CLASS_RADIO+ "\" />";
60360    },
60361
60362    /**
60363     * Formats text strings.
60364     *
60365     * @method DataTable.formatText
60366     * @param el {HTMLElement} The element to format with markup.
60367     * @param oRecord {YAHOO.widget.Record} Record instance.
60368     * @param oColumn {YAHOO.widget.Column} Column instance.
60369     * @param oData {String} (Optional) Data value for the cell. Values are
60370     * HTML-escaped.
60371     * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
60372     * @static
60373     */
60374    formatText : function(el, oRecord, oColumn, oData, oDataTable) {
60375        var value = (lang.isValue(oData)) ? oData : "";
60376        el.innerHTML = lang.escapeHTML(value.toString());
60377    },
60378
60379    /**
60380     * Formats TEXTAREA elements.
60381     *
60382     * @method DataTable.formatTextarea
60383     * @param el {HTMLElement} The element to format with markup.
60384     * @param oRecord {YAHOO.widget.Record} Record instance.
60385     * @param oColumn {YAHOO.widget.Column} Column instance.
60386     * @param oData {Object} (Optional) Data value for the cell. Values are
60387     * HTML-escaped.
60388     * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
60389     * @static
60390     */
60391    formatTextarea : function(el, oRecord, oColumn, oData, oDataTable) {
60392        var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "",
60393            markup = "<textarea>" + value + "</textarea>";
60394        el.innerHTML = markup;
60395    },
60396
60397    /**
60398     * Formats INPUT TYPE=TEXT elements.
60399     *
60400     * @method DataTable.formatTextbox
60401     * @param el {HTMLElement} The element to format with markup.
60402     * @param oRecord {YAHOO.widget.Record} Record instance.
60403     * @param oColumn {YAHOO.widget.Column} Column instance.
60404     * @param oData {Object} (Optional) Data value for the cell. Values are
60405     * HTML-escaped.
60406     * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
60407     * @static
60408     */
60409    formatTextbox : function(el, oRecord, oColumn, oData, oDataTable) {
60410        var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "",
60411            markup = "<input type=\"text\" value=\"" + value + "\" />";
60412        el.innerHTML = markup;
60413    },
60414
60415    /**
60416     * Default cell formatter
60417     *
60418     * @method DataTable.formatDefault
60419     * @param el {HTMLElement} The element to format with markup.
60420     * @param oRecord {YAHOO.widget.Record} Record instance.
60421     * @param oColumn {YAHOO.widget.Column} Column instance.
60422     * @param oData {HTML} (Optional) Data value for the cell. String values are
60423     * treated as markup and inserted into the DOM with innerHTML.
60424     * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
60425     * @static
60426     */
60427    formatDefault : function(el, oRecord, oColumn, oData, oDataTable) {
60428        el.innerHTML = (lang.isValue(oData) && oData !== "") ? oData.toString() : "&#160;";
60429    },
60430
60431    /**
60432     * Validates data value to type Number, doing type conversion as
60433     * necessary. A valid Number value is return, else null is returned
60434     * if input value does not validate.
60435     *
60436     *
60437     * @method DataTable.validateNumber
60438     * @param oData {Object} Data to validate.
60439     * @static
60440    */
60441    validateNumber : function(oData) {
60442        //Convert to number
60443        var number = oData * 1;
60444
60445        // Validate
60446        if(lang.isNumber(number)) {
60447            return number;
60448        }
60449        else {
60450            YAHOO.log("Could not validate data " + lang.dump(oData) + " to type Number", "warn", this.toString());
60451            return undefined;
60452        }
60453    }
60454});
60455
60456// Done in separate step so referenced functions are defined.
60457/**
60458 * Registry of cell formatting functions, enables shortcut pointers in Column
60459 * definition formatter value (i.e., {key:"myColumn", formatter:"date"}).
60460 * @property DataTable.Formatter
60461 * @type Object
60462 * @static
60463 */
60464DT.Formatter = {
60465    button   : DT.formatButton,
60466    checkbox : DT.formatCheckbox,
60467    currency : DT.formatCurrency,
60468    "date"   : DT.formatDate,
60469    dropdown : DT.formatDropdown,
60470    email    : DT.formatEmail,
60471    link     : DT.formatLink,
60472    "number" : DT.formatNumber,
60473    radio    : DT.formatRadio,
60474    text     : DT.formatText,
60475    textarea : DT.formatTextarea,
60476    textbox  : DT.formatTextbox,
60477
60478    defaultFormatter : DT.formatDefault
60479};
60480
60481lang.extend(DT, util.Element, {
60482
60483/////////////////////////////////////////////////////////////////////////////
60484//
60485// Superclass methods
60486//
60487/////////////////////////////////////////////////////////////////////////////
60488
60489/**
60490 * Implementation of Element's abstract method. Sets up config values.
60491 *
60492 * @method initAttributes
60493 * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
60494 * @private
60495 */
60496
60497initAttributes : function(oConfigs) {
60498    oConfigs = oConfigs || {};
60499    DT.superclass.initAttributes.call(this, oConfigs);
60500
60501    /**
60502    * @attribute summary
60503    * @description String value for the SUMMARY attribute.
60504    * @type String
60505    * @default ""    
60506    */
60507    this.setAttributeConfig("summary", {
60508        value: "",
60509        validator: lang.isString,
60510        method: function(sSummary) {
60511            if(this._elTable) {
60512                this._elTable.summary = sSummary;
60513            }
60514        }
60515    });
60516
60517    /**
60518    * @attribute selectionMode
60519    * @description Specifies row or cell selection mode. Accepts the following strings:
60520    *    <dl>
60521    *      <dt>"standard"</dt>
60522    *      <dd>Standard row selection with support for modifier keys to enable
60523    *      multiple selections.</dd>
60524    *
60525    *      <dt>"single"</dt>
60526    *      <dd>Row selection with modifier keys disabled to not allow
60527    *      multiple selections.</dd>
60528    *
60529    *      <dt>"singlecell"</dt>
60530    *      <dd>Cell selection with modifier keys disabled to not allow
60531    *      multiple selections.</dd>
60532    *
60533    *      <dt>"cellblock"</dt>
60534    *      <dd>Cell selection with support for modifier keys to enable multiple
60535    *      selections in a block-fashion, like a spreadsheet.</dd>
60536    *
60537    *      <dt>"cellrange"</dt>
60538    *      <dd>Cell selection with support for modifier keys to enable multiple
60539    *      selections in a range-fashion, like a calendar.</dd>
60540    *    </dl>
60541    *
60542    * @default "standard"
60543    * @type String
60544    */
60545    this.setAttributeConfig("selectionMode", {
60546        value: "standard",
60547        validator: lang.isString
60548    });
60549
60550    /**
60551    * @attribute sortedBy
60552    * @description Object literal provides metadata for initial sort values if
60553    * data will arrive pre-sorted:
60554    * <dl>
60555    *     <dt>sortedBy.key</dt>
60556    *     <dd>{String} Key of sorted Column</dd>
60557    *     <dt>sortedBy.dir</dt>
60558    *     <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
60559    * </dl>
60560    * @type Object | null
60561    */
60562    this.setAttributeConfig("sortedBy", {
60563        value: null,
60564        // TODO: accepted array for nested sorts
60565        validator: function(oNewSortedBy) {
60566            if(oNewSortedBy) {
60567                return (lang.isObject(oNewSortedBy) && oNewSortedBy.key);
60568            }
60569            else {
60570                return (oNewSortedBy === null);
60571            }
60572        },
60573        method: function(oNewSortedBy) {
60574            // Stash the previous value
60575            var oOldSortedBy = this.get("sortedBy");
60576            
60577            // Workaround for bug 1827195
60578            this._configs.sortedBy.value = oNewSortedBy;
60579
60580            // Remove ASC/DESC from TH
60581            var oOldColumn,
60582                nOldColumnKeyIndex,
60583                oNewColumn,
60584                nNewColumnKeyIndex;
60585                
60586            if(this._elThead) {
60587                if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) {
60588                    oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
60589                    nOldColumnKeyIndex = oOldColumn.getKeyIndex();
60590                    
60591                    // Remove previous UI from THEAD
60592                    var elOldTh = oOldColumn.getThEl();
60593                    Dom.removeClass(elOldTh, oOldSortedBy.dir);
60594                    this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy);
60595                    oOldColumn.getThEl().setAttribute("aria-sort","none");
60596                }
60597                if(oNewSortedBy) {
60598                    oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
60599                    nNewColumnKeyIndex = oNewColumn.getKeyIndex();
60600    
60601                    // Update THEAD with new UI
60602                    var elNewTh = oNewColumn.getThEl();
60603                    // Backward compatibility
60604                    if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") ||  (oNewSortedBy.dir == "desc"))) {
60605                        var newClass = (oNewSortedBy.dir == "desc") ?
60606                                DT.CLASS_DESC :
60607                                DT.CLASS_ASC;
60608                        Dom.addClass(elNewTh, newClass);
60609                    }
60610                    else {
60611                         var sortClass = oNewSortedBy.dir || DT.CLASS_ASC;
60612                         Dom.addClass(elNewTh, sortClass);
60613                    }
60614                    this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy);
60615                    if (oNewSortedBy.dir == DT.CLASS_ASC)
60616                    {
60617                       oNewColumn.getThEl().setAttribute("aria-sort","ascending");
60618                    }
60619                    else
60620                    {
60621                       oNewColumn.getThEl().setAttribute("aria-sort","descending");
60622                    }
60623                }
60624            }
60625          
60626            if(this._elTbody) {
60627                // Update TBODY UI
60628                this._elTbody.style.display = "none";
60629                var allRows = this._elTbody.rows,
60630                    allCells;
60631                for(var i=allRows.length-1; i>-1; i--) {
60632                    allCells = allRows[i].childNodes;
60633                    if(allCells[nOldColumnKeyIndex]) {
60634                        Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir);
60635                    }
60636                    if(allCells[nNewColumnKeyIndex]) {
60637                        Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir);
60638                    }
60639                }
60640                this._elTbody.style.display = "";
60641            }
60642                
60643            this._clearTrTemplateEl();
60644        }
60645    });
60646    
60647    /**
60648    * @attribute paginator
60649    * @description An instance of YAHOO.widget.Paginator.
60650    * @default null
60651    * @type {Object|YAHOO.widget.Paginator}
60652    */
60653    this.setAttributeConfig("paginator", {
60654        value : null,
60655        validator : function (val) {
60656            return val === null || val instanceof widget.Paginator;
60657        },
60658        method : function () { this._updatePaginator.apply(this,arguments); }
60659    });
60660
60661    /**
60662    * @attribute caption
60663    * @description Value for the CAPTION element. String values are treated as
60664    * markup and inserted into the DOM with innerHTML. NB: Not supported in
60665    * ScrollingDataTable.    
60666    * @type HTML
60667    */
60668    this.setAttributeConfig("caption", {
60669        value: null,
60670        validator: lang.isString,
60671        method: function(sCaption) {
60672            this._initCaptionEl(sCaption);
60673        }
60674    });
60675
60676    /**
60677    * @attribute draggableColumns
60678    * @description True if Columns are draggable to reorder, false otherwise.
60679    * The Drag & Drop Utility is required to enable this feature. Only top-level
60680    * and non-nested Columns are draggable. Write once.
60681    * @default false
60682    * @type Boolean
60683    */
60684    this.setAttributeConfig("draggableColumns", {
60685        value: false,
60686        validator: lang.isBoolean,
60687        method: function(oParam) {
60688            if(this._elThead) {
60689                if(oParam) {
60690                    this._initDraggableColumns();
60691                }
60692                else {
60693                    this._destroyDraggableColumns();
60694                }
60695            }
60696        }
60697    });
60698
60699    /**
60700    * @attribute renderLoopSize      
60701    * @description A value greater than 0 enables DOM rendering of rows to be
60702    * executed from a non-blocking timeout queue and sets how many rows to be
60703    * rendered per timeout. Recommended for very large data sets.     
60704    * @type Number      
60705    * @default 0      
60706    */      
60707     this.setAttributeConfig("renderLoopSize", {
60708         value: 0,
60709         validator: lang.isNumber
60710     });
60711
60712    /**
60713    * @attribute sortFunction
60714    * @description Default Column sort function, receives the following args:
60715    *    <dl>
60716    *      <dt>a {Object}</dt>
60717    *      <dd>First sort argument.</dd>
60718    *      <dt>b {Object}</dt>
60719    *      <dd>Second sort argument.</dd>
60720
60721    *      <dt>desc {Boolean}</dt>
60722    *      <dd>True if sort direction is descending, false if
60723    * sort direction is ascending.</dd>
60724    *      <dt>field {String}</dt>
60725    *      <dd>The field to sort by, from sortOptions.field</dd>
60726    *   </dl>
60727    * @type function
60728    */
60729    this.setAttributeConfig("sortFunction", {
60730        value: function(a, b, desc, field) {
60731            var compare = YAHOO.util.Sort.compare,
60732                sorted = compare(a.getData(field),b.getData(field), desc);
60733            if(sorted === 0) {
60734                return compare(a.getCount(),b.getCount(), desc); // Bug 1932978
60735            }
60736            else {
60737                return sorted;
60738            }
60739        }
60740    });
60741
60742    /**
60743    * @attribute formatRow
60744    * @description A function that accepts a TR element and its associated Record
60745    * for custom formatting. The function must return TRUE in order to automatically
60746    * continue formatting of child TD elements, else TD elements will not be
60747    * automatically formatted.
60748    * @type function
60749    * @default null
60750    */
60751    this.setAttributeConfig("formatRow", {
60752        value: null,
60753        validator: lang.isFunction
60754    });
60755
60756    /**
60757    * @attribute generateRequest
60758    * @description A function that converts an object literal of desired DataTable
60759    * states into a request value which is then passed to the DataSource's
60760    * sendRequest method in order to retrieve data for those states. This
60761    * function is passed an object literal of state data and a reference to the
60762    * DataTable instance:
60763    *     
60764    * <dl>
60765    *   <dt>pagination<dt>
60766    *   <dd>        
60767    *         <dt>offsetRecord</dt>
60768    *         <dd>{Number} Index of the first Record of the desired page</dd>
60769    *         <dt>rowsPerPage</dt>
60770    *         <dd>{Number} Number of rows per page</dd>
60771    *   </dd>
60772    *   <dt>sortedBy</dt>
60773    *   <dd>                
60774    *         <dt>key</dt>
60775    *         <dd>{String} Key of sorted Column</dd>
60776    *         <dt>dir</dt>
60777    *         <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
60778    *   </dd>
60779    *   <dt>self</dt>
60780    *   <dd>The DataTable instance</dd>
60781    * </dl>
60782    * 
60783    * and by default returns a String of syntax:
60784    * "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}"
60785    * @type function
60786    * @default HTMLFunction
60787    */
60788    this.setAttributeConfig("generateRequest", {
60789        value: function(oState, oSelf) {
60790            // Set defaults
60791            oState = oState || {pagination:null, sortedBy:null};
60792            var sort = encodeURIComponent((oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey());
60793            var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc";
60794            var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
60795            var results = (oState.pagination) ? oState.pagination.rowsPerPage : null;
60796            
60797            // Build the request
60798            return  "sort=" + sort +
60799                    "&dir=" + dir +
60800                    "&startIndex=" + startIndex +
60801                    ((results !== null) ? "&results=" + results : "");
60802        },
60803        validator: lang.isFunction
60804    });
60805
60806    /**
60807    * @attribute initialRequest
60808    * @description Defines the initial request that gets sent to the DataSource
60809    * during initialization. Value is ignored if initialLoad is set to any value
60810    * other than true.    
60811    * @type MIXED
60812    * @default null
60813    */
60814    this.setAttributeConfig("initialRequest", {
60815        value: null
60816    });
60817
60818    /**
60819    * @attribute initialLoad
60820    * @description Determines whether or not to load data at instantiation. By
60821    * default, will trigger a sendRequest() to the DataSource and pass in the
60822    * request defined by initialRequest. If set to false, data will not load
60823    * at instantiation. Alternatively, implementers who wish to work with a 
60824    * custom payload may pass in an object literal with the following values:
60825    *     
60826    *    <dl>
60827    *      <dt>request (MIXED)</dt>
60828    *      <dd>Request value.</dd>
60829    *
60830    *      <dt>argument (MIXED)</dt>
60831    *      <dd>Custom data that will be passed through to the callback function.</dd>
60832    *    </dl>
60833    *
60834    *                    
60835    * @type Boolean | Object
60836    * @default true
60837    */
60838    this.setAttributeConfig("initialLoad", {
60839        value: true
60840    });
60841    
60842    /**
60843    * @attribute dynamicData
60844    * @description If true, sorting and pagination are relegated to the DataSource
60845    * for handling, using the request returned by the "generateRequest" function.
60846    * Each new DataSource response blows away all previous Records. False by default, so 
60847    * sorting and pagination will be handled directly on the client side, without
60848    * causing any new requests for data from the DataSource.
60849    * @type Boolean
60850    * @default false
60851    */
60852    this.setAttributeConfig("dynamicData", {
60853        value: false,
60854        validator: lang.isBoolean
60855    });
60856
60857    /**
60858     * @attribute MSG_EMPTY
60859     * @description Message to display if DataTable has no data. String
60860     * values are treated as markup and inserted into the DOM with innerHTML.
60861     * @type HTML
60862     * @default "No records found."
60863     */
60864     this.setAttributeConfig("MSG_EMPTY", {
60865         value: "No records found.",
60866         validator: lang.isString
60867     });      
60868
60869    /**
60870     * @attribute MSG_LOADING
60871     * @description Message to display while DataTable is loading data. String
60872     * values are treated as markup and inserted into the DOM with innerHTML.
60873     * @type HTML
60874     * @default "Loading..."
60875     */      
60876     this.setAttributeConfig("MSG_LOADING", {
60877         value: "Loading...",
60878         validator: lang.isString
60879     });      
60880
60881    /**
60882     * @attribute MSG_ERROR
60883     * @description Message to display while DataTable has data error. String
60884     * values are treated as markup and inserted into the DOM with innerHTML.
60885     * @type HTML
60886     * @default "Data error."
60887     */      
60888     this.setAttributeConfig("MSG_ERROR", {
60889         value: "Data error.",
60890         validator: lang.isString
60891     });
60892
60893    /**
60894     * @attribute MSG_SORTASC
60895     * @description Message to display in tooltip to sort Column in ascending
60896     * order. String values are treated as markup and inserted into the DOM as
60897     * innerHTML.
60898     * @type HTML
60899     * @default "Click to sort ascending"
60900     */      
60901     this.setAttributeConfig("MSG_SORTASC", {      
60902         value: "Click to sort ascending",      
60903         validator: lang.isString,
60904         method: function(sParam) {
60905            if(this._elThead) {
60906                for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
60907                    if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) {
60908                        allKeys[i]._elThLabel.firstChild.title = sParam;
60909                    }
60910                }
60911            }      
60912         }
60913     });
60914
60915    /**
60916     * @attribute MSG_SORTDESC
60917     * @description Message to display in tooltip to sort Column in descending
60918     * order. String values are treated as markup and inserted into the DOM as
60919     * innerHTML.
60920     * @type HTML
60921     * @default "Click to sort descending"
60922     */      
60923     this.setAttributeConfig("MSG_SORTDESC", {      
60924         value: "Click to sort descending",      
60925         validator: lang.isString,
60926         method: function(sParam) {
60927            if(this._elThead) {
60928                for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
60929                    if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) {
60930                        allKeys[i]._elThLabel.firstChild.title = sParam;
60931                    }
60932                }
60933            }               
60934         }
60935     });
60936     
60937    /**
60938     * @attribute currencySymbol
60939     * @deprecated Use currencyOptions.
60940     */
60941    this.setAttributeConfig("currencySymbol", {
60942        value: "$",
60943        validator: lang.isString
60944    });
60945    
60946    /**
60947     * Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
60948     * @attribute currencyOptions
60949     * @type Object
60950     * @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","}
60951     */
60952    this.setAttributeConfig("currencyOptions", {
60953        value: {
60954            prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol
60955            decimalPlaces:2,
60956            decimalSeparator:".",
60957            thousandsSeparator:","
60958        }
60959    });
60960    
60961    /**
60962     * Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
60963     * @attribute dateOptions
60964     * @type Object
60965     * @default {format:"%m/%d/%Y", locale:"en"}
60966     */
60967    this.setAttributeConfig("dateOptions", {
60968        value: {format:"%m/%d/%Y", locale:"en"}
60969    });
60970    
60971    /**
60972     * Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter.
60973     * @attribute numberOptions
60974     * @type Object
60975     * @default {decimalPlaces:0, thousandsSeparator:","}
60976     */
60977    this.setAttributeConfig("numberOptions", {
60978        value: {
60979            decimalPlaces:0,
60980            thousandsSeparator:","
60981        }
60982    });
60983
60984},
60985
60986/////////////////////////////////////////////////////////////////////////////
60987//
60988// Private member variables
60989//
60990/////////////////////////////////////////////////////////////////////////////
60991
60992/**
60993 * True if instance is initialized, so as to fire the initEvent after render.
60994 *
60995 * @property _bInit
60996 * @type Boolean
60997 * @default true
60998 * @private
60999 */
61000_bInit : true,
61001
61002/**
61003 * Index assigned to instance.
61004 *
61005 * @property _nIndex
61006 * @type Number
61007 * @private
61008 */
61009_nIndex : null,
61010
61011/**
61012 * Counter for IDs assigned to TR elements.
61013 *
61014 * @property _nTrCount
61015 * @type Number
61016 * @private
61017 */
61018_nTrCount : 0,
61019
61020/**
61021 * Counter for IDs assigned to TD elements.
61022 *
61023 * @property _nTdCount
61024 * @type Number
61025 * @private
61026 */
61027_nTdCount : 0,
61028
61029/**
61030 * Unique id assigned to instance "yui-dtN", useful prefix for generating unique
61031 * DOM ID strings and log messages.
61032 *
61033 * @property _sId
61034 * @type String
61035 * @private
61036 */
61037_sId : null,
61038
61039/**
61040 * Render chain.
61041 *
61042 * @property _oChainRender
61043 * @type YAHOO.util.Chain
61044 * @private
61045 */
61046_oChainRender : null,
61047
61048/**
61049 * DOM reference to the container element for the DataTable instance into which
61050 * all other elements get created.
61051 *
61052 * @property _elContainer
61053 * @type HTMLElement
61054 * @private
61055 */
61056_elContainer : null,
61057
61058/**
61059 * DOM reference to the mask element for the DataTable instance which disables it.
61060 *
61061 * @property _elMask
61062 * @type HTMLElement
61063 * @private
61064 */
61065_elMask : null,
61066
61067/**
61068 * DOM reference to the TABLE element for the DataTable instance.
61069 *
61070 * @property _elTable
61071 * @type HTMLElement
61072 * @private
61073 */
61074_elTable : null,
61075
61076/**
61077 * DOM reference to the CAPTION element for the DataTable instance.
61078 *
61079 * @property _elCaption
61080 * @type HTMLElement
61081 * @private
61082 */
61083_elCaption : null,
61084
61085/**
61086 * DOM reference to the COLGROUP element for the DataTable instance.
61087 *
61088 * @property _elColgroup
61089 * @type HTMLElement
61090 * @private
61091 */
61092_elColgroup : null,
61093
61094/**
61095 * DOM reference to the THEAD element for the DataTable instance.
61096 *
61097 * @property _elThead
61098 * @type HTMLElement
61099 * @private
61100 */
61101_elThead : null,
61102
61103/**
61104 * DOM reference to the primary TBODY element for the DataTable instance.
61105 *
61106 * @property _elTbody
61107 * @type HTMLElement
61108 * @private
61109 */
61110_elTbody : null,
61111
61112/**
61113 * DOM reference to the secondary TBODY element used to display DataTable messages.
61114 *
61115 * @property _elMsgTbody
61116 * @type HTMLElement
61117 * @private
61118 */
61119_elMsgTbody : null,
61120
61121/**
61122 * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
61123 *
61124 * @property _elMsgTr
61125 * @type HTMLElement
61126 * @private
61127 */
61128_elMsgTr : null,
61129
61130/**
61131 * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
61132 *
61133 * @property _elMsgTd
61134 * @type HTMLElement
61135 * @private
61136 */
61137_elMsgTd : null,
61138
61139/**
61140 * Element reference to shared Column drag target.
61141 *
61142 * @property _elColumnDragTarget
61143 * @type HTMLElement
61144 * @private
61145 */
61146_elColumnDragTarget : null,
61147
61148/**
61149 * Element reference to shared Column resizer proxy.
61150 *
61151 * @property _elColumnResizerProxy
61152 * @type HTMLElement
61153 * @private
61154 */
61155_elColumnResizerProxy : null,
61156
61157/**
61158 * DataSource instance for the DataTable instance.
61159 *
61160 * @property _oDataSource
61161 * @type YAHOO.util.DataSource
61162 * @private
61163 */
61164_oDataSource : null,
61165
61166/**
61167 * ColumnSet instance for the DataTable instance.
61168 *
61169 * @property _oColumnSet
61170 * @type YAHOO.widget.ColumnSet
61171 * @private
61172 */
61173_oColumnSet : null,
61174
61175/**
61176 * RecordSet instance for the DataTable instance.
61177 *
61178 * @property _oRecordSet
61179 * @type YAHOO.widget.RecordSet
61180 * @private
61181 */
61182_oRecordSet : null,
61183
61184/**
61185 * The active CellEditor instance for the DataTable instance.
61186 *
61187 * @property _oCellEditor
61188 * @type YAHOO.widget.CellEditor
61189 * @private
61190 */
61191_oCellEditor : null,
61192
61193/**
61194 * ID string of first TR element of the current DataTable page.
61195 *
61196 * @property _sFirstTrId
61197 * @type String
61198 * @private
61199 */
61200_sFirstTrId : null,
61201
61202/**
61203 * ID string of the last TR element of the current DataTable page.
61204 *
61205 * @property _sLastTrId
61206 * @type String
61207 * @private
61208 */
61209_sLastTrId : null,
61210
61211/**
61212 * Template row to create all new rows from.
61213 * @property _elTrTemplate
61214 * @type {HTMLElement}
61215 * @private 
61216 */
61217_elTrTemplate : null,
61218
61219/**
61220 * Sparse array of custom functions to set column widths for browsers that don't
61221 * support dynamic CSS rules.  Functions are added at the index representing
61222 * the number of rows they update.
61223 *
61224 * @property _aDynFunctions
61225 * @type Array
61226 * @private
61227 */
61228_aDynFunctions : [],
61229
61230/**
61231 * Disabled state.
61232 *
61233 * @property _disabled
61234 * @type Boolean
61235 * @private
61236 */
61237_disabled : false,
61238
61239
61240
61241
61242
61243
61244
61245
61246
61247
61248
61249
61250
61251
61252
61253
61254
61255
61256
61257
61258
61259
61260
61261
61262
61263
61264
61265
61266/////////////////////////////////////////////////////////////////////////////
61267//
61268// Private methods
61269//
61270/////////////////////////////////////////////////////////////////////////////
61271
61272/**
61273 * Clears browser text selection. Useful to call on rowSelectEvent or
61274 * cellSelectEvent to prevent clicks or dblclicks from selecting text in the
61275 * browser.
61276 *
61277 * @method clearTextSelection
61278 */
61279clearTextSelection : function() {
61280    var sel;
61281    if(window.getSelection) {
61282        sel = window.getSelection();
61283    }
61284    else if(document.getSelection) {
61285        sel = document.getSelection();
61286    }
61287    else if(document.selection) {
61288        sel = document.selection;
61289    }
61290    if(sel) {
61291        if(sel.empty) {
61292            sel.empty();
61293        }
61294        else if (sel.removeAllRanges) {
61295            sel.removeAllRanges();
61296        }
61297        else if(sel.collapse) {
61298            sel.collapse();
61299        }
61300    }
61301},
61302
61303/**
61304 * Sets focus on the given element.
61305 *
61306 * @method _focusEl
61307 * @param el {HTMLElement} Element.
61308 * @private
61309 */
61310_focusEl : function(el) {
61311    el = el || this._elTbody;
61312    // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
61313    // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
61314    // strange unexpected things as the user clicks on buttons and other controls.
61315    setTimeout(function() {
61316        try {
61317            el.focus();
61318        }
61319        catch(e) {
61320        }
61321    },0);
61322},
61323
61324/**
61325 * Forces Gecko repaint.
61326 *
61327 * @method _repaintGecko
61328 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
61329 * @private
61330 */
61331_repaintGecko : (ua.gecko) ? 
61332    function(el) {
61333        el = el || this._elContainer;
61334        var parent = el.parentNode;
61335        var nextSibling = el.nextSibling;
61336        parent.insertBefore(parent.removeChild(el), nextSibling);
61337    } : function() {},
61338
61339/**
61340 * Forces Opera repaint.
61341 *
61342 * @method _repaintOpera
61343 * @private 
61344 */
61345_repaintOpera : (ua.opera) ? 
61346    function() {
61347        if(ua.opera) {
61348            document.documentElement.className += " ";
61349            document.documentElement.className = YAHOO.lang.trim(document.documentElement.className);
61350        }
61351    } : function() {} ,
61352
61353/**
61354 * Forces Webkit repaint.
61355 *
61356 * @method _repaintWebkit
61357 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
61358 * @private
61359 */
61360_repaintWebkit : (ua.webkit) ? 
61361    function(el) {
61362        el = el || this._elContainer;
61363        var parent = el.parentNode;
61364        var nextSibling = el.nextSibling;
61365        parent.insertBefore(parent.removeChild(el), nextSibling);
61366    } : function() {},
61367
61368
61369
61370
61371
61372
61373
61374
61375
61376
61377
61378
61379
61380
61381
61382
61383
61384
61385
61386
61387
61388
61389// INIT FUNCTIONS
61390
61391/**
61392 * Initializes object literal of config values.
61393 *
61394 * @method _initConfigs
61395 * @param oConfig {Object} Object literal of config values.
61396 * @private
61397 */
61398_initConfigs : function(oConfigs) {
61399    if(!oConfigs || !lang.isObject(oConfigs)) {
61400        oConfigs = {};
61401    }
61402    this.configs = oConfigs;
61403},
61404
61405/**
61406 * Initializes ColumnSet.
61407 *
61408 * @method _initColumnSet
61409 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
61410 * @private
61411 */
61412_initColumnSet : function(aColumnDefs) {
61413    var oColumn, i, len;
61414    
61415    if(this._oColumnSet) {
61416        // First clear _oDynStyles for existing ColumnSet and
61417        // uregister CellEditor Custom Events
61418        for(i=0, len=this._oColumnSet.keys.length; i<len; i++) {
61419            oColumn = this._oColumnSet.keys[i];
61420            DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined;
61421            if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility
61422                oColumn.editor.unsubscribeAll();
61423            }
61424        }
61425        
61426        this._oColumnSet = null;
61427        this._clearTrTemplateEl();
61428    }
61429    
61430    if(lang.isArray(aColumnDefs)) {
61431        this._oColumnSet =  new YAHOO.widget.ColumnSet(aColumnDefs);
61432    }
61433    // Backward compatibility
61434    else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
61435        this._oColumnSet =  aColumnDefs;
61436        YAHOO.log("DataTable's constructor now requires an array" +
61437        " of object literal Column definitions instead of a ColumnSet instance",
61438        "warn", this.toString());
61439    }
61440
61441    // Register CellEditor Custom Events
61442    var allKeys = this._oColumnSet.keys;
61443    for(i=0, len=allKeys.length; i<len; i++) {
61444        oColumn = allKeys[i];
61445        if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility
61446            oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
61447            oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
61448            oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
61449            oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
61450            oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
61451            oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
61452            oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
61453            oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
61454        }
61455    }
61456},
61457
61458/**
61459 * Initializes DataSource.
61460 *
61461 * @method _initDataSource
61462 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
61463 * @private
61464 */
61465_initDataSource : function(oDataSource) {
61466    this._oDataSource = null;
61467    if(oDataSource && (lang.isFunction(oDataSource.sendRequest))) {
61468        this._oDataSource = oDataSource;
61469    }
61470    // Backward compatibility
61471    else {
61472        var tmpTable = null;
61473        var tmpContainer = this._elContainer;
61474        var i=0;
61475        //TODO: this will break if re-initing DS at runtime for SDT
61476        // Peek in container child nodes to see if TABLE already exists
61477        if(tmpContainer.hasChildNodes()) {
61478            var tmpChildren = tmpContainer.childNodes;
61479            for(i=0; i<tmpChildren.length; i++) {
61480                if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") {
61481                    tmpTable = tmpChildren[i];
61482                    break;
61483                }
61484            }
61485            if(tmpTable) {
61486                var tmpFieldsArray = [];
61487                for(; i<this._oColumnSet.keys.length; i++) {
61488                    tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
61489                }
61490
61491                this._oDataSource = new DS(tmpTable);
61492                this._oDataSource.responseType = DS.TYPE_HTMLTABLE;
61493                this._oDataSource.responseSchema = {fields: tmpFieldsArray};
61494                YAHOO.log("Null DataSource for progressive enhancement from" +
61495                " markup has been deprecated", "warn", this.toString());
61496            }
61497        }
61498    }
61499},
61500
61501/**
61502 * Initializes RecordSet.
61503 *
61504 * @method _initRecordSet
61505 * @private
61506 */
61507_initRecordSet : function() {
61508    if(this._oRecordSet) {
61509        this._oRecordSet.reset();
61510    }
61511    else {
61512        this._oRecordSet = new YAHOO.widget.RecordSet();
61513    }
61514},
61515
61516/**
61517 * Initializes DOM elements.
61518 *
61519 * @method _initDomElements
61520 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
61521 * return {Boolean} False in case of error, otherwise true 
61522 * @private
61523 */
61524_initDomElements : function(elContainer) {
61525    // Outer container
61526    this._initContainerEl(elContainer);
61527    // TABLE
61528    this._initTableEl(this._elContainer);
61529    // COLGROUP
61530    this._initColgroupEl(this._elTable);
61531    // THEAD
61532    this._initTheadEl(this._elTable);
61533    
61534    // Message TBODY
61535    this._initMsgTbodyEl(this._elTable);  
61536
61537    // Primary TBODY
61538    this._initTbodyEl(this._elTable);
61539
61540    if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody) {
61541        return false;
61542    }
61543    else {
61544        return true;
61545    }
61546},
61547
61548/**
61549 * Destroy's the DataTable outer container element, if available.
61550 *
61551 * @method _destroyContainerEl
61552 * @param elContainer {HTMLElement} Reference to the container element. 
61553 * @private
61554 */
61555_destroyContainerEl : function(elContainer) {
61556        var columns = this._oColumnSet.keys,
61557        elements, i;
61558
61559        Dom.removeClass(elContainer, DT.CLASS_DATATABLE);
61560
61561    // Bug 2528783
61562    Ev.purgeElement( elContainer );
61563    Ev.purgeElement( this._elThead, true ); // recursive to get resize handles
61564    Ev.purgeElement( this._elTbody );
61565    Ev.purgeElement( this._elMsgTbody );
61566
61567    // because change doesn't bubble, each select (via formatDropdown) gets
61568    // its own subscription
61569    elements = elContainer.getElementsByTagName( 'select' );
61570
61571    if ( elements.length ) {
61572        Ev.detachListener( elements, 'change' );
61573    }
61574
61575    for ( i = columns.length - 1; i >= 0; --i ) {
61576        if ( columns[i].editor ) {
61577            Ev.purgeElement( columns[i].editor._elContainer );
61578        }
61579    }
61580
61581    elContainer.innerHTML = "";
61582    
61583    this._elContainer = null;
61584    this._elColgroup = null;
61585    this._elThead = null;
61586    this._elTbody = null;
61587},
61588
61589/**
61590 * Initializes the DataTable outer container element, including a mask.
61591 *
61592 * @method _initContainerEl
61593 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
61594 * @private
61595 */
61596_initContainerEl : function(elContainer) {
61597    // Validate container
61598    elContainer = Dom.get(elContainer);
61599    
61600    if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) {
61601        // Destroy previous
61602        this._destroyContainerEl(elContainer);
61603
61604        Dom.addClass(elContainer, DT.CLASS_DATATABLE);
61605        Ev.addListener(elContainer, "focus", this._onTableFocus, this);
61606        Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this);
61607        this._elContainer = elContainer;
61608        
61609        var elMask = document.createElement("div");
61610        elMask.className = DT.CLASS_MASK;
61611        elMask.style.display = "none";
61612        this._elMask = elContainer.appendChild(elMask);
61613    }
61614},
61615
61616/**
61617 * Destroy's the DataTable TABLE element, if available.
61618 *
61619 * @method _destroyTableEl
61620 * @private
61621 */
61622_destroyTableEl : function() {
61623    var elTable = this._elTable;
61624    if(elTable) {
61625        Ev.purgeElement(elTable, true);
61626        elTable.parentNode.removeChild(elTable);
61627        this._elCaption = null;
61628        this._elColgroup = null;
61629        this._elThead = null;
61630        this._elTbody = null;
61631    }
61632},
61633
61634/**
61635 * Creates HTML markup CAPTION element.
61636 *
61637 * @method _initCaptionEl
61638 * @param sCaption {HTML} Caption value. String values are treated as markup and
61639 * inserted into the DOM with innerHTML.
61640 * @private
61641 */
61642_initCaptionEl : function(sCaption) {
61643    if(this._elTable && sCaption) {
61644        // Create CAPTION element
61645        if(!this._elCaption) { 
61646            this._elCaption = this._elTable.createCaption();
61647        }
61648        // Set CAPTION value
61649        this._elCaption.innerHTML = sCaption;
61650    }
61651    else if(this._elCaption) {
61652        this._elCaption.parentNode.removeChild(this._elCaption);
61653    }
61654},
61655
61656/**
61657 * Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer
61658 * container element.
61659 *
61660 * @method _initTableEl
61661 * @param elContainer {HTMLElement} Container element into which to create TABLE.
61662 * @private
61663 */
61664_initTableEl : function(elContainer) {
61665    if(elContainer) {
61666        // Destroy previous
61667        this._destroyTableEl();
61668    
61669        // Create TABLE
61670        this._elTable = elContainer.appendChild(document.createElement("table"));  
61671         
61672        // Set SUMMARY attribute
61673        this._elTable.summary = this.get("summary");
61674        
61675        // Create CAPTION element
61676        if(this.get("caption")) {
61677            this._initCaptionEl(this.get("caption"));
61678        }
61679
61680        // Set up mouseover/mouseout events via mouseenter/mouseleave delegation
61681        Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "thead ."+DT.CLASS_LABEL, this);
61682        Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "thead ."+DT.CLASS_LABEL, this);
61683        Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-data>tr>td", this);
61684        Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-data>tr>td", this);
61685        Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-message>tr>td", this);
61686        Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-message>tr>td", this);
61687    }
61688},
61689
61690/**
61691 * Destroy's the DataTable COLGROUP element, if available.
61692 *
61693 * @method _destroyColgroupEl
61694 * @private
61695 */
61696_destroyColgroupEl : function() {
61697    var elColgroup = this._elColgroup;
61698    if(elColgroup) {
61699        var elTable = elColgroup.parentNode;
61700        Ev.purgeElement(elColgroup, true);
61701        elTable.removeChild(elColgroup);
61702        this._elColgroup = null;
61703    }
61704},
61705
61706/**
61707 * Initializes COLGROUP and COL elements for managing minWidth.
61708 *
61709 * @method _initColgroupEl
61710 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
61711 * @private
61712 */
61713_initColgroupEl : function(elTable) {
61714    if(elTable) {
61715        // Destroy previous
61716        this._destroyColgroupEl();
61717
61718        // Add COLs to DOCUMENT FRAGMENT
61719        var allCols = this._aColIds || [],
61720            allKeys = this._oColumnSet.keys,
61721            i = 0, len = allCols.length,
61722            elCol, oColumn,
61723            elFragment = document.createDocumentFragment(),
61724            elColTemplate = document.createElement("col");
61725    
61726        for(i=0,len=allKeys.length; i<len; i++) {
61727            oColumn = allKeys[i];
61728            elCol = elFragment.appendChild(elColTemplate.cloneNode(false));
61729        }
61730    
61731        // Create COLGROUP
61732        var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild);
61733        elColgroup.appendChild(elFragment);
61734        this._elColgroup = elColgroup;
61735    }
61736},
61737
61738/**
61739 * Adds a COL element to COLGROUP at given index.
61740 *
61741 * @method _insertColgroupColEl
61742 * @param index {Number} Index of new COL element.
61743 * @private
61744 */
61745_insertColgroupColEl : function(index) {
61746    if(lang.isNumber(index)&& this._elColgroup) {
61747        var nextSibling = this._elColgroup.childNodes[index] || null;
61748        this._elColgroup.insertBefore(document.createElement("col"), nextSibling);
61749    }
61750},
61751
61752/**
61753 * Removes a COL element to COLGROUP at given index.
61754 *
61755 * @method _removeColgroupColEl
61756 * @param index {Number} Index of removed COL element.
61757 * @private
61758 */
61759_removeColgroupColEl : function(index) {
61760    if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) {
61761        this._elColgroup.removeChild(this._elColgroup.childNodes[index]);
61762    }
61763},
61764
61765/**
61766 * Reorders a COL element from old index(es) to new index.
61767 *
61768 * @method _reorderColgroupColEl
61769 * @param aKeyIndexes {Number[]} Array of indexes of removed COL element.
61770 * @param newIndex {Number} New index. 
61771 * @private
61772 */
61773_reorderColgroupColEl : function(aKeyIndexes, newIndex) {
61774    if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > aKeyIndexes[aKeyIndexes.length-1])) {
61775        var i,
61776            tmpCols = [];
61777        // Remove COL
61778        for(i=aKeyIndexes.length-1; i>-1; i--) {
61779            tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]]));
61780        }
61781        // Insert COL
61782        var nextSibling = this._elColgroup.childNodes[newIndex] || null;
61783        for(i=tmpCols.length-1; i>-1; i--) {
61784            this._elColgroup.insertBefore(tmpCols[i], nextSibling);
61785        }
61786    }
61787},
61788
61789/**
61790 * Destroy's the DataTable THEAD element, if available.
61791 *
61792 * @method _destroyTheadEl
61793 * @private
61794 */
61795_destroyTheadEl : function() {
61796    var elThead = this._elThead;
61797    if(elThead) {
61798        var elTable = elThead.parentNode;
61799        Ev.purgeElement(elThead, true);
61800        this._destroyColumnHelpers();
61801        elTable.removeChild(elThead);
61802        this._elThead = null;
61803    }
61804},
61805
61806/**
61807 * Initializes THEAD element.
61808 *
61809 * @method _initTheadEl
61810 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
61811 * @param {HTMLElement} Initialized THEAD element. 
61812 * @private
61813 */
61814_initTheadEl : function(elTable) {
61815    elTable = elTable || this._elTable;
61816    
61817    if(elTable) {
61818        // Destroy previous
61819        this._destroyTheadEl();
61820    
61821        //TODO: append to DOM later for performance
61822        var elThead = (this._elColgroup) ?
61823            elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) :
61824            elTable.appendChild(document.createElement("thead"));
61825    
61826        // Set up DOM events for THEAD
61827        Ev.addListener(elThead, "focus", this._onTheadFocus, this);
61828        Ev.addListener(elThead, "keydown", this._onTheadKeydown, this);
61829        Ev.addListener(elThead, "mousedown", this._onTableMousedown, this);
61830        Ev.addListener(elThead, "mouseup", this._onTableMouseup, this);
61831        Ev.addListener(elThead, "click", this._onTheadClick, this);
61832        
61833        // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
61834        // delegation at the TABLE level
61835
61836        // Since we can't listen for click and dblclick on the same element...
61837        // Attach separately to THEAD and TBODY
61838        ///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this);
61839        
61840       var oColumnSet = this._oColumnSet,
61841            oColumn, i,j, l;
61842        
61843        // Add TRs to the THEAD
61844        var colTree = oColumnSet.tree;
61845        var elTh;
61846        for(i=0; i<colTree.length; i++) {
61847            var elTheadTr = elThead.appendChild(document.createElement("tr"));
61848    
61849            // ...and create TH cells
61850            for(j=0; j<colTree[i].length; j++) {
61851                oColumn = colTree[i][j];
61852                elTh = elTheadTr.appendChild(document.createElement("th"));
61853                this._initThEl(elTh,oColumn);
61854            }
61855    
61856                // Set FIRST/LAST on THEAD rows
61857                if(i === 0) {
61858                    Dom.addClass(elTheadTr, DT.CLASS_FIRST);
61859                }
61860                if(i === (colTree.length-1)) {
61861                    Dom.addClass(elTheadTr, DT.CLASS_LAST);
61862                }
61863
61864        }
61865
61866        // Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array
61867        var aFirstHeaders = oColumnSet.headers[0] || [];
61868        for(i=0; i<aFirstHeaders.length; i++) {
61869            Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST);
61870        }
61871        var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || [];
61872        for(i=0; i<aLastHeaders.length; i++) {
61873            Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST);
61874        }
61875        
61876        YAHOO.log("TH cells for " + this._oColumnSet.keys.length + " keys created","info",this.toString());
61877
61878        ///TODO: try _repaintGecko(this._elContainer) instead
61879        // Bug 1806891
61880        if(ua.webkit && ua.webkit < 420) {
61881            var oSelf = this;
61882            setTimeout(function() {
61883                elThead.style.display = "";
61884            },0);
61885            elThead.style.display = 'none';
61886        }
61887        
61888        this._elThead = elThead;
61889        
61890        // Column helpers needs _elThead to exist
61891        this._initColumnHelpers();  
61892    }
61893},
61894
61895/**
61896 * Populates TH element as defined by Column.
61897 *
61898 * @method _initThEl
61899 * @param elTh {HTMLElement} TH element reference.
61900 * @param oColumn {YAHOO.widget.Column} Column object.
61901 * @private
61902 */
61903_initThEl : function(elTh, oColumn) {
61904    elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD
61905    elTh.innerHTML = "";
61906    elTh.rowSpan = oColumn.getRowspan();
61907    elTh.colSpan = oColumn.getColspan();
61908    oColumn._elTh = elTh;
61909    
61910    var elThLiner = elTh.appendChild(document.createElement("div"));
61911    elThLiner.id = elTh.id + "-liner"; // Needed for resizer
61912    elThLiner.className = DT.CLASS_LINER;
61913    oColumn._elThLiner = elThLiner;
61914    
61915    var elThLabel = elThLiner.appendChild(document.createElement("span"));
61916    elThLabel.className = DT.CLASS_LABEL;    
61917
61918    // Assign abbr attribute
61919    if(oColumn.abbr) {
61920        elTh.abbr = oColumn.abbr;
61921    }
61922    // Clear minWidth on hidden Columns
61923    if(oColumn.hidden) {
61924        this._clearMinWidth(oColumn);
61925    }
61926        
61927    elTh.className = this._getColumnClassNames(oColumn);
61928            
61929    // Set Column width...
61930    if(oColumn.width) {
61931        // Validate minWidth
61932        var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
61933                oColumn.minWidth : oColumn.width;
61934        // ...for fallback cases
61935        if(DT._bDynStylesFallback) {
61936            elTh.firstChild.style.overflow = 'hidden';
61937            elTh.firstChild.style.width = nWidth + 'px';        
61938        }
61939        // ...for non fallback cases
61940        else {
61941            this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden');
61942        }
61943    }
61944
61945    this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy"));
61946    if (oColumn.sortable)
61947    {
61948       oColumn.getThEl().setAttribute("aria-sort","none");
61949    }
61950    oColumn._elThLabel = elThLabel;
61951},
61952
61953/**
61954 * Outputs markup into the given TH based on given Column.
61955 *
61956 * @method formatTheadCell
61957 * @param elCellLabel {HTMLElement} The label SPAN element within the TH liner,
61958 * not the liner DIV element.     
61959 * @param oColumn {YAHOO.widget.Column} Column instance.
61960 * @param oSortedBy {Object} Sort state object literal.
61961*/
61962formatTheadCell : function(elCellLabel, oColumn, oSortedBy) {
61963    var sKey = oColumn.getKey();
61964    var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
61965
61966    // Add accessibility link for sortable Columns
61967    if(oColumn.sortable) {
61968        // Calculate the direction
61969        var sSortClass = this.getColumnSortDir(oColumn, oSortedBy);
61970        var bDesc = (sSortClass === DT.CLASS_DESC);
61971
61972        // This is the sorted Column
61973        if(oSortedBy && (oColumn.key === oSortedBy.key)) {
61974            bDesc = !(oSortedBy.dir === DT.CLASS_DESC);
61975        }
61976
61977        // Generate a unique HREF for visited status
61978        var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey();
61979        
61980        // Generate a dynamic TITLE for sort status
61981        var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC");
61982        
61983        // Format the element
61984        elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>";
61985    }
61986    // Just display the label for non-sortable Columns
61987    else {
61988        elCellLabel.innerHTML = sLabel;
61989    }
61990},
61991
61992/**
61993 * Disables DD from top-level Column TH elements.
61994 *
61995 * @method _destroyDraggableColumns
61996 * @private
61997 */
61998_destroyDraggableColumns : function() {
61999    var oColumn, elTh;
62000    for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
62001        oColumn = this._oColumnSet.tree[0][i];
62002        if(oColumn._dd) {
62003            oColumn._dd = oColumn._dd.unreg();
62004            Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE);       
62005        }
62006    }
62007    
62008    // Destroy column drag proxy
62009    this._destroyColumnDragTargetEl();
62010},
62011
62012/**
62013 * Initializes top-level Column TH elements into DD instances.
62014 *
62015 * @method _initDraggableColumns
62016 * @private
62017 */
62018_initDraggableColumns : function() {
62019    this._destroyDraggableColumns();
62020    if(util.DD) {
62021        var oColumn, elTh, elDragTarget;
62022        for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
62023            oColumn = this._oColumnSet.tree[0][i];
62024            elTh = oColumn.getThEl();
62025            Dom.addClass(elTh, DT.CLASS_DRAGGABLE);
62026            elDragTarget = this._initColumnDragTargetEl();
62027            oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget);
62028        }
62029    }
62030    else {
62031        YAHOO.log("Could not find DragDrop for draggable Columns", "warn", this.toString());
62032    }
62033},
62034
62035/**
62036 * Destroys shared Column drag target.
62037 *
62038 * @method _destroyColumnDragTargetEl
62039 * @private
62040 */
62041_destroyColumnDragTargetEl : function() {
62042    if(this._elColumnDragTarget) {
62043        var el = this._elColumnDragTarget;
62044        YAHOO.util.Event.purgeElement(el);
62045        el.parentNode.removeChild(el);
62046        this._elColumnDragTarget = null;
62047    }
62048},
62049
62050/**
62051 * Creates HTML markup for shared Column drag target.
62052 *
62053 * @method _initColumnDragTargetEl
62054 * @return {HTMLElement} Reference to Column drag target.
62055 * @private
62056 */
62057_initColumnDragTargetEl : function() {
62058    if(!this._elColumnDragTarget) {
62059        // Attach Column drag target element as first child of body
62060        var elColumnDragTarget = document.createElement('div');
62061        elColumnDragTarget.id = this.getId() + "-coltarget";
62062        elColumnDragTarget.className = DT.CLASS_COLTARGET;
62063        elColumnDragTarget.style.display = "none";
62064        document.body.insertBefore(elColumnDragTarget, document.body.firstChild);
62065
62066        // Internal tracker of Column drag target
62067        this._elColumnDragTarget = elColumnDragTarget;
62068
62069    }
62070    return this._elColumnDragTarget;
62071},
62072
62073/**
62074 * Disables resizeability on key Column TH elements.
62075 *
62076 * @method _destroyResizeableColumns
62077 * @private
62078 */
62079_destroyResizeableColumns : function() {
62080    var aKeys = this._oColumnSet.keys;
62081    for(var i=0, len=aKeys.length; i<len; i++) {
62082        if(aKeys[i]._ddResizer) {
62083            aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg();
62084            Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE);
62085        }
62086    }
62087
62088    // Destroy resizer proxy
62089    this._destroyColumnResizerProxyEl();
62090},
62091
62092/**
62093 * Initializes resizeability on key Column TH elements.
62094 *
62095 * @method _initResizeableColumns
62096 * @private
62097 */
62098_initResizeableColumns : function() {
62099    this._destroyResizeableColumns();
62100    if(util.DD) {
62101        var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick;
62102        for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) {
62103            oColumn = this._oColumnSet.keys[i];
62104            if(oColumn.resizeable) {
62105                elTh = oColumn.getThEl();
62106                Dom.addClass(elTh, DT.CLASS_RESIZEABLE);
62107                elThLiner = oColumn.getThLinerEl();
62108                
62109                // Bug 1915349: So resizer is as tall as TH when rowspan > 1
62110                // Create a separate resizer liner with position:relative
62111                elThResizerLiner = elTh.appendChild(document.createElement("div"));
62112                elThResizerLiner.className = DT.CLASS_RESIZERLINER;
62113                
62114                // Move TH contents into the new resizer liner
62115                elThResizerLiner.appendChild(elThLiner);
62116                
62117                // Create the resizer
62118                elThResizer = elThResizerLiner.appendChild(document.createElement("div"));
62119                elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer
62120                elThResizer.className = DT.CLASS_RESIZER;
62121                oColumn._elResizer = elThResizer;
62122
62123                // Create the resizer proxy, once per instance
62124                elResizerProxy = this._initColumnResizerProxyEl();
62125                oColumn._ddResizer = new YAHOO.util.ColumnResizer(
62126                        this, oColumn, elTh, elThResizer, elResizerProxy);
62127                cancelClick = function(e) {
62128                    Ev.stopPropagation(e);
62129                };
62130                Ev.addListener(elThResizer,"click",cancelClick);
62131            }
62132        }
62133    }
62134    else {
62135        YAHOO.log("Could not find DragDrop for resizeable Columns", "warn", this.toString());
62136    }
62137},
62138
62139/**
62140 * Destroys shared Column resizer proxy.
62141 *
62142 * @method _destroyColumnResizerProxyEl
62143 * @return {HTMLElement} Reference to Column resizer proxy.
62144 * @private
62145 */
62146_destroyColumnResizerProxyEl : function() {
62147    if(this._elColumnResizerProxy) {
62148        var el = this._elColumnResizerProxy;
62149        YAHOO.util.Event.purgeElement(el);
62150        el.parentNode.removeChild(el);
62151        this._elColumnResizerProxy = null;
62152    }
62153},
62154
62155/**
62156 * Creates HTML markup for shared Column resizer proxy.
62157 *
62158 * @method _initColumnResizerProxyEl
62159 * @return {HTMLElement} Reference to Column resizer proxy.
62160 * @private
62161 */
62162_initColumnResizerProxyEl : function() {
62163    if(!this._elColumnResizerProxy) {
62164        // Attach Column resizer element as first child of body
62165        var elColumnResizerProxy = document.createElement("div");
62166        elColumnResizerProxy.id = this.getId() + "-colresizerproxy"; // Needed for ColumnResizer
62167        elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY;
62168        document.body.insertBefore(elColumnResizerProxy, document.body.firstChild);
62169
62170        // Internal tracker of Column resizer proxy
62171        this._elColumnResizerProxy = elColumnResizerProxy;
62172    }
62173    return this._elColumnResizerProxy;
62174},
62175
62176/**
62177 * Destroys elements associated with Column functionality: ColumnDD and ColumnResizers.
62178 *
62179 * @method _destroyColumnHelpers
62180 * @private
62181 */
62182_destroyColumnHelpers : function() {
62183    this._destroyDraggableColumns();
62184    this._destroyResizeableColumns();
62185},
62186
62187/**
62188 * Initializes elements associated with Column functionality: ColumnDD and ColumnResizers.
62189 *
62190 * @method _initColumnHelpers
62191 * @private
62192 */
62193_initColumnHelpers : function() {
62194    if(this.get("draggableColumns")) {
62195        this._initDraggableColumns();
62196    }
62197    this._initResizeableColumns();
62198},
62199
62200/**
62201 * Destroy's the DataTable TBODY element, if available.
62202 *
62203 * @method _destroyTbodyEl
62204 * @private
62205 */
62206_destroyTbodyEl : function() {
62207    var elTbody = this._elTbody;
62208    if(elTbody) {
62209        var elTable = elTbody.parentNode;
62210        Ev.purgeElement(elTbody, true);
62211        elTable.removeChild(elTbody);
62212        this._elTbody = null;
62213    }
62214},
62215
62216/**
62217 * Initializes TBODY element for data.
62218 *
62219 * @method _initTbodyEl
62220 * @param elTable {HTMLElement} TABLE element into which to create TBODY .
62221 * @private
62222 */
62223_initTbodyEl : function(elTable) {
62224    if(elTable) {
62225        // Destroy previous
62226        this._destroyTbodyEl();
62227        
62228        // Create TBODY
62229        var elTbody = elTable.appendChild(document.createElement("tbody"));
62230        elTbody.tabIndex = 0;
62231        elTbody.className = DT.CLASS_DATA;
62232    
62233        // Set up DOM events for TBODY
62234        Ev.addListener(elTbody, "focus", this._onTbodyFocus, this);
62235        Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this);
62236        Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this);
62237        Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this);
62238        Ev.addListener(elTbody, "click", this._onTbodyClick, this);
62239
62240        // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
62241        // delegation at the TABLE level
62242
62243        // Since we can't listen for click and dblclick on the same element...
62244        // Attach separately to THEAD and TBODY
62245        ///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this);
62246        
62247    
62248        // IE puts focus outline in the wrong place
62249        if(ua.ie) {
62250            elTbody.hideFocus=true;
62251        }
62252
62253        this._elTbody = elTbody;
62254    }
62255},
62256
62257/**
62258 * Destroy's the DataTable message TBODY element, if available.
62259 *
62260 * @method _destroyMsgTbodyEl
62261 * @private
62262 */
62263_destroyMsgTbodyEl : function() {
62264    var elMsgTbody = this._elMsgTbody;
62265    if(elMsgTbody) {
62266        var elTable = elMsgTbody.parentNode;
62267        Ev.purgeElement(elMsgTbody, true);
62268        elTable.removeChild(elMsgTbody);
62269        this._elTbody = null;
62270    }
62271},
62272
62273/**
62274 * Initializes TBODY element for messaging.
62275 *
62276 * @method _initMsgTbodyEl
62277 * @param elTable {HTMLElement} TABLE element into which to create TBODY 
62278 * @private
62279 */
62280_initMsgTbodyEl : function(elTable) {
62281    if(elTable) {
62282        var elMsgTbody = document.createElement("tbody");
62283        elMsgTbody.className = DT.CLASS_MESSAGE;
62284        var elMsgTr = elMsgTbody.appendChild(document.createElement("tr"));
62285        elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
62286        this._elMsgTr = elMsgTr;
62287        var elMsgTd = elMsgTr.appendChild(document.createElement("td"));
62288        elMsgTd.colSpan = this._oColumnSet.keys.length || 1;
62289        elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
62290        this._elMsgTd = elMsgTd;
62291        elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody);
62292        var elMsgLiner = elMsgTd.appendChild(document.createElement("div"));
62293        elMsgLiner.className = DT.CLASS_LINER;
62294        this._elMsgTbody = elMsgTbody;
62295
62296        // Set up DOM events for TBODY
62297        Ev.addListener(elMsgTbody, "focus", this._onTbodyFocus, this);
62298        Ev.addListener(elMsgTbody, "mousedown", this._onTableMousedown, this);
62299        Ev.addListener(elMsgTbody, "mouseup", this._onTableMouseup, this);
62300        Ev.addListener(elMsgTbody, "keydown", this._onTbodyKeydown, this);
62301        Ev.addListener(elMsgTbody, "click", this._onTbodyClick, this);
62302
62303        // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
62304        // delegation at the TABLE level
62305    }
62306},
62307
62308/**
62309 * Initialize internal event listeners
62310 *
62311 * @method _initEvents
62312 * @private
62313 */
62314_initEvents : function () {
62315    // Initialize Column sort
62316    this._initColumnSort();
62317        
62318    // Add the document level click listener
62319    YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
62320
62321    // Paginator integration
62322    this.subscribe("paginatorChange",function () {
62323        this._handlePaginatorChange.apply(this,arguments);
62324    });
62325
62326    this.subscribe("initEvent",function () {
62327        this.renderPaginator();
62328    });
62329
62330    // Initialize CellEditor integration
62331    this._initCellEditing();
62332},
62333
62334/**      
62335  * Initializes Column sorting.      
62336  *      
62337  * @method _initColumnSort      
62338  * @private      
62339  */      
62340_initColumnSort : function() {
62341    this.subscribe("theadCellClickEvent", this.onEventSortColumn);      
62342
62343    // Backward compatibility
62344    var oSortedBy = this.get("sortedBy");
62345    if(oSortedBy) {
62346        if(oSortedBy.dir == "desc") {
62347            this._configs.sortedBy.value.dir = DT.CLASS_DESC;
62348        }
62349        else if(oSortedBy.dir == "asc") {
62350            this._configs.sortedBy.value.dir = DT.CLASS_ASC;
62351        }
62352    }
62353},
62354
62355/**      
62356  * Initializes CellEditor integration.      
62357  *      
62358  * @method _initCellEditing      
62359  * @private      
62360  */      
62361_initCellEditing : function() {
62362    this.subscribe("editorBlurEvent",function () {
62363        this.onEditorBlurEvent.apply(this,arguments);
62364    });
62365    this.subscribe("editorBlockEvent",function () {
62366        this.onEditorBlockEvent.apply(this,arguments);
62367    });
62368    this.subscribe("editorUnblockEvent",function () {
62369        this.onEditorUnblockEvent.apply(this,arguments);
62370    });
62371},
62372
62373
62374
62375
62376
62377
62378
62379
62380
62381
62382
62383
62384
62385
62386
62387
62388
62389
62390
62391
62392
62393
62394
62395
62396
62397
62398
62399
62400
62401
62402
62403
62404
62405// DOM MUTATION FUNCTIONS
62406
62407/**
62408 * Retruns classnames to represent current Column states.
62409 * @method _getColumnClassnames 
62410 * @param oColumn {YAHOO.widget.Column} Column instance.
62411 * @param aAddClasses {String[]} An array of additional classnames to add to the
62412 * return value.  
62413 * @return {String} A String of classnames to be assigned to TH or TD elements
62414 * for given Column.  
62415 * @private 
62416 */
62417_getColumnClassNames : function (oColumn, aAddClasses) {
62418    var allClasses;
62419    
62420    // Add CSS classes
62421    if(lang.isString(oColumn.className)) {
62422        // Single custom class
62423        allClasses = [oColumn.className];
62424    }
62425    else if(lang.isArray(oColumn.className)) {
62426        // Array of custom classes
62427        allClasses = oColumn.className;
62428    }
62429    else {
62430        // no custom classes
62431        allClasses = [];
62432    }
62433    
62434    // Hook for setting width with via dynamic style uses key since ID is too disposable
62435    allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
62436
62437    // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
62438    allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
62439
62440    var isSortedBy = this.get("sortedBy") || {};
62441    // Sorted
62442    if(oColumn.key === isSortedBy.key) {
62443        allClasses[allClasses.length] = isSortedBy.dir || '';
62444    }
62445    // Hidden
62446    if(oColumn.hidden) {
62447        allClasses[allClasses.length] = DT.CLASS_HIDDEN;
62448    }
62449    // Selected
62450    if(oColumn.selected) {
62451        allClasses[allClasses.length] = DT.CLASS_SELECTED;
62452    }
62453    // Sortable
62454    if(oColumn.sortable) {
62455        allClasses[allClasses.length] = DT.CLASS_SORTABLE;
62456    }
62457    // Resizeable
62458    if(oColumn.resizeable) {
62459        allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
62460    }
62461    // Editable
62462    if(oColumn.editor) {
62463        allClasses[allClasses.length] = DT.CLASS_EDITABLE;
62464    }
62465    
62466    // Addtnl classes, including First/Last
62467    if(aAddClasses) {
62468        allClasses = allClasses.concat(aAddClasses);
62469    }
62470    
62471    return allClasses.join(' ');  
62472},
62473
62474/**
62475 * Clears TR element template in response to any Column state change.
62476 * @method _clearTrTemplateEl
62477 * @private 
62478 */
62479_clearTrTemplateEl : function () {
62480    this._elTrTemplate = null;
62481},
62482
62483/**
62484 * Returns a new TR element template with TD elements classed with current
62485 * Column states.
62486 * @method _getTrTemplateEl 
62487 * @return {HTMLElement} A TR element to be cloned and added to the DOM.
62488 * @private 
62489 */
62490_getTrTemplateEl : function (oRecord, index) {
62491    // Template is already available
62492    if(this._elTrTemplate) {
62493        return this._elTrTemplate;
62494    }
62495    // Template needs to be created
62496    else {
62497        var d   = document,
62498            tr  = d.createElement('tr'),
62499            td  = d.createElement('td'),
62500            div = d.createElement('div');
62501    
62502        // Append the liner element
62503        td.appendChild(div);
62504
62505        // Create TD elements into DOCUMENT FRAGMENT
62506        var df = document.createDocumentFragment(),
62507            allKeys = this._oColumnSet.keys,
62508            elTd;
62509
62510        // Set state for each TD;
62511        var aAddClasses;
62512        for(var i=0, keysLen=allKeys.length; i<keysLen; i++) {
62513            // Clone the TD template
62514            elTd = td.cloneNode(true);
62515
62516            // Format the base TD
62517            elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1));
62518                        
62519            df.appendChild(elTd);
62520        }
62521        tr.appendChild(df);
62522        tr.className = DT.CLASS_REC;
62523        this._elTrTemplate = tr;
62524        return tr;
62525    }   
62526},
62527
62528/**
62529 * Formats a basic TD element.
62530 * @method _formatTdEl 
62531 * @param oColumn {YAHOO.widget.Column} Associated Column instance. 
62532 * @param elTd {HTMLElement} An unformatted TD element.
62533 * @param index {Number} Column key index. 
62534 * @param isLast {Boolean} True if Column is last key of the ColumnSet.
62535 * @return {HTMLElement} A formatted TD element.
62536 * @private 
62537 */
62538_formatTdEl : function (oColumn, elTd, index, isLast) {
62539    var oColumnSet = this._oColumnSet;
62540    
62541    // Set the TD's accessibility headers
62542    var allHeaders = oColumnSet.headers,
62543        allColHeaders = allHeaders[index],
62544        sTdHeaders = "",
62545        sHeader;
62546    for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) {
62547        sHeader = this._sId + "-th-" + allColHeaders[j] + ' ';
62548        sTdHeaders += sHeader;
62549    }
62550    elTd.headers = sTdHeaders;
62551    
62552    // Class the TD element
62553    var aAddClasses = [];
62554    if(index === 0) {
62555        aAddClasses[aAddClasses.length] = DT.CLASS_FIRST;
62556    }
62557    if(isLast) {
62558        aAddClasses[aAddClasses.length] = DT.CLASS_LAST;
62559    }
62560    elTd.className = this._getColumnClassNames(oColumn, aAddClasses);
62561
62562    // Class the liner element
62563    elTd.firstChild.className = DT.CLASS_LINER;
62564
62565    // Set Column width for fallback cases
62566    if(oColumn.width && DT._bDynStylesFallback) {
62567        // Validate minWidth
62568        var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
62569                oColumn.minWidth : oColumn.width;
62570        elTd.firstChild.style.overflow = 'hidden';
62571        elTd.firstChild.style.width = nWidth + 'px';
62572    }
62573    
62574    return elTd;
62575},
62576
62577
62578/**
62579 * Create a new TR element for a given Record and appends it with the correct
62580 * number of Column-state-classed TD elements. Striping is the responsibility of
62581 * the calling function, which may decide to stripe the single row, a subset of
62582 * rows, or all the rows.
62583 * @method _createTrEl
62584 * @param oRecord {YAHOO.widget.Record} Record instance
62585 * @return {HTMLElement} The new TR element.  This must be added to the DOM.
62586 * @private 
62587 */
62588_addTrEl : function (oRecord) {
62589    var elTrTemplate = this._getTrTemplateEl();
62590    
62591    // Clone the TR template.
62592    var elTr = elTrTemplate.cloneNode(true);
62593    
62594    // Populate content
62595    return this._updateTrEl(elTr,oRecord);
62596},
62597
62598/**
62599 * Formats the contents of the given TR's TD elements with data from the given
62600 * Record. Only innerHTML should change, nothing structural.
62601 *
62602 * @method _updateTrEl
62603 * @param elTr {HTMLElement} The TR element to update.
62604 * @param oRecord {YAHOO.widget.Record} The associated Record instance.
62605 * @return {HTMLElement} DOM reference to the new TR element.
62606 * @private
62607 */
62608_updateTrEl : function(elTr, oRecord) {
62609    var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true;
62610    if(ok) {
62611        // Hide the row to prevent constant reflows
62612        elTr.style.display = 'none';
62613        
62614        // Update TD elements with new data
62615        var allTds = elTr.childNodes,
62616            elTd;
62617        for(var i=0,len=allTds.length; i<len; ++i) {
62618            elTd = allTds[i];
62619            
62620            // Set the cell content
62621            this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]);
62622        }
62623        
62624        // Redisplay the row for reflow
62625        elTr.style.display = '';
62626    }
62627    
62628     // Record-to-TR association and tracking of FIRST/LAST
62629    var oldId = elTr.id,
62630        newId = oRecord.getId();
62631    if(this._sFirstTrId === oldId) {
62632        this._sFirstTrId = newId;
62633    }
62634    if(this._sLastTrId === oldId) {
62635        this._sLastTrId = newId;
62636    }
62637    elTr.id = newId;
62638    return elTr;
62639},
62640
62641
62642/**
62643 * Deletes TR element by DOM reference or by DataTable page row index.
62644 *
62645 * @method _deleteTrEl
62646 * @param row {HTMLElement | Number} TR element reference or Datatable page row index.
62647 * @return {Boolean} Returns true if successful, else returns false.
62648 * @private
62649 */
62650_deleteTrEl : function(row) {
62651    var rowIndex;
62652
62653    // Get page row index for the element
62654    if(!lang.isNumber(row)) {
62655        rowIndex = Dom.get(row).sectionRowIndex;
62656    }
62657    else {
62658        rowIndex = row;
62659    }
62660    if(lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) {
62661        // Cannot use tbody.deleteRow due to IE6 instability
62662        //return this._elTbody.deleteRow(rowIndex);
62663        return this._elTbody.removeChild(this._elTbody.rows[row]);
62664    }
62665    else {
62666        return null;
62667    }
62668},
62669
62670
62671
62672
62673
62674
62675
62676
62677
62678
62679
62680
62681
62682
62683
62684
62685
62686
62687
62688
62689
62690
62691
62692
62693
62694
62695
62696// CSS/STATE FUNCTIONS
62697
62698
62699
62700
62701/**
62702 * Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element
62703 * of the DataTable page and updates internal tracker.
62704 *
62705 * @method _unsetFirstRow
62706 * @private
62707 */
62708_unsetFirstRow : function() {
62709    // Remove FIRST
62710    if(this._sFirstTrId) {
62711        Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST);
62712        this._sFirstTrId = null;
62713    }
62714},
62715
62716/**
62717 * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
62718 * of the DataTable page and updates internal tracker.
62719 *
62720 * @method _setFirstRow
62721 * @private
62722 */
62723_setFirstRow : function() {
62724    this._unsetFirstRow();
62725    var elTr = this.getFirstTrEl();
62726    if(elTr) {
62727        // Set FIRST
62728        Dom.addClass(elTr, DT.CLASS_FIRST);
62729        this._sFirstTrId = elTr.id;
62730    }
62731},
62732
62733/**
62734 * Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element
62735 * of the DataTable page and updates internal tracker.
62736 *
62737 * @method _unsetLastRow
62738 * @private
62739 */
62740_unsetLastRow : function() {
62741    // Unassign previous class
62742    if(this._sLastTrId) {
62743        Dom.removeClass(this._sLastTrId, DT.CLASS_LAST);
62744        this._sLastTrId = null;
62745    }   
62746},
62747
62748/**
62749 * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
62750 * of the DataTable page and updates internal tracker.
62751 *
62752 * @method _setLastRow
62753 * @private
62754 */
62755_setLastRow : function() {
62756    this._unsetLastRow();
62757    var elTr = this.getLastTrEl();
62758    if(elTr) {
62759        // Assign class
62760        Dom.addClass(elTr, DT.CLASS_LAST);
62761        this._sLastTrId = elTr.id;
62762    }
62763},
62764
62765/**
62766 * Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements.
62767 *
62768 * @method _setRowStripes
62769 * @param row {HTMLElement | String | Number} (optional) HTML TR element reference
62770 * or string ID, or page row index of where to start striping.
62771 * @param range {Number} (optional) If given, how many rows to stripe, otherwise
62772 * stripe all the rows until the end.
62773 * @private
62774 */
62775_setRowStripes : function(row, range) {
62776    // Default values stripe all rows
62777    var allRows = this._elTbody.rows,
62778        nStartIndex = 0,
62779        nEndIndex = allRows.length,
62780        aOdds = [], nOddIdx = 0,
62781        aEvens = [], nEvenIdx = 0;
62782
62783    // Stripe a subset
62784    if((row !== null) && (row !== undefined)) {
62785        // Validate given start row
62786        var elStartRow = this.getTrEl(row);
62787        if(elStartRow) {
62788            nStartIndex = elStartRow.sectionRowIndex;
62789
62790            // Validate given range
62791            if(lang.isNumber(range) && (range > 1)) {
62792                nEndIndex = nStartIndex + range;
62793            }
62794        }
62795    }
62796
62797    for(var i=nStartIndex; i<nEndIndex; i++) {
62798        if(i%2) {
62799            aOdds[nOddIdx++] = allRows[i];
62800        } else {
62801            aEvens[nEvenIdx++] = allRows[i];
62802        }
62803    }
62804
62805    if (aOdds.length) {
62806        Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD);
62807    }
62808
62809    if (aEvens.length) {
62810        Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN);
62811    }
62812},
62813
62814/**
62815 * Assigns the class DT.CLASS_SELECTED to TR and TD elements.
62816 *
62817 * @method _setSelections
62818 * @private
62819 */
62820_setSelections : function() {
62821    // Keep track of selected rows
62822    var allSelectedRows = this.getSelectedRows();
62823    // Keep track of selected cells
62824    var allSelectedCells = this.getSelectedCells();
62825    // Anything to select?
62826    if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) {
62827        var oColumnSet = this._oColumnSet,
62828            el;
62829        // Loop over each row
62830        for(var i=0; i<allSelectedRows.length; i++) {
62831            el = Dom.get(allSelectedRows[i]);
62832            if(el) {
62833                Dom.addClass(el, DT.CLASS_SELECTED);
62834            }
62835        }
62836        // Loop over each cell
62837        for(i=0; i<allSelectedCells.length; i++) {
62838            el = Dom.get(allSelectedCells[i].recordId);
62839            if(el) {
62840                Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED);
62841            }
62842        }
62843    }       
62844},
62845
62846
62847
62848
62849
62850
62851
62852
62853
62854
62855
62856
62857
62858
62859
62860
62861
62862
62863
62864
62865
62866
62867
62868
62869
62870
62871
62872
62873
62874
62875
62876
62877
62878
62879
62880
62881
62882
62883
62884
62885
62886
62887
62888/////////////////////////////////////////////////////////////////////////////
62889//
62890// Private DOM Event Handlers
62891//
62892/////////////////////////////////////////////////////////////////////////////
62893
62894/**
62895 * Validates minWidths whenever the render chain ends.
62896 *
62897 * @method _onRenderChainEnd
62898 * @private
62899 */
62900_onRenderChainEnd : function() {
62901    // Hide loading message
62902    this.hideTableMessage();
62903    
62904    // Show empty message
62905    if(this._elTbody.rows.length === 0) {
62906        this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);        
62907    }
62908
62909    // Execute in timeout thread to give implementers a chance
62910    // to subscribe after the constructor
62911    var oSelf = this;
62912    setTimeout(function() {
62913        if((oSelf instanceof DT) && oSelf._sId) {        
62914            // Init event
62915            if(oSelf._bInit) {
62916                oSelf._bInit = false;
62917                oSelf.fireEvent("initEvent");
62918            }
62919    
62920            // Render event
62921            oSelf.fireEvent("renderEvent");
62922            // Backward compatibility
62923            oSelf.fireEvent("refreshEvent");
62924            YAHOO.log("DataTable rendered", "info", oSelf.toString());
62925    
62926            // Post-render routine
62927            oSelf.validateColumnWidths();
62928    
62929            // Post-render event
62930            oSelf.fireEvent("postRenderEvent");
62931            
62932            /*if(YAHOO.example.Performance.trialStart) {
62933                YAHOO.log((new Date()).getTime() - YAHOO.example.Performance.trialStart.getTime() + " ms", "time");
62934                YAHOO.example.Performance.trialStart = null;
62935            }*/
62936            
62937            YAHOO.log("Post-render routine executed", "info", oSelf.toString());
62938        }
62939    }, 0);
62940},
62941
62942/**
62943 * Handles click events on the DOCUMENT.
62944 *
62945 * @method _onDocumentClick
62946 * @param e {HTMLEvent} The click event.
62947 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
62948 * @private
62949 */
62950_onDocumentClick : function(e, oSelf) {
62951    var elTarget = Ev.getTarget(e);
62952    var elTag = elTarget.nodeName.toLowerCase();
62953
62954    if(!Dom.isAncestor(oSelf._elContainer, elTarget)) {
62955        oSelf.fireEvent("tableBlurEvent");
62956
62957        // Fires editorBlurEvent when click is not within the TABLE.
62958        // For cases when click is within the TABLE, due to timing issues,
62959        // the editorBlurEvent needs to get fired by the lower-level DOM click
62960        // handlers below rather than by the TABLE click handler directly.
62961        if(oSelf._oCellEditor) {
62962            if(oSelf._oCellEditor.getContainerEl) {
62963                var elContainer = oSelf._oCellEditor.getContainerEl();
62964                // Only if the click was not within the CellEditor container
62965                if(!Dom.isAncestor(elContainer, elTarget) &&
62966                        (elContainer.id !== elTarget.id)) {
62967                    oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
62968                }
62969            }
62970            // Backward Compatibility
62971            else if(oSelf._oCellEditor.isActive) {
62972                // Only if the click was not within the Cell Editor container
62973                if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
62974                        (oSelf._oCellEditor.container.id !== elTarget.id)) {
62975                    oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
62976                }
62977            }
62978        }
62979    }
62980},
62981
62982/**
62983 * Handles focus events on the DataTable instance.
62984 *
62985 * @method _onTableFocus
62986 * @param e {HTMLEvent} The focus event.
62987 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
62988 * @private
62989 */
62990_onTableFocus : function(e, oSelf) {
62991    oSelf.fireEvent("tableFocusEvent");
62992},
62993
62994/**
62995 * Handles focus events on the THEAD element.
62996 *
62997 * @method _onTheadFocus
62998 * @param e {HTMLEvent} The focus event.
62999 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
63000 * @private
63001 */
63002_onTheadFocus : function(e, oSelf) {
63003    oSelf.fireEvent("theadFocusEvent");
63004    oSelf.fireEvent("tableFocusEvent");
63005},
63006
63007/**
63008 * Handles focus events on the TBODY element.
63009 *
63010 * @method _onTbodyFocus
63011 * @param e {HTMLEvent} The focus event.
63012 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
63013 * @private
63014 */
63015_onTbodyFocus : function(e, oSelf) {
63016    oSelf.fireEvent("tbodyFocusEvent");
63017    oSelf.fireEvent("tableFocusEvent");
63018},
63019
63020/**
63021 * Handles mouseover events on the DataTable instance.
63022 *
63023 * @method _onTableMouseover
63024 * @param e {HTMLEvent} The mouseover event.
63025 * @param origTarget {HTMLElement} The mouseenter delegated element.
63026 * @param container {HTMLElement} The mouseenter delegation container.
63027 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
63028 * @private
63029 */
63030_onTableMouseover : function(e, origTarget, container, oSelf) {
63031    var elTarget = origTarget;
63032    var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
63033    var bKeepBubbling = true;
63034    while(elTarget && (elTag != "table")) {
63035        switch(elTag) {
63036            case "body":
63037                 return;
63038            case "a":
63039                break;
63040            case "td":
63041                bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
63042                break;
63043            case "span":
63044                if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
63045                    bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e});
63046                    // Backward compatibility
63047                    bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
63048                }
63049                break;
63050            case "th":
63051                bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e});
63052                // Backward compatibility
63053                bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
63054                break;
63055            case "tr":
63056                if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
63057                    bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e});
63058                    // Backward compatibility
63059                    bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
63060                }
63061                else {
63062                    bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
63063                }
63064                break;
63065            default:
63066                break;
63067        }
63068        if(bKeepBubbling === false) {
63069            return;
63070        }
63071        else {
63072            elTarget = elTarget.parentNode;
63073            if(elTarget) {
63074                elTag = elTarget.nodeName.toLowerCase();
63075            }
63076        }
63077    }
63078    oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e});
63079},
63080
63081/**
63082 * Handles mouseout events on the DataTable instance.
63083 *
63084 * @method _onTableMouseout
63085 * @param e {HTMLEvent} The mouseout event.
63086 * @param origTarget {HTMLElement} The mouseleave delegated element.
63087 * @param container {HTMLElement} The mouseleave delegation container.
63088 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
63089 * @private
63090 */
63091_onTableMouseout : function(e, origTarget, container, oSelf) {
63092    var elTarget = origTarget;
63093    var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
63094    var bKeepBubbling = true;
63095    while(elTarget && (elTag != "table")) {
63096        switch(elTag) {
63097            case "body":
63098                return;
63099            case "a":
63100                break;
63101            case "td":
63102                bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
63103                break;
63104            case "span":
63105                if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
63106                    bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e});
63107                    // Backward compatibility
63108                    bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
63109                }
63110                break;
63111            case "th":
63112                bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e});
63113                // Backward compatibility
63114                bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
63115                break;
63116            case "tr":
63117                if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
63118                    bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e});
63119                    // Backward compatibility
63120                    bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
63121                }
63122                else {
63123                    bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
63124                }
63125                break;
63126            default:
63127                break;
63128        }
63129        if(bKeepBubbling === false) {
63130            return;
63131        }
63132        else {
63133            elTarget = elTarget.parentNode;
63134            if(elTarget) {
63135                elTag = elTarget.nodeName.toLowerCase();
63136            }
63137        }
63138    }
63139    oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e});
63140},
63141
63142/**
63143 * Handles mousedown events on the DataTable instance.
63144 *
63145 * @method _onTableMousedown
63146 * @param e {HTMLEvent} The mousedown event.
63147 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
63148 * @private
63149 */
63150_onTableMousedown : function(e, oSelf) {
63151    var elTarget = Ev.getTarget(e);
63152    var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
63153    var bKeepBubbling = true;
63154    while(elTarget && (elTag != "table")) {
63155        switch(elTag) {
63156            case "body":
63157                return;
63158            case "a":
63159                break;
63160            case "td":
63161                bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
63162                break;
63163            case "span":
63164                if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
63165                    bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e});
63166                    // Backward compatibility
63167                    bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
63168                }
63169                break;
63170            case "th":
63171                bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e});
63172                // Backward compatibility
63173                bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
63174                break;
63175            case "tr":
63176                if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
63177                    bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e});
63178                    // Backward compatibility
63179                    bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
63180                }
63181                else {
63182                    bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
63183                }
63184                break;
63185            default:
63186                break;
63187        }
63188        if(bKeepBubbling === false) {
63189            return;
63190        }
63191        else {
63192            elTarget = elTarget.parentNode;
63193            if(elTarget) {
63194                elTag = elTarget.nodeName.toLowerCase();
63195            }
63196        }
63197    }
63198    oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e});
63199},
63200
63201/**
63202 * Handles mouseup events on the DataTable instance.
63203 *
63204 * @method _onTableMouseup
63205 * @param e {HTMLEvent} The mouseup event.
63206 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
63207 * @private
63208 */
63209_onTableMouseup : function(e, oSelf) {
63210    var elTarget = Ev.getTarget(e);
63211    var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
63212    var bKeepBubbling = true;
63213    while(elTarget && (elTag != "table")) {
63214        switch(elTag) {
63215            case "body":
63216                return;
63217            case "a":
63218                break;
63219            case "td":
63220                bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e});
63221                break;
63222            case "span":
63223                if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
63224                    bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e});
63225                    // Backward compatibility
63226                    bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e});
63227                }
63228                break;
63229            case "th":
63230                bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e});
63231                // Backward compatibility
63232                bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e});
63233                break;
63234            case "tr":
63235                if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
63236                    bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e});
63237                    // Backward compatibility
63238                    bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e});
63239                }
63240                else {
63241                    bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e});
63242                }
63243                break;
63244            default:
63245                break;
63246        }
63247        if(bKeepBubbling === false) {
63248            return;
63249        }
63250        else {
63251            elTarget = elTarget.parentNode;
63252            if(elTarget) {
63253                elTag = elTarget.nodeName.toLowerCase();
63254            }
63255        }
63256    }
63257    oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e});
63258},
63259
63260/**
63261 * Handles dblclick events on the DataTable instance.
63262 *
63263 * @method _onTableDblclick
63264 * @param e {HTMLEvent} The dblclick event.
63265 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
63266 * @private
63267 */
63268_onTableDblclick : function(e, oSelf) {
63269    var elTarget = Ev.getTarget(e);
63270    var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
63271    var bKeepBubbling = true;
63272    while(elTarget && (elTag != "table")) {
63273        switch(elTag) {
63274            case "body":
63275                return;
63276            case "td":
63277                bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
63278                break;
63279            case "span":
63280                if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
63281                    bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e});
63282                    // Backward compatibility
63283                    bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
63284                }
63285                break;
63286            case "th":
63287                bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e});
63288                // Backward compatibility
63289                bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
63290                break;
63291            case "tr":
63292                if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
63293                    bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e});
63294                    // Backward compatibility
63295                    bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
63296                }
63297                else {
63298                    bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
63299                }
63300                break;
63301            default:
63302                break;
63303        }
63304        if(bKeepBubbling === false) {
63305            return;
63306        }
63307        else {
63308            elTarget = elTarget.parentNode;
63309            if(elTarget) {
63310                elTag = elTarget.nodeName.toLowerCase();
63311            }
63312        }
63313    }
63314    oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e});
63315},
63316/**
63317 * Handles keydown events on the THEAD element.
63318 *
63319 * @method _onTheadKeydown
63320 * @param e {HTMLEvent} The key event.
63321 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
63322 * @private
63323 */
63324_onTheadKeydown : function(e, oSelf) {
63325    var elTarget = Ev.getTarget(e);
63326    var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
63327    var bKeepBubbling = true;
63328    while(elTarget && (elTag != "table")) {
63329        switch(elTag) {
63330            case "body":
63331                return;
63332            case "input":
63333            case "textarea":
63334                // TODO: implement textareaKeyEvent
63335                break;
63336            case "thead":
63337                bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
63338                break;
63339            default:
63340                break;
63341        }
63342        if(bKeepBubbling === false) {
63343            return;
63344        }
63345        else {
63346            elTarget = elTarget.parentNode;
63347            if(elTarget) {
63348                elTag = elTarget.nodeName.toLowerCase();
63349            }
63350        }
63351    }
63352    oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
63353},
63354
63355/**
63356 * Handles keydown events on the TBODY element. Handles selection behavior,
63357 * provides hooks for ENTER to edit functionality.
63358 *
63359 * @method _onTbodyKeydown
63360 * @param e {HTMLEvent} The key event.
63361 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
63362 * @private
63363 */
63364_onTbodyKeydown : function(e, oSelf) {
63365    var sMode = oSelf.get("selectionMode");
63366
63367    if(sMode == "standard") {
63368        oSelf._handleStandardSelectionByKey(e);
63369    }
63370    else if(sMode == "single") {
63371        oSelf._handleSingleSelectionByKey(e);
63372    }
63373    else if(sMode == "cellblock") {
63374        oSelf._handleCellBlockSelectionByKey(e);
63375    }
63376    else if(sMode == "cellrange") {
63377        oSelf._handleCellRangeSelectionByKey(e);
63378    }
63379    else if(sMode == "singlecell") {
63380        oSelf._handleSingleCellSelectionByKey(e);
63381    }
63382    
63383    if(oSelf._oCellEditor) {
63384        if(oSelf._oCellEditor.fireEvent) {
63385            oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
63386        }
63387        else if(oSelf._oCellEditor.isActive) {
63388            oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
63389        }
63390    }
63391
63392    var elTarget = Ev.getTarget(e);
63393    var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
63394    var bKeepBubbling = true;
63395    while(elTarget && (elTag != "table")) {
63396        switch(elTag) {
63397            case "body":
63398                return;
63399            case "tbody":
63400                bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e});
63401                break;
63402            default:
63403                break;
63404        }
63405        if(bKeepBubbling === false) {
63406            return;
63407        }
63408        else {
63409            elTarget = elTarget.parentNode;
63410            if(elTarget) {
63411                elTag = elTarget.nodeName.toLowerCase();
63412            }
63413        }
63414    }
63415    oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
63416},
63417
63418/**
63419 * Handles click events on the THEAD element.
63420 *
63421 * @method _onTheadClick
63422 * @param e {HTMLEvent} The click event.
63423 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
63424 * @private
63425 */
63426_onTheadClick : function(e, oSelf) {
63427    // This blurs the CellEditor
63428    if(oSelf._oCellEditor) {
63429        if(oSelf._oCellEditor.fireEvent) {
63430            oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
63431        }
63432        // Backward compatibility
63433        else if(oSelf._oCellEditor.isActive) {
63434            oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
63435        }
63436    }
63437
63438    var elTarget = Ev.getTarget(e),
63439        elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(),
63440        bKeepBubbling = true;
63441    while(elTarget && (elTag != "table")) {
63442        switch(elTag) {
63443            case "body":
63444                return;
63445            case "input":
63446                var sType = elTarget.type.toLowerCase();
63447                if(sType == "checkbox") {
63448                    bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e});
63449                }
63450                else if(sType == "radio") {
63451                    bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e});
63452                }
63453                else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
63454                    if(!elTarget.disabled) {
63455                        bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
63456                    }
63457                    else {
63458                        bKeepBubbling = false;
63459                    }
63460                }
63461                else if (elTarget.disabled){
63462                    bKeepBubbling = false;
63463                }
63464                break;
63465            case "a":
63466                bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e});
63467                break;
63468            case "button":
63469                if(!elTarget.disabled) {
63470                    bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
63471                }
63472                else {
63473                    bKeepBubbling = false;
63474                }
63475                break;
63476            case "span":
63477                if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
63478                    bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e});
63479                    // Backward compatibility
63480                    bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
63481                }
63482                break;
63483            case "th":
63484                bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e});
63485                // Backward compatibility
63486                bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
63487                break;
63488            case "tr":
63489                bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e});
63490                // Backward compatibility
63491                bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
63492                break;
63493            default:
63494                break;
63495        }
63496        if(bKeepBubbling === false) {
63497            return;
63498        }
63499        else {
63500            elTarget = elTarget.parentNode;
63501            if(elTarget) {
63502                elTag = elTarget.nodeName.toLowerCase();
63503            }
63504        }
63505    }
63506    oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
63507},
63508
63509/**
63510 * Handles click events on the primary TBODY element.
63511 *
63512 * @method _onTbodyClick
63513 * @param e {HTMLEvent} The click event.
63514 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
63515 * @private
63516 */
63517_onTbodyClick : function(e, oSelf) {
63518    // This blurs the CellEditor
63519    if(oSelf._oCellEditor) {
63520        if(oSelf._oCellEditor.fireEvent) {
63521            oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
63522        }
63523        else if(oSelf._oCellEditor.isActive) {
63524            oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
63525        }
63526    }
63527
63528    // Fire Custom Events
63529    var elTarget = Ev.getTarget(e),
63530        elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(),
63531        bKeepBubbling = true;
63532    while(elTarget && (elTag != "table")) {
63533        switch(elTag) {
63534            case "body":
63535                return;
63536            case "input":
63537                var sType = elTarget.type.toLowerCase();
63538                if(sType == "checkbox") {
63539                    bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
63540                }
63541                else if(sType == "radio") {
63542                    bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
63543                }
63544                else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
63545                    if(!elTarget.disabled) {
63546                        bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
63547                    }
63548                    else {
63549                        bKeepBubbling = false;
63550                    }
63551                }
63552                else if (elTarget.disabled){
63553                    bKeepBubbling = false;
63554                }
63555                break;
63556            case "a":
63557                bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
63558                break;
63559            case "button":
63560                if(!elTarget.disabled) {
63561                    bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
63562                }
63563                else {
63564                    bKeepBubbling = false;
63565                }
63566                break;
63567            case "td":
63568                bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
63569                break;
63570            case "tr":
63571                bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
63572                break;
63573            default:
63574                break;
63575        }
63576        if(bKeepBubbling === false) {
63577            return;
63578        }
63579        else {
63580            elTarget = elTarget.parentNode;
63581            if(elTarget) {
63582                elTag = elTarget.nodeName.toLowerCase();
63583            }
63584        }
63585    }
63586    oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
63587},
63588
63589/**
63590 * Handles change events on SELECT elements within DataTable.
63591 *
63592 * @method _onDropdownChange
63593 * @param e {HTMLEvent} The change event.
63594 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
63595 * @private
63596 */
63597_onDropdownChange : function(e, oSelf) {
63598    var elTarget = Ev.getTarget(e);
63599    oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
63600},
63601
63602
63603
63604
63605
63606
63607
63608
63609
63610
63611
63612
63613
63614
63615
63616
63617
63618
63619
63620
63621
63622
63623
63624
63625
63626
63627
63628
63629
63630
63631
63632
63633/////////////////////////////////////////////////////////////////////////////
63634//
63635// Public member variables
63636//
63637/////////////////////////////////////////////////////////////////////////////
63638/**
63639 * Returns object literal of initial configs.
63640 *
63641 * @property configs
63642 * @type Object
63643 * @default {} 
63644 */
63645configs: null,
63646
63647
63648/////////////////////////////////////////////////////////////////////////////
63649//
63650// Public methods
63651//
63652/////////////////////////////////////////////////////////////////////////////
63653
63654/**
63655 * Returns unique id assigned to instance, which is a useful prefix for
63656 * generating unique DOM ID strings.
63657 *
63658 * @method getId
63659 * @return {String} Unique ID of the DataSource instance.
63660 */
63661getId : function() {
63662    return this._sId;
63663},
63664
63665/**
63666 * DataSource instance name, for logging.
63667 *
63668 * @method toString
63669 * @return {String} Unique name of the DataSource instance.
63670 */
63671
63672toString : function() {
63673    return "DataTable instance " + this._sId;
63674},
63675
63676/**
63677 * Returns the DataTable instance's DataSource instance.
63678 *
63679 * @method getDataSource
63680 * @return {YAHOO.util.DataSource} DataSource instance.
63681 */
63682getDataSource : function() {
63683    return this._oDataSource;
63684},
63685
63686/**
63687 * Returns the DataTable instance's ColumnSet instance.
63688 *
63689 * @method getColumnSet
63690 * @return {YAHOO.widget.ColumnSet} ColumnSet instance.
63691 */
63692getColumnSet : function() {
63693    return this._oColumnSet;
63694},
63695
63696/**
63697 * Returns the DataTable instance's RecordSet instance.
63698 *
63699 * @method getRecordSet
63700 * @return {YAHOO.widget.RecordSet} RecordSet instance.
63701 */
63702getRecordSet : function() {
63703    return this._oRecordSet;
63704},
63705
63706/**
63707 * Returns on object literal representing the DataTable instance's current
63708 * state with the following properties:
63709 * <dl>
63710 * <dt>pagination</dt>
63711 * <dd>Instance of YAHOO.widget.Paginator</dd>
63712 *
63713 * <dt>sortedBy</dt>
63714 * <dd>
63715 *     <dl>
63716 *         <dt>sortedBy.key</dt>
63717 *         <dd>{String} Key of sorted Column</dd>
63718 *         <dt>sortedBy.dir</dt>
63719 *         <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
63720 *     </dl>
63721 * </dd>
63722 *
63723 * <dt>selectedRows</dt>
63724 * <dd>Array of selected rows by Record ID.</dd>
63725 *
63726 * <dt>selectedCells</dt>
63727 * <dd>Selected cells as an array of object literals:
63728 *     {recordId:sRecordId, columnKey:sColumnKey}</dd>
63729 * </dl>
63730 *  
63731 * @method getState
63732 * @return {Object} DataTable instance state object literal values.
63733 */
63734getState : function() {
63735    return {
63736        totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(),
63737        pagination: this.get("paginator") ? this.get("paginator").getState() : null,
63738        sortedBy: this.get("sortedBy"),
63739        selectedRows: this.getSelectedRows(),
63740        selectedCells: this.getSelectedCells()
63741    };
63742},
63743
63744
63745
63746
63747
63748
63749
63750
63751
63752
63753
63754
63755
63756
63757
63758
63759
63760
63761
63762
63763
63764
63765
63766
63767
63768
63769
63770
63771
63772
63773
63774
63775
63776
63777
63778
63779
63780
63781
63782
63783
63784
63785
63786// DOM ACCESSORS
63787
63788/**
63789 * Returns DOM reference to the DataTable's container element.
63790 *
63791 * @method getContainerEl
63792 * @return {HTMLElement} Reference to DIV element.
63793 */
63794getContainerEl : function() {
63795    return this._elContainer;
63796},
63797
63798/**
63799 * Returns DOM reference to the DataTable's TABLE element.
63800 *
63801 * @method getTableEl
63802 * @return {HTMLElement} Reference to TABLE element.
63803 */
63804getTableEl : function() {
63805    return this._elTable;
63806},
63807
63808/**
63809 * Returns DOM reference to the DataTable's THEAD element.
63810 *
63811 * @method getTheadEl
63812 * @return {HTMLElement} Reference to THEAD element.
63813 */
63814getTheadEl : function() {
63815    return this._elThead;
63816},
63817
63818/**
63819 * Returns DOM reference to the DataTable's primary TBODY element.
63820 *
63821 * @method getTbodyEl
63822 * @return {HTMLElement} Reference to TBODY element.
63823 */
63824getTbodyEl : function() {
63825    return this._elTbody;
63826},
63827
63828/**
63829 * Returns DOM reference to the DataTable's secondary TBODY element that is
63830 * used to display messages.
63831 *
63832 * @method getMsgTbodyEl
63833 * @return {HTMLElement} Reference to TBODY element.
63834 */
63835getMsgTbodyEl : function() {
63836    return this._elMsgTbody;
63837},
63838
63839/**
63840 * Returns DOM reference to the TD element within the secondary TBODY that is
63841 * used to display messages.
63842 *
63843 * @method getMsgTdEl
63844 * @return {HTMLElement} Reference to TD element.
63845 */
63846getMsgTdEl : function() {
63847    return this._elMsgTd;
63848},
63849
63850/**
63851 * Returns the corresponding TR reference for a given DOM element, ID string or
63852 * page row index. If the given identifier is a child of a TR element,
63853 * then DOM tree is traversed until a parent TR element is returned, otherwise
63854 * null. Returns null if the row is not considered a primary row (i.e., row
63855 * extensions).
63856 *
63857 * @method getTrEl
63858 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
63859 * get: by element reference, ID string, page row index, or Record.
63860 * @return {HTMLElement} Reference to TR element, or null.
63861 */
63862getTrEl : function(row) {
63863    // By Record
63864    if(row instanceof YAHOO.widget.Record) {
63865        return document.getElementById(row.getId());
63866    }
63867    // By page row index
63868    else if(lang.isNumber(row)) {
63869        var dataRows = Dom.getElementsByClassName(DT.CLASS_REC, "tr", this._elTbody);
63870        return dataRows && dataRows[row] ? dataRows[row] : null;
63871    }
63872    // By ID string or element reference
63873    else if(row) {
63874        var elRow = (lang.isString(row)) ? document.getElementById(row) : row;
63875
63876        // Validate HTML element
63877        if(elRow && elRow.ownerDocument == document) {
63878            // Validate TR element
63879            if(elRow.nodeName.toLowerCase() != "tr") {
63880                // Traverse up the DOM to find the corresponding TR element
63881                elRow = Dom.getAncestorByTagName(elRow,"tr");
63882            }
63883
63884            return elRow;
63885        }
63886    }
63887
63888    return null;
63889},
63890
63891/**
63892 * Returns DOM reference to the first primary TR element in the DataTable page, or null.
63893 *
63894 * @method getFirstTrEl
63895 * @return {HTMLElement} Reference to TR element.
63896 */
63897getFirstTrEl : function() {
63898    var allRows = this._elTbody.rows,
63899        i=0;
63900    while(allRows[i]) {
63901        if(this.getRecord(allRows[i])) {
63902            return allRows[i];
63903        }
63904        i++;
63905    }
63906    return null;
63907
63908},
63909
63910/**
63911 * Returns DOM reference to the last primary TR element in the DataTable page, or null.
63912 *
63913 * @method getLastTrEl
63914 * @return {HTMLElement} Reference to last TR element.
63915 */
63916getLastTrEl : function() {
63917    var allRows = this._elTbody.rows,
63918        i=allRows.length-1;
63919    while(i>-1) {
63920        if(this.getRecord(allRows[i])) {
63921            return allRows[i];
63922        }
63923        i--;
63924    }
63925    return null;
63926},
63927
63928/**
63929 * Returns DOM reference to the next TR element from the given primary TR element, or null.
63930 *
63931 * @method getNextTrEl
63932 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
63933 * reference, ID string, page row index, or Record from which to get next TR element.
63934 * @param forcePrimary {Boolean} (optional) If true, will only return TR elements
63935 * that correspond to Records. Non-primary rows (such as row expansions)
63936 * will be skipped.
63937 * @return {HTMLElement} Reference to next TR element.
63938 */
63939getNextTrEl : function(row, forcePrimary) {
63940    var nThisTrIndex = this.getTrIndex(row);
63941    if(nThisTrIndex !== null) {
63942        var allRows = this._elTbody.rows;
63943        if(forcePrimary) {
63944            while(nThisTrIndex < allRows.length-1) {
63945                row = allRows[nThisTrIndex+1];
63946                if(this.getRecord(row)) {
63947                    return row;
63948                }
63949                nThisTrIndex++;
63950            }
63951        }
63952        else {
63953            if(nThisTrIndex < allRows.length-1) {
63954                return allRows[nThisTrIndex+1];
63955            }
63956        }
63957    }
63958
63959    YAHOO.log("Could not get next TR element for row " + row, "info", this.toString());
63960    return null;
63961},
63962
63963/**
63964 * Returns DOM reference to the previous TR element from the given primary TR element, or null.
63965 *
63966 * @method getPreviousTrEl
63967 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
63968 * reference, ID string, page row index, or Record from which to get previous TR element.
63969 * @param forcePrimary {Boolean} (optional) If true, will only return TR elements
63970 * from rothat correspond to Records. Non-primary rows (such as row expansions)
63971 * will be skipped.
63972 * @return {HTMLElement} Reference to previous TR element.
63973 */
63974getPreviousTrEl : function(row, forcePrimary) {
63975    var nThisTrIndex = this.getTrIndex(row);
63976    if(nThisTrIndex !== null) {
63977        var allRows = this._elTbody.rows;
63978
63979        if(forcePrimary) {
63980            while(nThisTrIndex > 0) {
63981                row = allRows[nThisTrIndex-1];
63982                if(this.getRecord(row)) {
63983                    return row;
63984                }
63985                nThisTrIndex--;
63986            }
63987        }
63988        else {
63989            if(nThisTrIndex > 0) {
63990                return allRows[nThisTrIndex-1];
63991            }
63992        }
63993    }
63994
63995    YAHOO.log("Could not get previous TR element for row " + row, "info", this.toString());
63996    return null;
63997},
63998
63999
64000/**
64001 * Workaround for IE bug where hidden or not-in-dom elements cause cellIndex
64002 * value to be incorrect.
64003 *
64004 * @method getCellIndex
64005 * @param cell {HTMLElement | Object} TD element or child of a TD element, or
64006 * object literal of syntax {record:oRecord, column:oColumn}.
64007 * @return {Number} TD.cellIndex value.
64008 */
64009getCellIndex : function(cell) {
64010    cell = this.getTdEl(cell);
64011    if(cell) {
64012        if(ua.ie > 0) {
64013            var i=0,
64014                tr = cell.parentNode,
64015                allCells = tr.childNodes,
64016                len = allCells.length;
64017            for(; i<len; i++) {
64018                if(allCells[i] == cell) {
64019                    return i;
64020                }
64021            }
64022        }
64023        else {
64024            return cell.cellIndex;
64025        }
64026    }
64027},
64028
64029/**
64030 * Returns DOM reference to a TD liner element.
64031 *
64032 * @method getTdLinerEl
64033 * @param cell {HTMLElement | Object} TD element or child of a TD element, or
64034 * object literal of syntax {record:oRecord, column:oColumn}.
64035 * @return {HTMLElement} Reference to TD liner element.
64036 */
64037getTdLinerEl : function(cell) {
64038    var elCell = this.getTdEl(cell);
64039    return elCell.firstChild || null;
64040},
64041
64042/**
64043 * Returns DOM reference to a TD element. Returns null if the row is not
64044 * considered a primary row (i.e., row extensions).
64045 *
64046 * @method getTdEl
64047 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
64048 * object literal of syntax {record:oRecord, column:oColumn}.
64049 * @return {HTMLElement} Reference to TD element.
64050 */
64051getTdEl : function(cell) {
64052    var elCell;
64053    var el = Dom.get(cell);
64054
64055    // Validate HTML element
64056    if(el && (el.ownerDocument == document)) {
64057        // Validate TD element
64058        if(el.nodeName.toLowerCase() != "td") {
64059            // Traverse up the DOM to find the corresponding TR element
64060            elCell = Dom.getAncestorByTagName(el, "td");
64061        }
64062        else {
64063            elCell = el;
64064        }
64065        
64066        // Make sure the TD is in this TBODY or is not in DOM
64067        // Bug 2527707 and bug 2263558
64068        if(elCell && ((elCell.parentNode.parentNode == this._elTbody) ||
64069            (elCell.parentNode.parentNode === null) ||
64070            (elCell.parentNode.parentNode.nodeType === 11))) {
64071            // Now we can return the TD element
64072            return elCell;
64073        }
64074    }
64075    else if(cell) {
64076        var oRecord, nColKeyIndex;
64077
64078        if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) {
64079            oRecord = this.getRecord(cell.recordId);
64080            var oColumn = this.getColumn(cell.columnKey);
64081            if(oColumn) {
64082                nColKeyIndex = oColumn.getKeyIndex();
64083            }
64084
64085        }
64086        if(cell.record && cell.column && cell.column.getKeyIndex) {
64087            oRecord = cell.record;
64088            nColKeyIndex = cell.column.getKeyIndex();
64089        }
64090        var elRow = this.getTrEl(oRecord);
64091        if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) {
64092            return elRow.cells[nColKeyIndex] || null;
64093        }
64094    }
64095
64096    return null;
64097},
64098
64099/**
64100 * Returns DOM reference to the first primary TD element in the DataTable page (by default),
64101 * the first TD element of the optionally given row, or null.
64102 *
64103 * @method getFirstTdEl
64104 * @param row {HTMLElement} (optional) row from which to get first TD
64105 * @return {HTMLElement} Reference to TD element.
64106 */
64107getFirstTdEl : function(row) {
64108    var elRow = lang.isValue(row) ? this.getTrEl(row) : this.getFirstTrEl();
64109    if(elRow) {
64110        if(elRow.cells && elRow.cells.length > 0) {
64111            return elRow.cells[0];
64112        }
64113        else if(elRow.childNodes && elRow.childNodes.length > 0) {
64114            return elRow.childNodes[0];
64115        }
64116    }
64117    YAHOO.log("Could not get first TD element for row " + elRow, "info", this.toString());
64118    return null;
64119},
64120
64121/**
64122 * Returns DOM reference to the last primary TD element in the DataTable page (by default),
64123 * the first TD element of the optionally given row, or null.
64124 *
64125 * @method getLastTdEl
64126 * @param row {HTMLElement} (optional) row from which to get first TD
64127 * @return {HTMLElement} Reference to last TD element.
64128 */
64129getLastTdEl : function(row) {
64130    var elRow = lang.isValue(row) ? this.getTrEl(row) : this.getLastTrEl();
64131    if(elRow) {
64132        if(elRow.cells && elRow.cells.length > 0) {
64133            return elRow.cells[elRow.cells.length-1];
64134        }
64135        else if(elRow.childNodes && elRow.childNodes.length > 0) {
64136            return elRow.childNodes[elRow.childNodes.length-1];
64137        }
64138    }
64139    YAHOO.log("Could not get last TD element for row " + elRow, "info", this.toString());
64140    return null;
64141},
64142
64143/**
64144 * Returns DOM reference to the next TD element from the given cell, or null.
64145 *
64146 * @method getNextTdEl
64147 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
64148 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
64149 * @return {HTMLElement} Reference to next TD element, or null.
64150 */
64151getNextTdEl : function(cell) {
64152    var elCell = this.getTdEl(cell);
64153    if(elCell) {
64154        var nThisTdIndex = this.getCellIndex(elCell);
64155        var elRow = this.getTrEl(elCell);
64156        if(elRow.cells && (elRow.cells.length) > 0 && (nThisTdIndex < elRow.cells.length-1)) {
64157            return elRow.cells[nThisTdIndex+1];
64158        }
64159        else if(elRow.childNodes && (elRow.childNodes.length) > 0 && (nThisTdIndex < elRow.childNodes.length-1)) {
64160            return elRow.childNodes[nThisTdIndex+1];
64161        }
64162        else {
64163            var elNextRow = this.getNextTrEl(elRow);
64164            if(elNextRow) {
64165                return elNextRow.cells[0];
64166            }
64167        }
64168    }
64169    YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString());
64170    return null;
64171},
64172
64173/**
64174 * Returns DOM reference to the previous TD element from the given cell, or null.
64175 *
64176 * @method getPreviousTdEl
64177 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
64178 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
64179 * @return {HTMLElement} Reference to previous TD element, or null.
64180 */
64181getPreviousTdEl : function(cell) {
64182    var elCell = this.getTdEl(cell);
64183    if(elCell) {
64184        var nThisTdIndex = this.getCellIndex(elCell);
64185        var elRow = this.getTrEl(elCell);
64186        if(nThisTdIndex > 0) {
64187            if(elRow.cells && elRow.cells.length > 0) {
64188                return elRow.cells[nThisTdIndex-1];
64189            }
64190            else if(elRow.childNodes && elRow.childNodes.length > 0) {
64191                return elRow.childNodes[nThisTdIndex-1];
64192            }
64193        }
64194        else {
64195            var elPreviousRow = this.getPreviousTrEl(elRow);
64196            if(elPreviousRow) {
64197                return this.getLastTdEl(elPreviousRow);
64198            }
64199        }
64200    }
64201    YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString());
64202    return null;
64203},
64204
64205/**
64206 * Returns DOM reference to the above TD element from the given cell, or null.
64207 *
64208 * @method getAboveTdEl
64209 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
64210 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
64211 * @param forcePrimary {Boolean} (optional) If true, will only return TD elements
64212 * from rows that correspond to Records. Non-primary rows (such as row expansions)
64213 * will be skipped.
64214 * @return {HTMLElement} Reference to above TD element, or null.
64215 */
64216getAboveTdEl : function(cell, forcePrimary) {
64217    var elCell = this.getTdEl(cell);
64218    if(elCell) {
64219        var elPreviousRow = this.getPreviousTrEl(elCell, forcePrimary);
64220        if(elPreviousRow ) {
64221            var cellIndex = this.getCellIndex(elCell);
64222            if(elPreviousRow.cells && elPreviousRow.cells.length > 0) {
64223                return elPreviousRow.cells[cellIndex] ? elPreviousRow.cells[cellIndex] : null;
64224            }
64225            else if(elPreviousRow.childNodes && elPreviousRow.childNodes.length > 0) {
64226                return elPreviousRow.childNodes[cellIndex] ? elPreviousRow.childNodes[cellIndex] : null;
64227            }
64228        }
64229    }
64230    YAHOO.log("Could not get above TD element for cell " + cell, "info", this.toString());
64231    return null;
64232},
64233
64234/**
64235 * Returns DOM reference to the below TD element from the given cell, or null.
64236 *
64237 * @method getBelowTdEl
64238 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
64239 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
64240 * @param forcePrimary {Boolean} (optional) If true, will only return TD elements
64241 * from rows that correspond to Records. Non-primary rows (such as row expansions)
64242 * will be skipped.
64243 * @return {HTMLElement} Reference to below TD element, or null.
64244 */
64245getBelowTdEl : function(cell, forcePrimary) {
64246    var elCell = this.getTdEl(cell);
64247    if(elCell) {
64248        var elNextRow = this.getNextTrEl(elCell, forcePrimary);
64249        if(elNextRow) {
64250            var cellIndex = this.getCellIndex(elCell);
64251            if(elNextRow.cells && elNextRow.cells.length > 0) {
64252                return elNextRow.cells[cellIndex] ? elNextRow.cells[cellIndex] : null;
64253            }
64254            else if(elNextRow.childNodes && elNextRow.childNodes.length > 0) {
64255                return elNextRow.childNodes[cellIndex] ? elNextRow.childNodes[cellIndex] : null;
64256            }
64257        }
64258    }
64259    YAHOO.log("Could not get below TD element for cell " + cell, "info", this.toString());
64260    return null;
64261},
64262
64263/**
64264 * Returns DOM reference to a TH liner element. Needed to normalize for resizeable 
64265 * Columns, which have an additional resizer liner DIV element between the TH
64266 * element and the liner DIV element. 
64267 *
64268 * @method getThLinerEl
64269 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
64270 * DOM element reference, or string ID.
64271 * @return {HTMLElement} Reference to TH liner element.
64272 */
64273getThLinerEl : function(theadCell) {
64274    var oColumn = this.getColumn(theadCell);
64275    return (oColumn) ? oColumn.getThLinerEl() : null;
64276},
64277
64278/**
64279 * Returns DOM reference to a TH element.
64280 *
64281 * @method getThEl
64282 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
64283 * DOM element reference, or string ID.
64284 * @return {HTMLElement} Reference to TH element.
64285 */
64286getThEl : function(theadCell) {
64287    var elTh;
64288
64289    // Validate Column instance
64290    if(theadCell instanceof YAHOO.widget.Column) {
64291        var oColumn = theadCell;
64292        elTh = oColumn.getThEl();
64293        if(elTh) {
64294            return elTh;
64295        }
64296    }
64297    // Validate HTML element
64298    else {
64299        var el = Dom.get(theadCell);
64300
64301        if(el && (el.ownerDocument == document)) {
64302            // Validate TH element
64303            if(el.nodeName.toLowerCase() != "th") {
64304                // Traverse up the DOM to find the corresponding TR element
64305                elTh = Dom.getAncestorByTagName(el,"th");
64306            }
64307            else {
64308                elTh = el;
64309            }
64310
64311            return elTh;
64312        }
64313    }
64314
64315    return null;
64316},
64317
64318/**
64319 * Returns the page row index of given primary row. Returns null if the row is not on the
64320 * current DataTable page, or if row is not considered a primary row (i.e., row
64321 * extensions).
64322 *
64323 * @method getTrIndex
64324 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
64325 * string reference to an element within the DataTable page, a Record instance,
64326 * or a Record's RecordSet index.
64327 * @return {Number} Page row index, or null if data row does not exist or is not on current page.
64328 */
64329getTrIndex : function(row) {
64330    var record = this.getRecord(row),
64331        index = this.getRecordIndex(record),
64332        tr;
64333    if(record) {
64334        tr = this.getTrEl(record);
64335        if(tr) {
64336            return tr.sectionRowIndex;
64337        }
64338        else {
64339            var oPaginator = this.get("paginator");
64340            if(oPaginator) {
64341                return oPaginator.get('recordOffset') + index;
64342            }
64343            else {
64344                return index;
64345            }
64346        }
64347    }
64348    YAHOO.log("Could not get page row index for row " + row, "info", this.toString());
64349    return null;
64350},
64351
64352
64353
64354
64355
64356
64357
64358
64359
64360
64361
64362
64363
64364
64365
64366
64367
64368
64369
64370
64371
64372
64373
64374
64375
64376
64377
64378
64379
64380
64381
64382
64383
64384
64385
64386
64387
64388
64389
64390
64391
64392
64393
64394
64395
64396
64397// TABLE FUNCTIONS
64398
64399/**
64400 * Loads new data. Convenience method that calls DataSource's sendRequest()
64401 * method under the hood.
64402 *
64403 * @method load
64404 * @param oConfig {object} Optional configuration parameters:
64405 *
64406 * <dl>
64407 * <dt>request</dt><dd>Pass in a new request, or initialRequest is used.</dd>
64408 * <dt>callback</dt><dd>Pass in DataSource sendRequest() callback object, or the following is used:
64409 *    <dl>
64410 *      <dt>success</dt><dd>datatable.onDataReturnInitializeTable</dd>
64411 *      <dt>failure</dt><dd>datatable.onDataReturnInitializeTable</dd>
64412 *      <dt>scope</dt><dd>datatable</dd>
64413 *      <dt>argument</dt><dd>datatable.getState()</dd>
64414 *    </dl>
64415 * </dd>
64416 * <dt>datasource</dt><dd>Pass in a new DataSource instance to override the current DataSource for this transaction.</dd>
64417 * </dl>
64418 */
64419load : function(oConfig) {
64420    oConfig = oConfig || {};
64421
64422    (oConfig.datasource || this._oDataSource).sendRequest(oConfig.request || this.get("initialRequest"), oConfig.callback || {
64423        success: this.onDataReturnInitializeTable,
64424        failure: this.onDataReturnInitializeTable,
64425        scope: this,
64426        argument: this.getState()
64427    });
64428},
64429
64430/**
64431 * Resets a RecordSet with the given data and populates the page view
64432 * with the new data. Any previous data, and selection and sort states are
64433 * cleared. New data should be added as a separate step. 
64434 *
64435 * @method initializeTable
64436 */
64437initializeTable : function() {
64438    // Reset init flag
64439    this._bInit = true;
64440    
64441    // Clear the RecordSet
64442    this._oRecordSet.reset();
64443
64444    // Clear the Paginator's totalRecords if paginating
64445    var pag = this.get('paginator');
64446    if (pag) {
64447        pag.set('totalRecords',0);
64448    }
64449
64450    // Clear selections
64451    this._unselectAllTrEls();
64452    this._unselectAllTdEls();
64453    this._aSelections = null;
64454    this._oAnchorRecord = null;
64455    this._oAnchorCell = null;
64456    
64457    // Clear sort
64458    this.set("sortedBy", null);
64459},
64460
64461/**
64462 * Internal wrapper calls run() on render Chain instance.
64463 *
64464 * @method _runRenderChain
64465 * @private 
64466 */
64467_runRenderChain : function() {
64468    this._oChainRender.run();
64469},
64470
64471/**
64472 * Returns array of Records for current view. For example, if paginated, it
64473 * returns the subset of Records for current page.
64474 *
64475 * @method _getViewRecords
64476 * @protected
64477 * @return {Array} Array of Records to display in current view.
64478 */
64479_getViewRecords : function() {
64480    // Paginator is enabled, show a subset of Records
64481    var oPaginator = this.get('paginator');
64482    if(oPaginator) {
64483        return this._oRecordSet.getRecords(
64484                        oPaginator.getStartIndex(),
64485                        oPaginator.getRowsPerPage());
64486    }
64487    // Not paginated, show all records
64488    else {
64489        return this._oRecordSet.getRecords();
64490    }
64491
64492},
64493
64494/**
64495 * Renders the view with existing Records from the RecordSet while
64496 * maintaining sort, pagination, and selection states. For performance, reuses
64497 * existing DOM elements when possible while deleting extraneous elements.
64498 *
64499 * @method render
64500 */
64501render : function() {
64502//YAHOO.example.Performance.trialStart = new Date();
64503
64504    this._oChainRender.stop();
64505
64506    this.fireEvent("beforeRenderEvent");
64507    YAHOO.log("DataTable rendering...", "info", this.toString());
64508
64509    var i, j, k, len,
64510        allRecords = this._getViewRecords();
64511
64512
64513    // From the top, update in-place existing rows, so as to reuse DOM elements
64514    var elTbody = this._elTbody,
64515        loopN = this.get("renderLoopSize"),
64516        nRecordsLength = allRecords.length;
64517    
64518    // Table has rows
64519    if(nRecordsLength > 0) {                
64520        elTbody.style.display = "none";
64521        while(elTbody.lastChild) {
64522            elTbody.removeChild(elTbody.lastChild);
64523        }
64524        elTbody.style.display = "";
64525
64526        // Set up the loop Chain to render rows
64527        this._oChainRender.add({
64528            method: function(oArg) {
64529                if((this instanceof DT) && this._sId) {
64530                    var i = oArg.nCurrentRecord,
64531                        endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ?
64532                                nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength),
64533                        elRow, nextSibling;
64534
64535                    elTbody.style.display = "none";
64536                    
64537                    for(; i<endRecordIndex; i++) {
64538                        elRow = Dom.get(allRecords[i].getId());
64539                        elRow = elRow || this._addTrEl(allRecords[i]);
64540                        nextSibling = elTbody.childNodes[i] || null;
64541                        elTbody.insertBefore(elRow, nextSibling);
64542                    }
64543                    elTbody.style.display = "";
64544                    
64545                    // Set up for the next loop
64546                    oArg.nCurrentRecord = i;
64547                }
64548            },
64549            scope: this,
64550            iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1,
64551            argument: {
64552                nCurrentRecord: 0,//nRecordsLength-1,  // Start at first Record
64553                nLoopLength: (loopN > 0) ? loopN : nRecordsLength
64554            },
64555            timeout: (loopN > 0) ? 0 : -1
64556        });
64557        
64558        // Post-render tasks
64559        this._oChainRender.add({
64560            method: function(oArg) {
64561                if((this instanceof DT) && this._sId) {
64562                    while(elTbody.rows.length > nRecordsLength) {
64563                        elTbody.removeChild(elTbody.lastChild);
64564                    }
64565                    this._setFirstRow();
64566                    this._setLastRow();
64567                    this._setRowStripes();
64568                    this._setSelections();
64569                }
64570            },
64571            scope: this,
64572            timeout: (loopN > 0) ? 0 : -1
64573        });
64574     
64575    }
64576    // Table has no rows
64577    else {
64578        // Set up the loop Chain to delete rows
64579        var nTotal = elTbody.rows.length;
64580        if(nTotal > 0) {
64581            this._oChainRender.add({
64582                method: function(oArg) {
64583                    if((this instanceof DT) && this._sId) {
64584                        var i = oArg.nCurrent,
64585                            loopN = oArg.nLoopLength,
64586                            nIterEnd = (i - loopN < 0) ? 0 : i - loopN;
64587    
64588                        elTbody.style.display = "none";
64589                        
64590                        for(; i>nIterEnd; i--) {
64591                            elTbody.deleteRow(-1);
64592                        }
64593                        elTbody.style.display = "";
64594                        
64595                        // Set up for the next loop
64596                        oArg.nCurrent = i;
64597                    }
64598                },
64599                scope: this,
64600                iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1,
64601                argument: {
64602                    nCurrent: nTotal, 
64603                    nLoopLength: (loopN > 0) ? loopN : nTotal
64604                },
64605                timeout: (loopN > 0) ? 0 : -1
64606            });
64607        }
64608    }
64609    this._runRenderChain();
64610},
64611
64612/**
64613 * Disables DataTable UI.
64614 *
64615 * @method disable
64616 */
64617disable : function() {
64618    this._disabled = true;
64619    var elTable = this._elTable;
64620    var elMask = this._elMask;
64621    elMask.style.width = elTable.offsetWidth + "px";
64622    elMask.style.height = elTable.offsetHeight + "px";
64623    elMask.style.left = elTable.offsetLeft + "px";
64624    elMask.style.display = "";
64625    this.fireEvent("disableEvent");
64626},
64627
64628/**
64629 * Undisables DataTable UI.
64630 *
64631 * @method undisable
64632 */
64633undisable : function() {
64634    this._disabled = false;
64635    this._elMask.style.display = "none";
64636    this.fireEvent("undisableEvent");
64637},
64638
64639 /**
64640 * Returns disabled state.
64641 *
64642 * @method isDisabled
64643 * @return {Boolean} True if UI is disabled, otherwise false
64644 */
64645isDisabled : function() {
64646    return this._disabled;
64647},
64648
64649/**
64650 * Nulls out the entire DataTable instance and related objects, removes attached
64651 * event listeners, and clears out DOM elements inside the container. After
64652 * calling this method, the instance reference should be expliclitly nulled by
64653 * implementer, as in myDataTable = null. Use with caution!
64654 *
64655 * @method destroy
64656 */
64657destroy : function() {
64658    // Store for later
64659    var instanceName = this.toString();
64660
64661    this._oChainRender.stop();
64662    
64663    // Destroy ColumnDD and ColumnResizers
64664    this._destroyColumnHelpers();
64665    
64666    // Destroy all CellEditors
64667    var oCellEditor;
64668    for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) {
64669        oCellEditor = this._oColumnSet.flat[i].editor;
64670        if(oCellEditor && oCellEditor.destroy) {
64671            oCellEditor.destroy();
64672            this._oColumnSet.flat[i].editor = null;
64673        }
64674    }
64675
64676    // Destroy Paginator
64677    this._destroyPaginator();
64678
64679    // Unhook custom events
64680    this._oRecordSet.unsubscribeAll();
64681    this.unsubscribeAll();
64682
64683    // Unhook DOM events
64684    Ev.removeListener(document, "click", this._onDocumentClick);
64685    
64686    // Clear out the container
64687    this._destroyContainerEl(this._elContainer);
64688
64689    // Null out objects
64690    for(var param in this) {
64691        if(lang.hasOwnProperty(this, param)) {
64692            this[param] = null;
64693        }
64694    }
64695    
64696    // Clean up static values
64697    DT._nCurrentCount--;
64698    
64699    if(DT._nCurrentCount < 1) {
64700        if(DT._elDynStyleNode) {
64701            document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode);
64702            DT._elDynStyleNode = null;
64703        }
64704    }
64705
64706    YAHOO.log("DataTable instance destroyed: " + instanceName);
64707},
64708
64709/**
64710 * Displays message within secondary TBODY.
64711 *
64712 * @method showTableMessage
64713 * @param sHTML {HTML} (optional) Value for innerHTML.
64714 * @param sClassName {String} (optional) Classname.
64715 */
64716showTableMessage : function(sHTML, sClassName) {
64717    var elCell = this._elMsgTd;
64718    if(lang.isString(sHTML)) {
64719        elCell.firstChild.innerHTML = sHTML;
64720    }
64721    if(lang.isString(sClassName)) {
64722        elCell.className = sClassName;
64723    }
64724
64725    this._elMsgTbody.style.display = "";
64726
64727    this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
64728    YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString());
64729},
64730
64731/**
64732 * Hides secondary TBODY.
64733 *
64734 * @method hideTableMessage
64735 */
64736hideTableMessage : function() {
64737    if(this._elMsgTbody.style.display != "none") {
64738        this._elMsgTbody.style.display = "none";
64739        this._elMsgTbody.parentNode.style.width = "";
64740        this.fireEvent("tableMsgHideEvent");
64741        YAHOO.log("DataTable message hidden", "info", this.toString());
64742    }
64743},
64744
64745/**
64746 * Brings focus to the TBODY element. Alias to focusTbodyEl.
64747 *
64748 * @method focus
64749 */
64750focus : function() {
64751    this.focusTbodyEl();
64752},
64753
64754/**
64755 * Brings focus to the THEAD element.
64756 *
64757 * @method focusTheadEl
64758 */
64759focusTheadEl : function() {
64760    this._focusEl(this._elThead);
64761},
64762
64763/**
64764 * Brings focus to the TBODY element.
64765 *
64766 * @method focusTbodyEl
64767 */
64768focusTbodyEl : function() {
64769    this._focusEl(this._elTbody);
64770},
64771
64772/**
64773 * Setting display:none on DataTable or any parent may impact width validations.
64774 * After setting display back to "", implementers should call this method to 
64775 * manually perform those validations.
64776 *
64777 * @method onShow
64778 */
64779onShow : function() {
64780    this.validateColumnWidths();
64781    
64782    for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) {
64783        col = allKeys[i];
64784        if(col._ddResizer) {
64785            col._ddResizer.resetResizerEl();
64786        }
64787    }
64788},
64789
64790
64791
64792
64793
64794
64795
64796
64797
64798
64799
64800
64801
64802
64803
64804
64805
64806
64807
64808
64809
64810
64811
64812
64813
64814
64815
64816
64817
64818
64819
64820
64821
64822
64823
64824
64825
64826
64827
64828
64829
64830
64831
64832
64833
64834
64835
64836
64837
64838
64839
64840
64841
64842
64843
64844
64845
64846
64847
64848
64849
64850
64851
64852
64853
64854
64855
64856// RECORDSET FUNCTIONS
64857
64858/**
64859 * Returns Record index for given TR element or page row index.
64860 *
64861 * @method getRecordIndex
64862 * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
64863 * element reference or page row index.
64864 * @return {Number} Record's RecordSet index, or null.
64865 */
64866getRecordIndex : function(row) {
64867    var nTrIndex;
64868
64869    if(!lang.isNumber(row)) {
64870        // By Record
64871        if(row instanceof YAHOO.widget.Record) {
64872            return this._oRecordSet.getRecordIndex(row);
64873        }
64874        // By element reference
64875        else {
64876            // Find the TR element
64877            var el = this.getTrEl(row);
64878            if(el) {
64879                nTrIndex = el.sectionRowIndex;
64880            }
64881        }
64882    }
64883    // By page row index
64884    else {
64885        nTrIndex = row;
64886    }
64887
64888    if(lang.isNumber(nTrIndex)) {
64889        var oPaginator = this.get("paginator");
64890        if(oPaginator) {
64891            return oPaginator.get('recordOffset') + nTrIndex;
64892        }
64893        else {
64894            return nTrIndex;
64895        }
64896    }
64897
64898    YAHOO.log("Could not get Record index for row " + row, "info", this.toString());
64899    return null;
64900},
64901
64902/**
64903 * For the given identifier, returns the associated Record instance.
64904 *
64905 * @method getRecord
64906 * @param row {HTMLElement | Number | String} DOM reference to a TR element (or
64907 * child of a TR element), RecordSet position index, or Record ID.
64908 * @return {YAHOO.widget.Record} Record instance.
64909 */
64910getRecord : function(row) {
64911    var oRecord = this._oRecordSet.getRecord(row);
64912
64913    if(!oRecord) {
64914        // Validate TR element
64915        var elRow = this.getTrEl(row);
64916        if(elRow) {
64917            oRecord = this._oRecordSet.getRecord(elRow.id);
64918        }
64919    }
64920
64921    if(oRecord instanceof YAHOO.widget.Record) {
64922        return this._oRecordSet.getRecord(oRecord);
64923    }
64924    else {
64925        YAHOO.log("Could not get Record for row at " + row, "info", this.toString());
64926        return null;
64927    }
64928},
64929
64930
64931
64932
64933
64934
64935
64936
64937
64938
64939
64940
64941
64942
64943
64944
64945
64946
64947
64948
64949
64950
64951
64952
64953
64954
64955
64956
64957
64958
64959
64960
64961
64962
64963
64964
64965
64966
64967
64968
64969
64970
64971
64972
64973
64974
64975// COLUMN FUNCTIONS
64976
64977/**
64978 * For the given identifier, returns the associated Column instance. Note: For
64979 * getting Columns by Column ID string, please use the method getColumnById().
64980 *
64981 * @method getColumn
64982 * @param column {HTMLElement | String | Number} TH/TD element (or child of a
64983 * TH/TD element), a Column key, or a ColumnSet key index.
64984 * @return {YAHOO.widget.Column} Column instance.
64985 */
64986getColumn : function(column) {
64987    var oColumn = this._oColumnSet.getColumn(column);
64988
64989    if(!oColumn) {
64990        // Validate TD element
64991        var elCell = this.getTdEl(column);
64992        if(elCell) {
64993            oColumn = this._oColumnSet.getColumn(this.getCellIndex(elCell));
64994        }
64995        // Validate TH element
64996        else {
64997            elCell = this.getThEl(column);
64998            if(elCell) {
64999                // Find by TH el ID
65000                var allColumns = this._oColumnSet.flat;
65001                for(var i=0, len=allColumns.length; i<len; i++) {
65002                    if(allColumns[i].getThEl().id === elCell.id) {
65003                        oColumn = allColumns[i];
65004                    } 
65005                }
65006            }
65007        }
65008    }
65009    if(!oColumn) {
65010        YAHOO.log("Could not get Column for column at " + column, "info", this.toString());
65011    }
65012    return oColumn;
65013},
65014
65015/**
65016 * For the given Column ID, returns the associated Column instance. Note: For
65017 * getting Columns by key, please use the method getColumn().
65018 *
65019 * @method getColumnById
65020 * @param column {String} Column ID string.
65021 * @return {YAHOO.widget.Column} Column instance.
65022 */
65023getColumnById : function(column) {
65024    return this._oColumnSet.getColumnById(column);
65025},
65026
65027/**
65028 * For the given Column instance, returns next direction to sort.
65029 *
65030 * @method getColumnSortDir
65031 * @param oColumn {YAHOO.widget.Column} Column instance.
65032 * @param oSortedBy {Object} (optional) Specify the state, or use current state. 
65033 * @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC.
65034 */
65035getColumnSortDir : function(oColumn, oSortedBy) {
65036    // Backward compatibility
65037    if(oColumn.sortOptions && oColumn.sortOptions.defaultDir) {
65038        if(oColumn.sortOptions.defaultDir == "asc") {
65039            oColumn.sortOptions.defaultDir = DT.CLASS_ASC;
65040        }
65041        else if (oColumn.sortOptions.defaultDir == "desc") {
65042            oColumn.sortOptions.defaultDir = DT.CLASS_DESC;
65043        }
65044    }
65045    
65046    // What is the Column's default sort direction?
65047    var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC;
65048
65049    // Is the Column currently sorted?
65050    var bSorted = false;
65051    oSortedBy = oSortedBy || this.get("sortedBy");
65052    if(oSortedBy && (oSortedBy.key === oColumn.key)) {
65053        bSorted = true;
65054        if(oSortedBy.dir) {
65055            sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
65056        }
65057        else {
65058            sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
65059        }
65060    }
65061    return sortDir;
65062},
65063
65064/**
65065 * Overridable method gives implementers a hook to show loading message before
65066 * sorting Column.
65067 *
65068 * @method doBeforeSortColumn
65069 * @param oColumn {YAHOO.widget.Column} Column instance.
65070 * @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or
65071 * YAHOO.widget.DataTable.CLASS_DESC.
65072 * @return {Boolean} Return true to continue sorting Column.
65073 */
65074doBeforeSortColumn : function(oColumn, sSortDir) {
65075    this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
65076    return true;
65077},
65078
65079/**
65080 * Sorts given Column. If "dynamicData" is true, current selections are purged before
65081 * a request is sent to the DataSource for data for the new state (using the
65082 * request returned by "generateRequest()").
65083 *
65084 * @method sortColumn
65085 * @param oColumn {YAHOO.widget.Column} Column instance.
65086 * @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or
65087 * YAHOO.widget.DataTable.CLASS_DESC
65088 */
65089sortColumn : function(oColumn, sDir) {
65090    if(oColumn && (oColumn instanceof YAHOO.widget.Column)) {
65091        if(!oColumn.sortable) {
65092            Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE);
65093        }
65094        
65095        // Validate given direction
65096        if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) {
65097            sDir = null;
65098        }
65099        
65100        // Get the sort dir
65101        var sSortDir = sDir || this.getColumnSortDir(oColumn);
65102
65103        // Is the Column currently sorted?
65104        var oSortedBy = this.get("sortedBy") || {};
65105        var bSorted = (oSortedBy.key === oColumn.key) ? true : false;
65106
65107        var ok = this.doBeforeSortColumn(oColumn, sSortDir);
65108        if(ok) {
65109            // Server-side sort
65110            if(this.get("dynamicData")) {
65111                // Get current state
65112                var oState = this.getState();
65113                
65114                // Reset record offset, if paginated
65115                if(oState.pagination) {
65116                    oState.pagination.recordOffset = 0;
65117                }
65118                
65119                // Update sortedBy to new values
65120                oState.sortedBy = {
65121                    key: oColumn.key,
65122                    dir: sSortDir
65123                };
65124                
65125                // Get the request for the new state
65126                var request = this.get("generateRequest")(oState, this);
65127
65128                // Purge selections
65129                this.unselectAllRows();
65130                this.unselectAllCells();
65131
65132                // Send request for new data
65133                var callback = {
65134                    success : this.onDataReturnSetRows,
65135                    failure : this.onDataReturnSetRows,
65136                    argument : oState, // Pass along the new state to the callback
65137                    scope : this
65138                };
65139                this._oDataSource.sendRequest(request, callback);            
65140            }
65141            // Client-side sort
65142            else {
65143                // Is there a custom sort handler function defined?
65144                var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ?
65145                        // Custom sort function
65146                        oColumn.sortOptions.sortFunction : null;
65147                   
65148                // Sort the Records
65149                if(!bSorted || sDir || sortFnc) {
65150                    // Default sort function if necessary
65151                    sortFnc = sortFnc || this.get("sortFunction");
65152                    // Get the field to sort
65153                    var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field;
65154
65155                    // Sort the Records        
65156                    this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false), sField);
65157                }
65158                // Just reverse the Records
65159                else {
65160                    this._oRecordSet.reverseRecords();
65161                }
65162        
65163                // Reset to first page if paginated
65164                var oPaginator = this.get('paginator');
65165                if (oPaginator) {
65166                    // Set page silently, so as not to fire change event.
65167                    oPaginator.setPage(1,true);
65168                }
65169        
65170                // Update UI via sortedBy
65171                this.render();
65172                this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn}); 
65173            }       
65174            
65175            this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir});
65176            YAHOO.log("Column \"" + oColumn.key + "\" sorted \"" + sSortDir + "\"", "info", this.toString());
65177            return;
65178        }
65179    }
65180    YAHOO.log("Could not sort Column \"" + oColumn.key + "\"", "warn", this.toString());
65181},
65182
65183/**
65184 * Sets given Column to given pixel width. If new width is less than minimum
65185 * width, sets to minimum width. Updates oColumn.width value.
65186 *
65187 * @method setColumnWidth
65188 * @param oColumn {YAHOO.widget.Column} Column instance.
65189 * @param nWidth {Number} New width in pixels. A null value auto-sizes Column,
65190 * subject to minWidth and maxAutoWidth validations. 
65191 */
65192setColumnWidth : function(oColumn, nWidth) {
65193    if(!(oColumn instanceof YAHOO.widget.Column)) {
65194        oColumn = this.getColumn(oColumn);
65195    }
65196    if(oColumn) {
65197        // Validate new width against minimum width
65198        if(lang.isNumber(nWidth)) {
65199            // This is why we must require a Number... :-|
65200            nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
65201
65202            // Save state
65203            oColumn.width = nWidth;
65204            
65205            // Resize the DOM elements
65206            this._setColumnWidth(oColumn, nWidth+"px");
65207            
65208            this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
65209            YAHOO.log("Set width of Column " + oColumn + " to " + nWidth + "px", "info", this.toString());
65210        }
65211        // Unsets a width to auto-size
65212        else if(nWidth === null) {
65213            // Save state
65214            oColumn.width = nWidth;
65215            
65216            // Resize the DOM elements
65217            this._setColumnWidth(oColumn, "auto");
65218            this.validateColumnWidths(oColumn);
65219            this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
65220            YAHOO.log("Column " + oColumn + " width unset", "info", this.toString());
65221        }
65222                
65223        // Bug 2339454: resize then sort misaligment
65224        this._clearTrTemplateEl();
65225    }
65226    else {
65227        YAHOO.log("Could not set width of Column " + oColumn + " to " + nWidth + "px", "warn", this.toString());
65228    }
65229},
65230
65231/**
65232 * Sets liner DIV elements of given Column to given width. When value should be
65233 * auto-calculated to fit content overflow is set to visible, otherwise overflow
65234 * is set to hidden. No validations against minimum width and no updating
65235 * Column.width value.
65236 *
65237 * @method _setColumnWidth
65238 * @param oColumn {YAHOO.widget.Column} Column instance.
65239 * @param sWidth {String} New width value.
65240 * @param sOverflow {String} Should be "hidden" when Column width is explicitly
65241 * being set to a value, but should be "visible" when Column is meant to auto-fit content.  
65242 * @private
65243 */
65244_setColumnWidth : function(oColumn, sWidth, sOverflow) {
65245    if(oColumn && (oColumn.getKeyIndex() !== null)) {
65246        sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden');
65247    
65248        // Dynamic style algorithm
65249        if(!DT._bDynStylesFallback) {
65250            this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow);
65251        }
65252        // Dynamic function algorithm
65253        else {
65254            this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow);
65255        }
65256    }
65257    else {
65258        YAHOO.log("Could not set width of unknown Column " + oColumn + " to " + sWidth, "warn", this.toString());
65259    }
65260},
65261
65262/**
65263 * Updates width of a Column's liner DIV elements by dynamically creating a
65264 * STYLE node and writing and updating CSS style rules to it. If this fails during
65265 * runtime, the fallback method _setColumnWidthDynFunction() will be called.
65266 * Notes: This technique is not performant in IE6. IE7 crashes if DataTable is
65267 * nested within another TABLE element. For these cases, it is recommended to
65268 * use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE.
65269 *
65270 * @method _setColumnWidthDynStyles
65271 * @param oColumn {YAHOO.widget.Column} Column instance.
65272 * @param sWidth {String} New width value.
65273 * @private
65274 */
65275_setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) {
65276    var s = DT._elDynStyleNode,
65277        rule;
65278    
65279    // Create a new STYLE node
65280    if(!s) {
65281        s = document.createElement('style');
65282        s.type = 'text/css';
65283        s = document.getElementsByTagName('head').item(0).appendChild(s);
65284        DT._elDynStyleNode = s;
65285    }
65286    
65287    // We have a STYLE node to update
65288    if(s) {
65289        // Use unique classname for this Column instance as a hook for resizing
65290        var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER;
65291        
65292        // Hide for performance
65293        if(this._elTbody) {
65294            this._elTbody.style.display = 'none';
65295        }
65296        
65297        rule = DT._oDynStyles[sClassname];
65298
65299        // The Column does not yet have a rule
65300        if(!rule) {
65301            if(s.styleSheet && s.styleSheet.addRule) {
65302                s.styleSheet.addRule(sClassname,"overflow:"+sOverflow);
65303                s.styleSheet.addRule(sClassname,'width:'+sWidth);
65304                rule = s.styleSheet.rules[s.styleSheet.rules.length-1];
65305                DT._oDynStyles[sClassname] = rule;
65306            }
65307            else if(s.sheet && s.sheet.insertRule) {
65308                s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length);
65309                rule = s.sheet.cssRules[s.sheet.cssRules.length-1];
65310                DT._oDynStyles[sClassname] = rule;
65311            }
65312        }
65313        // We have a rule to update
65314        else {
65315            rule.style.overflow = sOverflow;
65316            rule.style.width = sWidth;
65317        } 
65318        
65319        // Unhide
65320        if(this._elTbody) {
65321            this._elTbody.style.display = '';
65322        }
65323    }
65324    
65325    // That was not a success, we must call the fallback routine
65326    if(!rule) {
65327        DT._bDynStylesFallback = true;
65328        this._setColumnWidthDynFunction(oColumn, sWidth);
65329    }
65330},
65331
65332/**
65333 * Updates width of a Column's liner DIV elements by dynamically creating a
65334 * function to update all element style properties in one pass. Note: This
65335 * technique is not supported in sandboxed environments that prohibit EVALs.    
65336 *
65337 * @method _setColumnWidthDynFunction
65338 * @param oColumn {YAHOO.widget.Column} Column instance.
65339 * @param sWidth {String} New width value.
65340 * @private
65341 */
65342_setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) {
65343    // TODO: why is this here?
65344    if(sWidth == 'auto') {
65345        sWidth = ''; 
65346    }
65347    
65348    // Create one function for each value of rows.length
65349    var rowslen = this._elTbody ? this._elTbody.rows.length : 0;
65350    
65351    // Dynamically create the function
65352    if (!this._aDynFunctions[rowslen]) {
65353        
65354        //Compile a custom function to do all the liner div width
65355        //assignments at the same time.  A unique function is required
65356        //for each unique number of rows in _elTbody.  This will
65357        //result in a function declaration like:
65358        //function (oColumn,sWidth,sOverflow) {
65359        //    var colIdx = oColumn.getKeyIndex();
65360        //    oColumn.getThLinerEl().style.overflow =
65361        //    this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow =
65362        //    this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow =
65363        //    ... (for all row indices in this._elTbody.rows.length - 1)
65364        //    this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow =
65365        //    sOverflow;
65366        //    oColumn.getThLinerEl().style.width =
65367        //    this._elTbody.rows[0].cells[colIdx].firstChild.style.width =
65368        //    this._elTbody.rows[1].cells[colIdx].firstChild.style.width =
65369        //    ... (for all row indices in this._elTbody.rows.length - 1)
65370        //    this._elTbody.rows[99].cells[colIdx].firstChild.style.width =
65371        //    sWidth;
65372        //}
65373        
65374        var i,j,k;
65375        var resizerDef = [
65376            'var colIdx=oColumn.getKeyIndex();',
65377            'oColumn.getThLinerEl().style.overflow='
65378        ];
65379        for (i=rowslen-1, j=2; i >= 0; --i) {
65380            resizerDef[j++] = 'this._elTbody.rows[';
65381            resizerDef[j++] = i;
65382            resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow=';
65383        }
65384        resizerDef[j] = 'sOverflow;';
65385        resizerDef[j+1] = 'oColumn.getThLinerEl().style.width=';
65386        for (i=rowslen-1, k=j+2; i >= 0; --i) {
65387            resizerDef[k++] = 'this._elTbody.rows[';
65388            resizerDef[k++] = i;
65389            resizerDef[k++] = '].cells[colIdx].firstChild.style.width=';
65390        }
65391        resizerDef[k] = 'sWidth;';
65392        this._aDynFunctions[rowslen] =
65393            new Function('oColumn','sWidth','sOverflow',resizerDef.join(''));
65394    }
65395    
65396    // Get the function to execute
65397    var resizerFn = this._aDynFunctions[rowslen];
65398
65399    // TODO: Hide TBODY for performance in _setColumnWidthDynFunction?
65400    if (resizerFn) {
65401        resizerFn.call(this,oColumn,sWidth,sOverflow);
65402    }
65403},
65404
65405/**
65406 * For one or all Columns, when Column is not hidden, width is not set, and minWidth
65407 * and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth.
65408 *
65409 * @method validateColumnWidths
65410 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
65411 */
65412validateColumnWidths : function(oColumn) {
65413    var elColgroup = this._elColgroup;
65414    var elColgroupClone = elColgroup.cloneNode(true);
65415    var bNeedsValidation = false;
65416    var allKeys = this._oColumnSet.keys;
65417    var elThLiner;
65418    // Validate just one Column's minWidth and/or maxAutoWidth
65419    if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) {
65420            elThLiner = oColumn.getThLinerEl();
65421            if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
65422                elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width = 
65423                        oColumn.minWidth + 
65424                        (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
65425                        (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
65426                bNeedsValidation = true;
65427            }
65428            else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
65429                this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
65430            }
65431    }
65432    // Validate all Columns
65433    else {
65434        for(var i=0, len=allKeys.length; i<len; i++) {
65435            oColumn = allKeys[i];
65436            if(!oColumn.hidden && !oColumn.width) {
65437                elThLiner = oColumn.getThLinerEl();
65438                if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
65439                    elColgroupClone.childNodes[i].style.width = 
65440                            oColumn.minWidth + 
65441                            (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
65442                            (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
65443                    bNeedsValidation = true;
65444                }
65445                else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
65446                    this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
65447                }
65448            }
65449        }
65450    }
65451    if(bNeedsValidation) {
65452        elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup);
65453        this._elColgroup = elColgroupClone;
65454    }
65455},
65456
65457/**
65458 * Clears minWidth.
65459 *
65460 * @method _clearMinWidth
65461 * @param oColumn {YAHOO.widget.Column} Which Column.
65462 * @private
65463 */
65464_clearMinWidth : function(oColumn) {
65465    if(oColumn.getKeyIndex() !== null) {
65466        this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = '';
65467    }
65468},
65469
65470/**
65471 * Restores minWidth.
65472 *
65473 * @method _restoreMinWidth
65474 * @param oColumn {YAHOO.widget.Column} Which Column.
65475 * @private
65476 */
65477_restoreMinWidth : function(oColumn) {
65478    if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) {
65479        this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px';
65480    }
65481},
65482
65483/**
65484 * Hides given Column. NOTE: You cannot hide/show nested Columns. You can only
65485 * hide/show non-nested Columns, and top-level parent Columns (which will
65486 * hide/show all children Columns).
65487 *
65488 * @method hideColumn
65489 * @param oColumn {YAHOO.widget.Column | HTMLElement | String | Number} Column
65490 * instance, TH/TD element (or child of a TH/TD element), a Column key, or a
65491 * ColumnSet key index.
65492 */
65493hideColumn : function(oColumn) {
65494    if(!(oColumn instanceof YAHOO.widget.Column)) {
65495        oColumn = this.getColumn(oColumn);
65496    }
65497    // Only top-level Columns can get hidden due to issues in FF2 and SF3
65498    if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) {
65499        
65500        var allrows = this.getTbodyEl().rows;
65501        var l = allrows.length;
65502        var allDescendants = this._oColumnSet.getDescendants(oColumn);
65503        
65504        // Hide each nested Column
65505        for(var i=0, len=allDescendants.length; i<len; i++) {
65506            var thisColumn = allDescendants[i];
65507            thisColumn.hidden = true;
65508
65509            // Style the head cell
65510            Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
65511            
65512            // Does this Column have body cells?
65513            var thisKeyIndex = thisColumn.getKeyIndex();
65514            if(thisKeyIndex !== null) {                    
65515                // Clear minWidth
65516                this._clearMinWidth(oColumn);
65517                
65518                // Style the body cells
65519                for(var j=0;j<l;j++) {
65520                    Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
65521                }
65522            }
65523            
65524            this.fireEvent("columnHideEvent",{column:thisColumn});
65525            YAHOO.log("Column \"" + oColumn.key + "\" hidden", "info", this.toString());
65526        }
65527      
65528        this._repaintOpera();
65529        this._clearTrTemplateEl();
65530    }
65531    else {
65532        YAHOO.log("Could not hide Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be hidden", "warn", this.toString());
65533    }
65534},
65535
65536/**
65537 * Shows given Column. NOTE: You cannot hide/show nested Columns. You can only
65538 * hide/show non-nested Columns, and top-level parent Columns (which will
65539 * hide/show all children Columns).
65540 *
65541 * @method showColumn
65542 * @param oColumn {YAHOO.widget.Column | HTMLElement | String | Number} Column
65543 * instance, TH/TD element (or child of a TH/TD element), a Column key, or a
65544 * ColumnSet key index.
65545 */
65546showColumn : function(oColumn) {
65547    if(!(oColumn instanceof YAHOO.widget.Column)) {
65548        oColumn = this.getColumn(oColumn);
65549    }
65550    // Only top-level Columns can get hidden
65551    if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) {
65552        var allrows = this.getTbodyEl().rows;
65553        var l = allrows.length;
65554        var allDescendants = this._oColumnSet.getDescendants(oColumn);
65555        
65556        // Show each nested Column
65557        for(var i=0, len=allDescendants.length; i<len; i++) {
65558            var thisColumn = allDescendants[i];
65559            thisColumn.hidden = false;
65560            
65561            // Unstyle the head cell
65562            Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
65563
65564            // Does this Column have body cells?
65565            var thisKeyIndex = thisColumn.getKeyIndex();
65566            if(thisKeyIndex !== null) {
65567                // Restore minWidth
65568                this._restoreMinWidth(oColumn);
65569                
65570            
65571                // Unstyle the body cells
65572                for(var j=0;j<l;j++) {
65573                    Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
65574                }
65575            }
65576
65577            this.fireEvent("columnShowEvent",{column:thisColumn});
65578            YAHOO.log("Column \"" + oColumn.key + "\" shown", "info", this.toString());
65579        }
65580        this._clearTrTemplateEl();
65581    }
65582    else {
65583        YAHOO.log("Could not show Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be shown", "warn", this.toString());
65584    }
65585},
65586
65587/**
65588 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
65589 * non-nested Columns, and top-level parent Columns (which will remove all
65590 * children Columns).
65591 *
65592 * @method removeColumn
65593 * @param oColumn {YAHOO.widget.Column} Column instance.
65594 * @return oColumn {YAHOO.widget.Column} Removed Column instance.
65595 */
65596removeColumn : function(oColumn) {
65597    // Validate Column
65598    if(!(oColumn instanceof YAHOO.widget.Column)) {
65599        oColumn = this.getColumn(oColumn);
65600    }
65601    if(oColumn) {
65602        var nColTreeIndex = oColumn.getTreeIndex();
65603        if(nColTreeIndex !== null) {
65604            // Which key index(es)
65605            var i, len,
65606                aKeyIndexes = oColumn.getKeyIndex();
65607            // Must be a parent Column
65608            if(aKeyIndexes === null) {
65609                var descKeyIndexes = [];
65610                var allDescendants = this._oColumnSet.getDescendants(oColumn);
65611                for(i=0, len=allDescendants.length; i<len; i++) {
65612                    // Is this descendant a key Column?
65613                    var thisKey = allDescendants[i].getKeyIndex();
65614                    if(thisKey !== null) {
65615                        descKeyIndexes[descKeyIndexes.length] = thisKey;
65616                    }
65617                }
65618                if(descKeyIndexes.length > 0) {
65619                    aKeyIndexes = descKeyIndexes;
65620                }
65621            }
65622            // Must be a key Column
65623            else {
65624                aKeyIndexes = [aKeyIndexes];
65625            }
65626            
65627            if(aKeyIndexes !== null) {
65628                // Sort the indexes so we can remove from the right
65629                aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
65630                
65631                // Destroy previous THEAD
65632                this._destroyTheadEl();
65633    
65634                // Create new THEAD
65635                var aOrigColumnDefs = this._oColumnSet.getDefinitions();
65636                oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0];
65637                this._initColumnSet(aOrigColumnDefs);
65638                this._initTheadEl();
65639                
65640                // Remove COL
65641                for(i=aKeyIndexes.length-1; i>-1; i--) {
65642                    this._removeColgroupColEl(aKeyIndexes[i]);
65643                }
65644                
65645                // Remove TD
65646                var allRows = this._elTbody.rows;
65647                if(allRows.length > 0) {
65648                    var loopN = this.get("renderLoopSize"),
65649                        loopEnd = allRows.length;
65650                    this._oChainRender.add({
65651                        method: function(oArg) {
65652                            if((this instanceof DT) && this._sId) {
65653                                var i = oArg.nCurrentRow,
65654                                    len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
65655                                    aIndexes = oArg.aIndexes,
65656                                    j;
65657                                for(; i < len; ++i) {
65658                                    for(j = aIndexes.length-1; j>-1; j--) {
65659                                        allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]);
65660                                    }
65661                                }
65662                                oArg.nCurrentRow = i;
65663                            }
65664                        },
65665                        iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
65666                        argument: {nCurrentRow:0, aIndexes:aKeyIndexes},
65667                        scope: this,
65668                        timeout: (loopN > 0) ? 0 : -1
65669                    });
65670                    this._runRenderChain();
65671                }
65672        
65673                this.fireEvent("columnRemoveEvent",{column:oColumn});
65674                YAHOO.log("Column \"" + oColumn.key + "\" removed", "info", this.toString());
65675                return oColumn;
65676            }
65677        }
65678    }
65679    YAHOO.log("Could not remove Column \"" + oColumn.key + "\". Only non-ne
65679sted Columns can be removed", "warn", this.toString());
65680},
65681
65682/**
65683 * Inserts given Column at the index if given, otherwise at the end. NOTE: You
65684 * can only add non-nested Columns and top-level parent Columns. You cannot add
65685 * a nested Column to an existing parent.
65686 *
65687 * @method insertColumn
65688 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
65689 * definition or a Column instance.
65690 * @param index {Number} (optional) New tree index.
65691 * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
65692 */
65693insertColumn : function(oColumn, index) {
65694    // Validate Column
65695    if(oColumn instanceof YAHOO.widget.Column) {
65696        oColumn = oColumn.getDefinition();
65697    }
65698    else if(oColumn.constructor !== Object) {
65699        YAHOO.log("Could not insert Column \"" + oColumn + "\" due to invalid argument", "warn", this.toString());
65700        return;
65701    }
65702    
65703    // Validate index or append new Column to the end of the ColumnSet
65704    var oColumnSet = this._oColumnSet;
65705    if(!lang.isValue(index) || !lang.isNumber(index)) {
65706        index = oColumnSet.tree[0].length;
65707    }
65708    
65709    // Destroy previous THEAD
65710    this._destroyTheadEl();
65711    
65712    // Create new THEAD
65713    var aNewColumnDefs = this._oColumnSet.getDefinitions();
65714    aNewColumnDefs.splice(index, 0, oColumn);
65715    this._initColumnSet(aNewColumnDefs);
65716    this._initTheadEl();
65717    
65718    // Need to refresh the reference
65719    oColumnSet = this._oColumnSet;
65720    var oNewColumn = oColumnSet.tree[0][index];
65721    
65722    // Get key index(es) for new Column
65723    var i, len,
65724        descKeyIndexes = [];
65725    var allDescendants = oColumnSet.getDescendants(oNewColumn);
65726    for(i=0, len=allDescendants.length; i<len; i++) {
65727        // Is this descendant a key Column?
65728        var thisKey = allDescendants[i].getKeyIndex();
65729        if(thisKey !== null) {
65730            descKeyIndexes[descKeyIndexes.length] = thisKey;
65731        }
65732    }
65733    
65734    if(descKeyIndexes.length > 0) {  
65735        // Sort the indexes
65736        var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
65737        
65738        // Add COL
65739        for(i=descKeyIndexes.length-1; i>-1; i--) {
65740            this._insertColgroupColEl(descKeyIndexes[i]);
65741        }
65742            
65743        // Add TD
65744        var allRows = this._elTbody.rows;
65745        if(allRows.length > 0) {
65746            var loopN = this.get("renderLoopSize"),
65747                loopEnd = allRows.length;
65748            
65749            // Get templates for each new TD
65750            var aTdTemplates = [],
65751                elTdTemplate;
65752            for(i=0, len=descKeyIndexes.length; i<len; i++) {
65753                var thisKeyIndex = descKeyIndexes[i];
65754                elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true);
65755                elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1));
65756                aTdTemplates[thisKeyIndex] = elTdTemplate;
65757            }
65758            
65759            this._oChainRender.add({
65760                method: function(oArg) {
65761                    if((this instanceof DT) && this._sId) {
65762                        var i = oArg.nCurrentRow, j,
65763                            descKeyIndexes = oArg.descKeyIndexes,
65764                            len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
65765                            nextSibling;
65766                        for(; i < len; ++i) {
65767                            nextSibling = allRows[i].childNodes[newIndex] || null;
65768                            for(j=descKeyIndexes.length-1; j>-1; j--) {
65769                                allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling);
65770                            }
65771                        }
65772                        oArg.nCurrentRow = i;
65773                    }
65774                },
65775                iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
65776                argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes},
65777                scope: this,
65778                timeout: (loopN > 0) ? 0 : -1
65779            });
65780            this._runRenderChain(); 
65781        }
65782
65783        this.fireEvent("columnInsertEvent",{column:oColumn,index:index});
65784        YAHOO.log("Column \"" + oColumn.key + "\" inserted into index " + index, "info", this.toString());
65785        return oNewColumn;
65786    }
65787},
65788
65789/**
65790 * Removes given Column and inserts into given tree index. NOTE: You
65791 * can only reorder non-nested Columns and top-level parent Columns. You cannot
65792 * reorder a nested Column to an existing parent.
65793 *
65794 * @method reorderColumn
65795 * @param oColumn {YAHOO.widget.Column} Column instance.
65796 * @param index {Number} New tree index.
65797 * @return oColumn {YAHOO.widget.Column} Reordered Column instance. 
65798 */
65799reorderColumn : function(oColumn, index) {
65800    // Validate Column and new index
65801    if(!(oColumn instanceof YAHOO.widget.Column)) {
65802        oColumn = this.getColumn(oColumn);
65803    }
65804    if(oColumn && YAHOO.lang.isNumber(index)) {
65805        var nOrigTreeIndex = oColumn.getTreeIndex();
65806        if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) {
65807            // Which key index(es)
65808            var i, len,
65809                aOrigKeyIndexes = oColumn.getKeyIndex(),
65810                allDescendants,
65811                descKeyIndexes = [],
65812                thisKey;
65813            // Must be a parent Column...
65814            if(aOrigKeyIndexes === null) {
65815                allDescendants = this._oColumnSet.getDescendants(oColumn);
65816                for(i=0, len=allDescendants.length; i<len; i++) {
65817                    // Is this descendant a key Column?
65818                    thisKey = allDescendants[i].getKeyIndex();
65819                    if(thisKey !== null) {
65820                        descKeyIndexes[descKeyIndexes.length] = thisKey;
65821                    }
65822                }
65823                if(descKeyIndexes.length > 0) {
65824                    aOrigKeyIndexes = descKeyIndexes;
65825                }
65826            }
65827            // ...or else must be a key Column
65828            else {
65829                aOrigKeyIndexes = [aOrigKeyIndexes];
65830            }
65831            
65832            if(aOrigKeyIndexes !== null) {                   
65833                // Sort the indexes
65834                aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
65835                
65836                // Destroy previous THEAD
65837                this._destroyTheadEl();
65838    
65839                // Create new THEAD
65840                var aColumnDefs = this._oColumnSet.getDefinitions();
65841                var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0];
65842                aColumnDefs.splice(index, 0, oColumnDef);
65843                this._initColumnSet(aColumnDefs);
65844                this._initTheadEl();
65845                
65846                // Need to refresh the reference
65847                var oNewColumn = this._oColumnSet.tree[0][index];
65848
65849                // What are new key index(es)
65850                var aNewKeyIndexes = oNewColumn.getKeyIndex();
65851                // Must be a parent Column
65852                if(aNewKeyIndexes === null) {
65853                    descKeyIndexes = [];
65854                    allDescendants = this._oColumnSet.getDescendants(oNewColumn);
65855                    for(i=0, len=allDescendants.length; i<len; i++) {
65856                        // Is this descendant a key Column?
65857                        thisKey = allDescendants[i].getKeyIndex();
65858                        if(thisKey !== null) {
65859                            descKeyIndexes[descKeyIndexes.length] = thisKey;
65860                        }
65861                    }
65862                    if(descKeyIndexes.length > 0) {
65863                        aNewKeyIndexes = descKeyIndexes;
65864                    }
65865                }
65866                // Must be a key Column
65867                else {
65868                    aNewKeyIndexes = [aNewKeyIndexes];
65869                }
65870                
65871                // Sort the new indexes and grab the first one for the new location
65872                var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
65873
65874                // Reorder COL
65875                this._reorderColgroupColEl(aOrigKeyIndexes, newIndex);
65876                
65877                // Reorder TD
65878                var allRows = this._elTbody.rows;
65879                if(allRows.length > 0) {
65880                    var loopN = this.get("renderLoopSize"),
65881                        loopEnd = allRows.length;
65882                    this._oChainRender.add({
65883                        method: function(oArg) {
65884                            if((this instanceof DT) && this._sId) {
65885                                var i = oArg.nCurrentRow, j, tmpTds, nextSibling,
65886                                    len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
65887                                    aIndexes = oArg.aIndexes, thisTr;
65888                                // For each row
65889                                for(; i < len; ++i) {
65890                                    tmpTds = [];
65891                                    thisTr = allRows[i];
65892                                    
65893                                    // Remove each TD
65894                                    for(j=aIndexes.length-1; j>-1; j--) {
65895                                        tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]]));
65896                                    }
65897                                    
65898                                    // Insert each TD
65899                                    nextSibling = thisTr.childNodes[newIndex] || null;
65900                                    for(j=tmpTds.length-1; j>-1; j--) {
65901                                        thisTr.insertBefore(tmpTds[j], nextSibling);
65902                                    }                                    
65903                                }
65904                                oArg.nCurrentRow = i;
65905                            }
65906                        },
65907                        iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
65908                        argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes},
65909                        scope: this,
65910                        timeout: (loopN > 0) ? 0 : -1
65911                    });
65912                    this._runRenderChain();
65913                }
65914        
65915                this.fireEvent("columnReorderEvent",{column:oNewColumn, oldIndex:nOrigTreeIndex});
65916                YAHOO.log("Column \"" + oNewColumn.key + "\" reordered", "info", this.toString());
65917                return oNewColumn;
65918            }
65919        }
65920    }
65921    YAHOO.log("Could not reorder Column \"" + oColumn.key + "\". Only non-ne
65921sted Columns can be reordered", "warn", this.toString());
65922},
65923
65924/**
65925 * Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only
65926 * select/unselect non-nested Columns, and bottom-level key Columns.
65927 *
65928 * @method selectColumn
65929 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
65930 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
65931 */
65932selectColumn : function(oColumn) {
65933    oColumn = this.getColumn(oColumn);
65934    if(oColumn && !oColumn.selected) {
65935        // Only bottom-level Columns can get hidden
65936        if(oColumn.getKeyIndex() !== null) {
65937            oColumn.selected = true;
65938            
65939            // Update head cell
65940            var elTh = oColumn.getThEl();
65941            Dom.addClass(elTh,DT.CLASS_SELECTED);
65942
65943            // Update body cells
65944            var allRows = this.getTbodyEl().rows;
65945            var oChainRender = this._oChainRender;
65946            oChainRender.add({
65947                method: function(oArg) {
65948                    if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
65949                        Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);                    
65950                    }
65951                    oArg.rowIndex++;
65952                },
65953                scope: this,
65954                iterations: allRows.length,
65955                argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
65956            });
65957
65958            this._clearTrTemplateEl();
65959            
65960            this._elTbody.style.display = "none";
65961            this._runRenderChain();
65962            this._elTbody.style.display = "";      
65963            
65964            this.fireEvent("columnSelectEvent",{column:oColumn});
65965            YAHOO.log("Column \"" + oColumn.key + "\" selected", "info", this.toString());
65966        }
65967        else {
65968            YAHOO.log("Could not select Column \"" + oColumn.key + "\". Only non-nested Columns can be selected", "warn", this.toString());
65969        }
65970    }
65971},
65972
65973/**
65974 * Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only
65975 * select/unselect non-nested Columns, and bottom-level key Columns.
65976 *
65977 * @method unselectColumn
65978 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
65979 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
65980 */
65981unselectColumn : function(oColumn) {
65982    oColumn = this.getColumn(oColumn);
65983    if(oColumn && oColumn.selected) {
65984        // Only bottom-level Columns can get hidden
65985        if(oColumn.getKeyIndex() !== null) {
65986            oColumn.selected = false;
65987            
65988            // Update head cell
65989            var elTh = oColumn.getThEl();
65990            Dom.removeClass(elTh,DT.CLASS_SELECTED);
65991
65992            // Update body cells
65993            var allRows = this.getTbodyEl().rows;
65994            var oChainRender = this._oChainRender;
65995            oChainRender.add({
65996                method: function(oArg) {
65997                    if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
65998                        Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED); 
65999                    }                   
66000                    oArg.rowIndex++;
66001                },
66002                scope: this,
66003                iterations:allRows.length,
66004                argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
66005            });
66006            
66007            this._clearTrTemplateEl();
66008
66009            this._elTbody.style.display = "none";
66010            this._runRenderChain();
66011            this._elTbody.style.display = "";      
66012            
66013            this.fireEvent("columnUnselectEvent",{column:oColumn});
66014            YAHOO.log("Column \"" + oColumn.key + "\" unselected", "info", this.toString());
66015        }
66016        else {
66017            YAHOO.log("Could not unselect Column \"" + oColumn.key + "\". Only non-nested Columns can be unselected", "warn", this.toString());
66018        }
66019    }
66020},
66021
66022/**
66023 * Returns an array selected Column instances.
66024 *
66025 * @method getSelectedColumns
66026 * @return {YAHOO.widget.Column[]} Array of Column instances.
66027 */
66028getSelectedColumns : function(oColumn) {
66029    var selectedColumns = [];
66030    var aKeys = this._oColumnSet.keys;
66031    for(var i=0,len=aKeys.length; i<len; i++) {
66032        if(aKeys[i].selected) {
66033            selectedColumns[selectedColumns.length] = aKeys[i];
66034        }
66035    }
66036    return selectedColumns;
66037},
66038
66039/**
66040 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
66041 * NOTE: You cannot highlight/unhighlight nested Columns. You can only
66042 * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
66043 *
66044 * @method highlightColumn
66045 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
66046 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
66047 */
66048highlightColumn : function(column) {
66049    var oColumn = this.getColumn(column);
66050    // Only bottom-level Columns can get highlighted
66051    if(oColumn && (oColumn.getKeyIndex() !== null)) {            
66052        // Update head cell
66053        var elTh = oColumn.getThEl();
66054        Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED);
66055
66056        // Update body cells
66057        var allRows = this.getTbodyEl().rows;
66058        var oChainRender = this._oChainRender;
66059        oChainRender.add({
66060            method: function(oArg) {
66061                if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
66062                    Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);   
66063                }                 
66064                oArg.rowIndex++;
66065            },
66066            scope: this,
66067            iterations:allRows.length,
66068            argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
66069            timeout: -1
66070        });
66071        this._elTbody.style.display = "none";
66072        this._runRenderChain();
66073        this._elTbody.style.display = "";      
66074            
66075        this.fireEvent("columnHighlightEvent",{column:oColumn});
66076        YAHOO.log("Column \"" + oColumn.key + "\" highlighed", "info", this.toString());
66077    }
66078    else {
66079        YAHOO.log("Could not highlight Column \"" + oColumn.key + "\". Only non-nested Columns can be highlighted", "warn", this.toString());
66080    }
66081},
66082
66083/**
66084 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
66085 * NOTE: You cannot highlight/unhighlight nested Columns. You can only
66086 * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
66087 *
66088 * @method unhighlightColumn
66089 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
66090 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
66091 */
66092unhighlightColumn : function(column) {
66093    var oColumn = this.getColumn(column);
66094    // Only bottom-level Columns can get highlighted
66095    if(oColumn && (oColumn.getKeyIndex() !== null)) {
66096        // Update head cell
66097        var elTh = oColumn.getThEl();
66098        Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED);
66099
66100        // Update body cells
66101        var allRows = this.getTbodyEl().rows;
66102        var oChainRender = this._oChainRender;
66103        oChainRender.add({
66104            method: function(oArg) {
66105                if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
66106                    Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
66107                }                 
66108                oArg.rowIndex++;
66109            },
66110            scope: this,
66111            iterations:allRows.length,
66112            argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
66113            timeout: -1
66114        });
66115        this._elTbody.style.display = "none";
66116        this._runRenderChain();
66117        this._elTbody.style.display = "";     
66118            
66119        this.fireEvent("columnUnhighlightEvent",{column:oColumn});
66120        YAHOO.log("Column \"" + oColumn.key + "\" unhighlighted", "info", this.toString());
66121    }
66122    else {
66123        YAHOO.log("Could not unhighlight Column \"" + oColumn.key + "\". Only non-nested Columns can be unhighlighted", "warn", this.toString());
66124    }
66125},
66126
66127
66128
66129
66130
66131
66132
66133
66134
66135
66136
66137
66138
66139
66140
66141
66142
66143
66144
66145
66146
66147
66148
66149
66150
66151
66152
66153
66154
66155
66156
66157
66158
66159
66160
66161
66162
66163
66164
66165
66166
66167
66168
66169
66170// ROW FUNCTIONS
66171
66172/**
66173 * Adds one new Record of data into the RecordSet at the index if given,
66174 * otherwise at the end. If the new Record is in page view, the
66175 * corresponding DOM elements are also updated.
66176 *
66177 * @method addRow
66178 * @param oData {Object} Object literal of data for the row.
66179 * @param index {Number} (optional) RecordSet position index at which to add data.
66180 */
66181addRow : function(oData, index) {
66182    if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
66183        YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString());
66184        return;
66185    }
66186
66187    if(oData && lang.isObject(oData)) {
66188        var oRecord = this._oRecordSet.addRecord(oData, index);
66189        if(oRecord) {
66190            var recIndex;
66191            var oPaginator = this.get('paginator');
66192
66193            // Paginated
66194            if (oPaginator) {     
66195                // Update the paginator's totalRecords
66196                var totalRecords = oPaginator.get('totalRecords');
66197                if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
66198                    oPaginator.set('totalRecords',totalRecords + 1);
66199                }
66200
66201                recIndex = this.getRecordIndex(oRecord);
66202                var endRecIndex = (oPaginator.getPageRecords())[1];
66203
66204                // New record affects the view
66205                if (recIndex <= endRecIndex) {
66206                    // Defer UI updates to the render method
66207                    this.render();
66208                }
66209                
66210                this.fireEvent("rowAddEvent", {record:oRecord});
66211                YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString()); 
66212                return;
66213            }
66214            // Not paginated
66215            else {
66216                recIndex = this.getRecordIndex(oRecord);
66217                if(lang.isNumber(recIndex)) {
66218                    // Add the TR element
66219                    this._oChainRender.add({
66220                        method: function(oArg) {
66221                            if((this instanceof DT) && this._sId) {
66222                                var oRecord = oArg.record;
66223                                var recIndex = oArg.recIndex;
66224                                var elNewTr = this._addTrEl(oRecord);
66225                                if(elNewTr) {
66226                                    var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null;
66227                                    this._elTbody.insertBefore(elNewTr, elNext);
66228
66229                                    // Set FIRST/LAST
66230                                    if(recIndex === 0) {
66231                                        this._setFirstRow();
66232                                    }
66233                                    if(elNext === null) {
66234                                        this._setLastRow();
66235                                    }
66236                                    // Set EVEN/ODD
66237                                    this._setRowStripes();                           
66238                                    
66239                                    this.hideTableMessage();
66240            
66241                                    this.fireEvent("rowAddEvent", {record:oRecord});
66242                                    YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString());
66243                                }
66244                            }
66245                        },
66246                        argument: {record: oRecord, recIndex: recIndex},
66247                        scope: this,
66248                        timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
66249                    });
66250                    this._runRenderChain();
66251                    return;
66252                }
66253            }            
66254        }
66255    }
66256    YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString());
66257},
66258
66259/**
66260 * Convenience method to add multiple rows.
66261 *
66262 * @method addRows
66263 * @param aData {Object[]} Array of object literal data for the rows.
66264 * @param index {Number} (optional) RecordSet position index at which to add data.
66265 */
66266addRows : function(aData, index) {
66267    if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
66268        YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString());    
66269        return;
66270    }
66271
66272    if(lang.isArray(aData)) {
66273        var aRecords = this._oRecordSet.addRecords(aData, index);
66274        if(aRecords) {
66275            var recIndex = this.getRecordIndex(aRecords[0]);
66276            
66277            // Paginated
66278            var oPaginator = this.get('paginator');
66279            if (oPaginator) {
66280                // Update the paginator's totalRecords
66281                var totalRecords = oPaginator.get('totalRecords');
66282                if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
66283                    oPaginator.set('totalRecords',totalRecords + aRecords.length);
66284                }
66285    
66286                var endRecIndex = (oPaginator.getPageRecords())[1];
66287
66288                // At least one of the new records affects the view
66289                if (recIndex <= endRecIndex) {
66290                    this.render();
66291                }
66292                
66293                this.fireEvent("rowsAddEvent", {records:aRecords});
66294                YAHOO.log("Added " + aRecords.length + 
66295                        " rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) +
66296                        " with data " + lang.dump(aData), "info", this.toString());
66297                return;
66298            }
66299            // Not paginated
66300            else {
66301                // Add the TR elements
66302                var loopN = this.get("renderLoopSize");
66303                var loopEnd = recIndex + aData.length;
66304                var nRowsNeeded = (loopEnd - recIndex); // how many needed
66305                var isLast = (recIndex >= this._elTbody.rows.length);
66306                this._oChainRender.add({
66307                    method: function(oArg) {
66308                        if((this instanceof DT) && this._sId) {
66309                            var aRecords = oArg.aRecords,
66310                                i = oArg.nCurrentRow,
66311                                j = oArg.nCurrentRecord,
66312                                len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd,
66313                                df = document.createDocumentFragment(),
66314                                elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null;
66315                            for(; i < len; i++, j++) {
66316                                df.appendChild(this._addTrEl(aRecords[j]));
66317                            }
66318                            this._elTbody.insertBefore(df, elNext);
66319                            oArg.nCurrentRow = i;
66320                            oArg.nCurrentRecord = j;
66321                        }
66322                    },
66323                    iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
66324                    argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords},
66325                    scope: this,
66326                    timeout: (loopN > 0) ? 0 : -1
66327                });
66328                this._oChainRender.add({
66329                    method: function(oArg) {
66330                        var recIndex = oArg.recIndex;
66331                        // Set FIRST/LAST
66332                        if(recIndex === 0) {
66333                            this._setFirstRow();
66334                        }
66335                        if(oArg.isLast) {
66336                            this._setLastRow();
66337                        }
66338                        // Set EVEN/ODD
66339                        this._setRowStripes();                           
66340
66341                        this.fireEvent("rowsAddEvent", {records:aRecords});
66342                        YAHOO.log("Added " + aRecords.length + 
66343                                " rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) +
66344                                " with data " + lang.dump(aData), "info", this.toString());
66345                    },
66346                    argument: {recIndex: recIndex, isLast: isLast},
66347                    scope: this,
66348                    timeout: -1 // Needs to run immediately after the DOM insertions above
66349                });
66350                this._runRenderChain();
66351                this.hideTableMessage();                
66352                return;
66353            }            
66354        }
66355    }
66356    YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString());    
66357},
66358
66359/**
66360 * For the given row, updates the associated Record with the given data. If the
66361 * row is on current page, the corresponding DOM elements are also updated.
66362 *
66363 * @method updateRow
66364 * @param row {YAHOO.widget.Record | Number | HTMLElement | String}
66365 * Which row to update: By Record instance, by Record's RecordSet
66366 * position index, by HTMLElement reference to the TR element, or by ID string
66367 * of the TR element.
66368 * @param oData {Object} Object literal of data for the row.
66369 */
66370updateRow : function(row, oData) {
66371    var index = row;
66372    if (!lang.isNumber(index)) {
66373        index = this.getRecordIndex(row);
66374    }
66375
66376    // Update the Record
66377    if(lang.isNumber(index) && (index >= 0)) {
66378        var oRecordSet = this._oRecordSet,
66379            oldRecord = oRecordSet.getRecord(index);
66380
66381        if(oldRecord) {
66382            var updatedRecord = this._oRecordSet.setRecord(oData, index),
66383                elRow = this.getTrEl(oldRecord),
66384                // Copy data from the Record for the event that gets fired later
66385                oldData = oldRecord ? oldRecord.getData() : null;
66386
66387            if(updatedRecord) {
66388                // Update selected rows as necessary
66389                var tracker = this._aSelections || [],
66390                i=0,
66391                oldId = oldRecord.getId(),
66392                newId = updatedRecord.getId();
66393                for(; i<tracker.length; i++) {
66394                    if((tracker[i] === oldId)) {
66395                        tracker[i] = newId;
66396                    }
66397                    else if(tracker[i].recordId === oldId) {
66398                        tracker[i].recordId = newId;
66399                    }
66400                }
66401
66402                // Update anchors as necessary
66403                if(this._oAnchorRecord && this._oAnchorRecord.getId() === oldId) {
66404                    this._oAnchorRecord = updatedRecord;
66405                }
66406                if(this._oAnchorCell && this._oAnchorCell.record.getId() === oldId) {
66407                    this._oAnchorCell.record = updatedRecord;
66408                }
66409
66410                // Update the TR only if row is on current page
66411                this._oChainRender.add({
66412                    method: function() {
66413                        if((this instanceof DT) && this._sId) {
66414                            // Paginated
66415                            var oPaginator = this.get('paginator');
66416                            if (oPaginator) {
66417                                var pageStartIndex = (oPaginator.getPageRecords())[0],
66418                                    pageLastIndex = (oPaginator.getPageRecords())[1];
66419
66420                                // At least one of the new records affects the view
66421                                if ((index >= pageStartIndex) || (index <= pageLastIndex)) {
66422                                    this.render();
66423                                }
66424                            }
66425                            else {
66426                                if(elRow) {
66427                                    this._updateTrEl(elRow, updatedRecord);
66428                                }
66429                                else {
66430                                    this.getTbodyEl().appendChild(this._addTrEl(updatedRecord));
66431                                }
66432                            }
66433                            this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData}
66433);
66434                            YAHOO.log("DataTable row updated: Record ID = " + updatedRecord.getId() +
66435                                    ", Record index = " + this.getRecordIndex(updatedRecord) +
66436                                    ", page row index = " + this.getTrIndex(updatedRecord), "info", this.toString());
66437                        }
66438                    },
66439                    scope: this,
66440                    timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
66441                });
66442                this._runRenderChain();
66443                return;
66444            }
66445        }
66446    }
66447    YAHOO.log("Could not update row " + row + " with the data : " + lang.dump(oData), "warn", this.toString());
66448    return;
66449},
66450
66451/**
66452 * Starting with the given row, updates associated Records with the given data.
66453 * The number of rows to update are determined by the array of data provided.
66454 * Undefined data (i.e., not an object literal) causes a row to be skipped. If
66455 * any of the rows are on current page, the corresponding DOM elements are also
66456 * updated.
66457 *
66458 * @method updateRows
66459 * @param startrow {YAHOO.widget.Record | Number | HTMLElement | String}
66460 * Starting row to update: By Record instance, by Record's RecordSet
66461 * position index, by HTMLElement reference to the TR element, or by ID string
66462 * of the TR element.
66463 * @param aData {Object[]} Array of object literal of data for the rows.
66464 */
66465updateRows : function(startrow, aData) {
66466    if(lang.isArray(aData)) {
66467        var startIndex = startrow,
66468            oRecordSet = this._oRecordSet,
66469            lastRowIndex = oRecordSet.getLength();
66470
66471        if (!lang.isNumber(startrow)) {
66472            startIndex = this.getRecordIndex(startrow);
66473        }
66474            
66475        if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) {
66476            var lastIndex = startIndex + aData.length,
66477                aOldRecords = oRecordSet.getRecords(startIndex, aData.length),
66478                aNewRecords = oRecordSet.setRecords(aData, startIndex);
66479            if(aNewRecords) {
66480                var tracker = this._aSelections || [],
66481                    i=0, j, newRecord, newId, oldId,
66482                    anchorRecord = this._oAnchorRecord ? this._oAnchorRecord.getId() : null,
66483                    anchorCell = this._oAnchorCell ? this._oAnchorCell.record.getId() : null;
66484                for(; i<aOldRecords.length; i++) {
66485                    oldId = aOldRecords[i].getId();
66486                    newRecord = aNewRecords[i];
66487                    newId = newRecord.getId();
66488
66489                    // Update selected rows as necessary
66490                    for(j=0; j<tracker.length; j++) {
66491                        if((tracker[j] === oldId)) {
66492                            tracker[j] = newId;
66493                        }
66494                        else if(tracker[j].recordId === oldId) {
66495                            tracker[j].recordId = newId;
66496                        }
66497                    }
66498
66499                    // Update anchors as necessary
66500                    if(anchorRecord && anchorRecord === oldId) {
66501                        this._oAnchorRecord = newRecord;
66502                    }
66503                    if(anchorCell && anchorCell === oldId) {
66504                        this._oAnchorCell.record = newRecord;
66505                    }
66506               }
66507
66508                // Paginated
66509                var oPaginator = this.get('paginator');
66510                if (oPaginator) {
66511                    var pageStartIndex = (oPaginator.getPageRecords())[0],
66512                        pageLastIndex = (oPaginator.getPageRecords())[1];
66513    
66514                    // At least one of the new records affects the view
66515                    if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) {
66516                        this.render();
66517                    }
66518
66519                    this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
66520                    YAHOO.log("Added " + aNewRecords.length + 
66521                            " rows starting at index " + startIndex +
66522                            " with data " + lang.dump(aData), "info", this.toString());
66523                    return;
66524                }
66525                // Not paginated
66526                else {
66527                    // Update the TR elements
66528                    var loopN = this.get("renderLoopSize"),
66529                        rowCount = aData.length, // how many needed
66530                        isLast = (lastIndex >= lastRowIndex),
66531                        isAdding = (lastIndex > lastRowIndex);
66532                                           
66533                    this._oChainRender.add({
66534                        method: function(oArg) {
66535                            if((this instanceof DT) && this._sId) {
66536                                var aRecords = oArg.aRecords,
66537                                    i = oArg.nCurrentRow,
66538                                    j = oArg.nDataPointer,
66539                                    len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length;
66540                                    
66541                                for(; i < len; i++,j++) {
66542                                    if(isAdding && (i>=lastRowIndex)) {
66543                                        this._elTbody.appendChild(this._addTrEl(aRecords[j]));
66544                                    }
66545                                    else {
66546                                        this._updateTrEl(this._elTbody.rows[i], aRecords[j]);
66547                                    }
66548                                }
66549                                oArg.nCurrentRow = i;
66550                                oArg.nDataPointer = j;
66551                            }
66552                        },
66553                        iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1,
66554                        argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding},
66555                        scope: this,
66556                        timeout: (loopN > 0) ? 0 : -1
66557                    });
66558                    this._oChainRender.add({
66559                        method: function(oArg) {
66560                            var recIndex = oArg.recIndex;
66561                            // Set FIRST/LAST
66562                            if(recIndex === 0) {
66563                                this._setFirstRow();
66564                            }
66565                            if(oArg.isLast) {
66566                                this._setLastRow();
66567                            }
66568                            // Set EVEN/ODD
66569                            this._setRowStripes();                           
66570    
66571                            this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
66572                            YAHOO.log("Added " + aNewRecords.length + 
66573                                    " rows starting at index " + startIndex +
66574                                    " with data " + lang.dump(aData), "info", this.toString());
66575                        },
66576                        argument: {recIndex: startIndex, isLast: isLast},
66577                        scope: this,
66578                        timeout: -1 // Needs to run immediately after the DOM insertions above
66579                    });
66580                    this._runRenderChain();
66581                    this.hideTableMessage();                
66582                    return;
66583                }            
66584            }
66585        }
66586    }
66587    YAHOO.log("Could not update rows at " + startrow + " with " + lang.dump(aData), "warn", this.toString());
66588},
66589
66590/**
66591 * Deletes the given row's Record from the RecordSet. If the row is on current page,
66592 * the corresponding DOM elements are also deleted.
66593 *
66594 * @method deleteRow
66595 * @param row {HTMLElement | String | Number} DOM element reference or ID string
66596 * to DataTable page element or RecordSet index.
66597 */
66598deleteRow : function(row) {
66599    var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
66600    if(lang.isNumber(nRecordIndex)) {
66601        var oRecord = this.getRecord(nRecordIndex);
66602        if(oRecord) {
66603            var nTrIndex = this.getTrIndex(nRecordIndex);
66604            
66605            // Remove from selection tracker if there
66606            var sRecordId = oRecord.getId();
66607            var tracker = this._aSelections || [];
66608            for(var j=tracker.length-1; j>-1; j--) {
66609                if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
66610                        (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
66611                    tracker.splice(j,1);
66612                }
66613            }
66614    
66615            // Delete Record from RecordSet
66616            var oData = this._oRecordSet.deleteRecord(nRecordIndex);
66617    
66618            // Update the UI
66619            if(oData) {
66620                // If paginated and the deleted row was on this or a prior page, just
66621                // re-render
66622                var oPaginator = this.get('paginator');
66623                if (oPaginator) {
66624                    // Update the paginator's totalRecords
66625                    var totalRecords = oPaginator.get('totalRecords'),
66626                        // must capture before the totalRecords change because
66627                        // Paginator shifts to previous page automatically
66628                        rng = oPaginator.getPageRecords();
66629
66630                    if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
66631                        oPaginator.set('totalRecords',totalRecords - 1);
66632                    }
66633    
66634                    // The deleted record was on this or a prior page, re-render
66635                    if (!rng || nRecordIndex <= rng[1]) {
66636                        this.render();
66637                    }
66638
66639                    this._oChainRender.add({
66640                        method: function() {
66641                            if((this instanceof DT) && this._sId) {
66642                                this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex});
66643                                YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString());     
66644                            }
66645                        },
66646                        scope: this,
66647                        timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
66648                    });
66649                    this._runRenderChain();
66650                }
66651                // Not paginated
66652                else {
66653                    if(lang.isNumber(nTrIndex)) {
66654                        this._oChainRender.add({
66655                            method: function() {
66656                                if((this instanceof DT) && this._sId) {
66657                                    var isLast = (nRecordIndex === this._oRecordSet.getLength());//(nTrIndex == this.getLastTrEl().sectionRowIndex);
66658                                    this._deleteTrEl(nTrIndex);
66659                    
66660                                    // Post-delete tasks
66661                                    if(this._elTbody.rows.length > 0) {
66662                                        // Set FIRST/LAST
66663                                        if(nTrIndex === 0) {
66664                                            this._setFirstRow();
66665                                        }
66666                                        if(isLast) {
66667                                            this._setLastRow();
66668                                        }
66669                                        // Set EVEN/ODD
66670                                        if(nTrIndex != this._elTbody.rows.length) {
66671                                            this._setRowStripes(nTrIndex);
66672                                        }                                
66673                                    }
66674                    
66675                                    this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex});
66676                                    YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString());     
66677                                }
66678                            },
66679                            scope: this,
66680                            timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
66681                        });
66682                        this._runRenderChain();
66683                        return;
66684                    }
66685                }
66686            }
66687        }
66688    }
66689    YAHOO.log("Could not delete row: " + row, "warn", this.toString());
66690    return null;
66691},
66692
66693/**
66694 * Convenience method to delete multiple rows.
66695 *
66696 * @method deleteRows
66697 * @param row {HTMLElement | String | Number} DOM element reference or ID string
66698 * to DataTable page element or RecordSet index.
66699 * @param count {Number} (optional) How many rows to delete. A negative value
66700 * will delete towards the beginning.
66701 */
66702deleteRows : function(row, count) {
66703    var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
66704    if(lang.isNumber(nRecordIndex)) {
66705        var oRecord = this.getRecord(nRecordIndex);
66706        if(oRecord) {
66707            var nTrIndex = this.getTrIndex(nRecordIndex);
66708            
66709            // Remove from selection tracker if there
66710            var sRecordId = oRecord.getId();
66711            var tracker = this._aSelections || [];
66712            for(var j=tracker.length-1; j>-1; j--) {
66713                if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
66714                        (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
66715                    tracker.splice(j,1);
66716                }
66717            }
66718    
66719            // Delete Record from RecordSet
66720            var highIndex = nRecordIndex;
66721            var lowIndex = nRecordIndex;
66722        
66723            // Validate count and account for negative value
66724            if(count && lang.isNumber(count)) {
66725                highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
66726                lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
66727                count = (count > 0) ? count : count*-1;
66728                if(lowIndex < 0) {
66729                    lowIndex = 0;
66730                    count = highIndex - lowIndex + 1;
66731                }
66732            }
66733            else {
66734                count = 1;
66735            }
66736            
66737            var aData = this._oRecordSet.deleteRecords(lowIndex, count);
66738    
66739            // Update the UI
66740            if(aData) {
66741                var oPaginator = this.get('paginator'),
66742                    loopN = this.get("renderLoopSize");
66743                // If paginated and the deleted row was on this or a prior page, just
66744                // re-render
66745                if (oPaginator) {
66746                    // Update the paginator's totalRecords
66747                    var totalRecords = oPaginator.get('totalRecords'),
66748                        // must capture before the totalRecords change because
66749                        // Paginator shifts to previous page automatically
66750                        rng = oPaginator.getPageRecords();
66751
66752                    if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
66753                        oPaginator.set('totalRecords',totalRecords - aData.length);
66754                    }
66755    
66756                    // The records were on this or a prior page, re-render
66757                    if (!rng || lowIndex <= rng[1]) {
66758                        this.render();
66759                    }
66760
66761                    this._oChainRender.add({
66762                        method: function(oArg) {
66763                            if((this instanceof DT) && this._sId) {
66764                                this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
66765                                YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString());
66766                            }
66767                        },
66768                        scope: this,
66769                        timeout: (loopN > 0) ? 0 : -1
66770                    });
66771                    this._runRenderChain();
66772                    return;
66773                }
66774                // Not paginated
66775                else {
66776                    if(lang.isNumber(nTrIndex)) {
66777                        // Delete the TR elements starting with highest index
66778                        var loopEnd = lowIndex;
66779   
66779                     var nRowsNeeded = count; // how many needed
66780                        this._oChainRender.add({
66781                            method: function(oArg) {
66782                                if((this instanceof DT) && this._sId) {
66783                                    var i = oArg.nCurrentRow,
66784                                        len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1;
66785                                    for(; i>len; --i) {
66786                                        this._deleteTrEl(i);
66787                                    }
66788                                    oArg.nCurrentRow = i;
66789                                }
66790                            },
66791                            iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1,
66792                            argument: {nCurrentRow:highIndex},
66793                            scope: this,
66794                            timeout: (loopN > 0) ? 0 : -1
66795                        });
66796                        this._oChainRender.add({
66797                            method: function() {    
66798                                // Post-delete tasks
66799                                if(this._elTbody.rows.length > 0) {
66800                                    this._setFirstRow();
66801                                    this._setLastRow();
66802                                    this._setRowStripes();
66803                                }
66804                                
66805                                this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
66806                                YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString());
66807                            },
66808                            scope: this,
66809                            timeout: -1 // Needs to run immediately after the DOM deletions above
66810                        });
66811                        this._runRenderChain();
66812                        return;
66813                    }
66814                }
66815            }
66816        }
66817    }
66818    YAHOO.log("Could not delete " + count + " rows at row " + row, "warn", this.toString());
66819    return null;
66820},
66821
66822
66823
66824
66825
66826
66827
66828
66829
66830
66831
66832
66833
66834
66835
66836
66837
66838
66839
66840
66841
66842
66843
66844
66845
66846
66847
66848
66849
66850
66851
66852
66853
66854
66855
66856
66857
66858
66859
66860
66861
66862
66863
66864
66865
66866
66867// CELL FUNCTIONS
66868
66869/**
66870 * Outputs markup into the given TD based on given Record.
66871 *
66872 * @method formatCell
66873 * @param elLiner {HTMLElement} The liner DIV element within the TD.
66874 * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
66875 * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
66876 */
66877formatCell : function(elLiner, oRecord, oColumn) {
66878    if(!oRecord) {
66879        oRecord = this.getRecord(elLiner);
66880    }
66881    if(!oColumn) {
66882        oColumn = this.getColumn(this.getCellIndex(elLiner.parentNode));
66883    }
66884
66885    if(oRecord && oColumn) {
66886        var sField = oColumn.field;
66887        var oData = oRecord.getData(sField);
66888
66889        var fnFormatter = typeof oColumn.formatter === 'function' ?
66890                          oColumn.formatter :
66891                          DT.Formatter[oColumn.formatter+''] ||
66892                          DT.Formatter.defaultFormatter;
66893
66894        // Apply special formatter
66895        if(fnFormatter) {
66896            fnFormatter.call(this, elLiner, oRecord, oColumn, oData);
66897        }
66898        else {
66899            elLiner.innerHTML = oData;
66900        }
66901
66902        this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elLiner});
66903    }
66904    else {
66905        YAHOO.log("Could not format cell " + elLiner, "error", this.toString());
66906    }
66907},
66908
66909/**
66910 * For the given row and column, updates the Record with the given data. If the
66911 * cell is on current page, the corresponding DOM elements are also updated.
66912 *
66913 * @method updateCell
66914 * @param oRecord {YAHOO.widget.Record} Record instance.
66915 * @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index.
66916 * @param oData {Object} New data value for the cell.
66917 * @param skipRender {Boolean} Skips render step. Editors that update multiple
66918 * cells in ScrollingDataTable should render only on the last call to updateCell().
66919 */
66920updateCell : function(oRecord, oColumn, oData, skipRender) {
66921    // Validate Column and Record
66922    oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn);
66923    if(oColumn && oColumn.getField() && (oRecord instanceof YAHOO.widget.Record)) {
66924        var sKey = oColumn.getField(),
66925        
66926        // Copy data from the Record for the event that gets fired later
66927        //var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData());
66928            oldData = oRecord.getData(sKey);
66929
66930        // Update Record with new data
66931        this._oRecordSet.updateRecordValue(oRecord, sKey, oData);
66932    
66933        // Update the TD only if row is on current page
66934        var elTd = this.getTdEl({record: oRecord, column: oColumn});
66935        if(elTd) {
66936            this._oChainRender.add({
66937                method: function() {
66938                    if((this instanceof DT) && this._sId) {
66939                        this.formatCell(elTd.firstChild, oRecord, oColumn);
66940                        this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData}
66940);
66941                        YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() +
66942                                ", Record index = " + this.getRecordIndex(oRecord) +
66943                                ", page row index = " + this.getTrIndex(oRecord) +
66944                                ", Column key = " + oColumn.getKey(), "info", this.toString());
66945                    }
66946                },
66947                scope: this,
66948                timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
66949            });
66950            // Bug 2529024
66951            if(!skipRender) {
66952                this._runRenderChain();
66953            }
66954        }
66955        else {
66956            this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
66957            YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() +
66958                    ", Record index = " + this.getRecordIndex(oRecord) +
66959                    ", page row index = " + this.getTrIndex(oRecord) +
66960                    ", Column key = " + oColumn.getKey(), "info", this.toString());   
66961        }
66962    }
66963},
66964
66965
66966
66967
66968
66969
66970
66971
66972
66973
66974
66975
66976
66977
66978
66979
66980
66981
66982
66983
66984
66985
66986
66987
66988
66989
66990
66991
66992
66993
66994
66995
66996
66997
66998
66999
67000
67001
67002
67003
67004
67005
67006
67007
67008
67009
67010
67011
67012
67013
67014
67015// PAGINATION
67016/**
67017 * Method executed during set() operation for the "paginator" attribute.
67018 * Adds and/or severs event listeners between DataTable and Paginator
67019 *
67020 * @method _updatePaginator
67021 * @param newPag {Paginator} Paginator instance (or null) for DataTable to use
67022 * @private
67023 */
67024_updatePaginator : function (newPag) {
67025    var oldPag = this.get('paginator');
67026    if (oldPag && newPag !== oldPag) {
67027        oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
67028    }
67029    if (newPag) {
67030        newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
67031    }
67032},
67033
67034/**
67035 * Update the UI infrastructure in response to a "paginator" attribute change.
67036 *
67037 * @method _handlePaginatorChange
67038 * @param e {Object} Change event object containing keys 'type','newValue',
67039 *                   and 'prevValue'
67040 * @private
67041 */
67042_handlePaginatorChange : function (e) {
67043    if (e.prevValue === e.newValue) { return; }
67044
67045    var newPag     = e.newValue,
67046        oldPag     = e.prevValue,
67047        containers = this._defaultPaginatorContainers();
67048
67049    if (oldPag) {
67050        if (oldPag.getContainerNodes()[0] == containers[0]) {
67051            oldPag.set('containers',[]);
67052        }
67053        oldPag.destroy();
67054
67055        // Convenience: share the default containers if possible.
67056        // Otherwise, remove the default containers from the DOM.
67057        if (containers[0]) {
67058            if (newPag && !newPag.getContainerNodes().length) {
67059                newPag.set('containers',containers);
67060            } else {
67061                // No new Paginator to use existing containers, OR new
67062                // Paginator has configured containers.
67063                for (var i = containers.length - 1; i >= 0; --i) {
67064                    if (containers[i]) {
67065                        containers[i].parentNode.removeChild(containers[i]);
67066                    }
67067                }
67068            }
67069        }
67070    }
67071
67072    if (!this._bInit) {
67073        this.render();
67074
67075    }
67076
67077    if (newPag) {
67078        this.renderPaginator();
67079    }
67080
67081},
67082
67083/**
67084 * Returns the default containers used for Paginators.  If create param is
67085 * passed, the containers will be created and added to the DataTable container.
67086 *
67087 * @method _defaultPaginatorContainers
67088 * @param create {boolean} Create the default containers if not found
67089 * @private
67090 */
67091_defaultPaginatorContainers : function (create) {
67092    var above_id = this._sId + '-paginator0',
67093        below_id = this._sId + '-paginator1',
67094        above    = Dom.get(above_id),
67095        below    = Dom.get(below_id);
67096
67097    if (create && (!above || !below)) {
67098        // One above and one below the table
67099        if (!above) {
67100            above    = document.createElement('div');
67101            above.id = above_id;
67102            Dom.addClass(above, DT.CLASS_PAGINATOR);
67103
67104            this._elContainer.insertBefore(above,this._elContainer.firstChild);
67105        }
67106
67107        if (!below) {
67108            below    = document.createElement('div');
67109            below.id = below_id;
67110            Dom.addClass(below, DT.CLASS_PAGINATOR);
67111
67112            this._elContainer.appendChild(below);
67113        }
67114    }
67115
67116    return [above,below];
67117},
67118
67119/**
67120 * Calls Paginator's destroy() method
67121 *
67122 * @method _destroyPaginator
67123 * @private
67124 */
67125_destroyPaginator : function () {
67126    var oldPag = this.get('paginator');
67127    if (oldPag) {
67128        oldPag.destroy();
67129    }
67130},
67131
67132/**
67133 * Renders the Paginator to the DataTable UI
67134 *
67135 * @method renderPaginator
67136 */
67137renderPaginator : function () {
67138    var pag = this.get("paginator");
67139    if (!pag) { return; }
67140
67141    // Add the containers if the Paginator is not configured with containers
67142    if (!pag.getContainerNodes().length) {
67143        pag.set('containers',this._defaultPaginatorContainers(true));
67144    }
67145
67146    pag.render();
67147},
67148
67149/**
67150 * Overridable method gives implementers a hook to show loading message before
67151 * changing Paginator value.
67152 *
67153 * @method doBeforePaginatorChange
67154 * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
67155 * @return {Boolean} Return true to continue changing Paginator value.
67156 */
67157doBeforePaginatorChange : function(oPaginatorState) {
67158    this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
67159    return true;
67160},
67161
67162/**
67163 * Responds to new Pagination states. By default, updates the UI to reflect the
67164 * new state. If "dynamicData" is true, current selections are purged before
67165 * a request is sent to the DataSource for data for the new state (using the
67166 * request returned by "generateRequest()").
67167 *  
67168 * @method onPaginatorChangeRequest
67169 * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
67170 */
67171onPaginatorChangeRequest : function (oPaginatorState) {
67172    var ok = this.doBeforePaginatorChange(oPaginatorState);
67173    if(ok) {
67174        // Server-side pagination
67175        if(this.get("dynamicData")) {
67176            // Get the current state
67177            var oState = this.getState();
67178            
67179            // Update pagination values
67180            oState.pagination = oPaginatorState;
67181    
67182            // Get the request for the new state
67183            var request = this.get("generateRequest")(oState, this);
67184            
67185            // Purge selections
67186            this.unselectAllRows();
67187            this.unselectAllCells();
67188            
67189            // Get the new data from the server
67190            var callback = {
67191                success : this.onDataReturnSetRows,
67192                failure : this.onDataReturnSetRows,
67193                argument : oState, // Pass along the new state to the callback
67194                scope : this
67195            };
67196            this._oDataSource.sendRequest(request, callback);
67197        }
67198        // Client-side pagination
67199        else {
67200            // Set the core pagination values silently (the second param)
67201            // to avoid looping back through the changeRequest mechanism
67202            oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true);
67203            oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true);
67204    
67205            // Update the UI
67206            this.render();
67207        }
67208    }
67209    else {
67210        YAHOO.log("Could not change Paginator value \"" + oPaginatorState + "\"", "warn", this.toString());
67211    }
67212},
67213
67214
67215
67216
67217
67218
67219
67220
67221
67222
67223
67224
67225
67226
67227
67228
67229
67230
67231
67232
67233
67234
67235
67236
67237
67238
67239
67240
67241
67242
67243
67244
67245
67246
67247
67248
67249
67250
67251
67252
67253
67254
67255
67256
67257
67258
67259
67260
67261
67262
67263// SELECTION/HIGHLIGHTING
67264
67265/*
67266 * Reference to last highlighted cell element
67267 *
67268 * @property _elLastHighlightedTd
67269 * @type HTMLElement
67270 * @private
67271 */
67272_elLastHighlightedTd : null,
67273
67274/*
67275 * ID string of last highlighted row element
67276 *
67277 * @property _sLastHighlightedTrElId
67278 * @type String
67279 * @private
67280 */
67281//_sLastHighlightedTrElId : null,
67282
67283/**
67284 * Array to track row selections (by sRecordId) and/or cell selections
67285 * (by {recordId:sRecordId, columnKey:sColumnKey})
67286 *
67287 * @property _aSelections
67288 * @type Object[]
67289 * @private
67290 */
67291_aSelections : null,
67292
67293/**
67294 * Record instance of the row selection anchor.
67295 *
67296 * @property _oAnchorRecord
67297 * @type YAHOO.widget.Record
67298 * @private
67299 */
67300_oAnchorRecord : null,
67301
67302/**
67303 * Object literal representing cell selection anchor:
67304 * {recordId:sRecordId, columnKey:sColumnKey}.
67305 *
67306 * @property _oAnchorCell
67307 * @type Object
67308 * @private
67309 */
67310_oAnchorCell : null,
67311
67312/**
67313 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
67314 * from all TR elements on the page.
67315 *
67316 * @method _unselectAllTrEls
67317 * @private
67318 */
67319_unselectAllTrEls : function() {
67320    var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
67321    Dom.removeClass(selectedRows, DT.CLASS_SELECTED);
67322},
67323
67324/**
67325 * Returns object literal of values that represent the selection trigger. Used
67326 * to determine selection behavior resulting from a key event.
67327 *
67328 * @method _getSelectionTrigger
67329 * @private
67330 */
67331_getSelectionTrigger : function() {
67332    var sMode = this.get("selectionMode");
67333    var oTrigger = {};
67334    var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex;
67335
67336    // Cell mode
67337    if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
67338        oTriggerCell = this.getLastSelectedCell();
67339        // No selected cells found
67340        if(!oTriggerCell) {
67341            return null;
67342        }
67343        else {
67344            oTriggerRecord = this.getRecord(oTriggerCell.recordId);
67345            nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
67346            elTriggerRow = this.getTrEl(oTriggerRecord);
67347            nTriggerTrIndex = this.getTrIndex(elTriggerRow);
67348
67349            // Selected cell not found on this page
67350            if(nTriggerTrIndex === null) {
67351                return null;
67352            }
67353            else {
67354                oTrigger.record = oTriggerRecord;
67355                oTrigger.recordIndex = nTriggerRecordIndex;
67356                oTrigger.el = this.getTdEl(oTriggerCell);
67357                oTrigger.trIndex = nTriggerTrIndex;
67358                oTrigger.column = this.getColumn(oTriggerCell.columnKey);
67359                oTrigger.colKeyIndex = oTrigger.column.getKeyIndex();
67360                oTrigger.cell = oTriggerCell;
67361                return oTrigger;
67362            }
67363        }
67364    }
67365    // Row mode
67366    else {
67367        oTriggerRecord = this.getLastSelectedRecord();
67368        // No selected rows found
67369        if(!oTriggerRecord) {
67370                return null;
67371        }
67372        else {
67373            // Selected row found, but is it on current page?
67374            oTriggerRecord = this.getRecord(oTriggerRecord);
67375            nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
67376            elTriggerRow = this.getTrEl(oTriggerRecord);
67377            nTriggerTrIndex = this.getTrIndex(elTriggerRow);
67378
67379            // Selected row not found on this page
67380            if(nTriggerTrIndex === null) {
67381                return null;
67382            }
67383            else {
67384                oTrigger.record = oTriggerRecord;
67385                oTrigger.recordIndex = nTriggerRecordIndex;
67386                oTrigger.el = elTriggerRow;
67387                oTrigger.trIndex = nTriggerTrIndex;
67388                return oTrigger;
67389            }
67390        }
67391    }
67392},
67393
67394/**
67395 * Returns object literal of values that represent the selection anchor. Used
67396 * to determine selection behavior resulting from a user event.
67397 *
67398 * @method _getSelectionAnchor
67399 * @param oTrigger {Object} (Optional) Object literal of selection trigger values
67400 * (for key events).
67401 * @private
67402 */
67403_getSelectionAnchor : function(oTrigger) {
67404    var sMode = this.get("selectionMode");
67405    var oAnchor = {};
67406    var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex;
67407
67408    // Cell mode
67409    if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
67410        // Validate anchor cell
67411        var oAnchorCell = this._oAnchorCell;
67412        if(!oAnchorCell) {
67413            if(oTrigger) {
67414                oAnchorCell = this._oAnchorCell = oTrigger.cell;
67415            }
67416            else {
67417                return null;
67418            }
67419        }
67420        oAnchorRecord = this._oAnchorCell.record;
67421        nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord);
67422        nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
67423        // If anchor cell is not on this page...
67424        if(nAnchorTrIndex === null) {
67425            // ...set TR index equal to top TR
67426            if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
67427                nAnchorTrIndex = 0;
67428            }
67429            // ...set TR index equal to bottom TR
67430            else {
67431                nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
67432            }
67433        }
67434
67435        oAnchor.record = oAnchorRecord;
67436        oAnchor.recordIndex = nAnchorRecordIndex;
67437        oAnchor.trIndex = nAnchorTrIndex;
67438        oAnchor.column = this._oAnchorCell.column;
67439        oAnchor.colKeyIndex = oAnchor.column.getKeyIndex();
67440        oAnchor.cell = oAnchorCell;
67441        return oAnchor;
67442    }
67443    // Row mode
67444    else {
67445        oAnchorRecord = this._oAnchorRecord;
67446        if(!oAnchorRecord) {
67447            if(oTrigger) {
67448                oAnchorRecord = this._oAnchorRecord = oTrigger.record;
67449            }
67450            else {
67451                return null;
67452            }
67453        }
67454
67455        nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord);
67456        nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
67457        // If anchor row is not on this page...
67458        if(nAnchorTrIndex === null) {
67459            // ...set TR index equal to top TR
67460            if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
67461                nAnchorTrIndex = 0;
67462            }
67463            // ...set TR index equal to bottom TR
67464            else {
67465                nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
67466            }
67467        }
67468
67469        oAnchor.record = oAnchorRecord;
67470        oAnchor.recordIndex = nAnchorRecordIndex;
67471        oAnchor.trIndex = nAnchorTrIndex;
67472        return oAnchor;
67473    }
67474},
67475
67476/**
67477 * Determines selection behavior resulting from a mouse event when selection mode
67478 * is set to "standard".
67479 *
67480 * @method _handleStandardSelectionByMouse
67481 * @param oArgs.event {HTMLEvent} Event object.
67482 * @param oArgs.target {HTMLElement} Target element.
67483 * @private
67484 */
67485_handleStandardSelectionByMouse : function(oArgs) {
67486    var elTarget = oArgs.target;
67487
67488    // Validate target row
67489    var elTargetRow = this.getTrEl(elTarget);
67490    if(elTargetRow) {
67491        var e = oArgs.event;
67492        var bSHIFT = e.shiftKey;
67493        var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
67494
67495        var oTargetRecord = this.getRecord(elTargetRow);
67496        var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
67497
67498        var oAnchor = this._getSelectionAnchor();
67499
67500        var i;
67501
67502        // Both SHIFT and CTRL
67503        if(bSHIFT && bCTRL) {
67504            // Validate anchor
67505            if(oAnchor) {
67506                if(this.isSelected(oAnchor.record)) {
67507                    // Select all rows between anchor row and target row, including target row
67508                    if(oAnchor.recordIndex < nTargetRecordIndex) {
67509                        for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) {
67510                            if(!this.isSelected(i)) {
67511                                this.selectRow(i);
67512                            }
67513                        }
67514                    }
67515                    // Select all rows between target row and anchor row, including target row
67516                    else {
67517                        for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) {
67518                            if(!this.isSelected(i)) {
67519                                this.selectRow(i);
67520                            }
67521                        }
67522                    }
67523                }
67524                else {
67525                    // Unselect all rows between anchor row and target row
67526                    if(oAnchor.recordIndex < nTargetRecordIndex) {
67527                        for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) {
67528                            if(this.isSelected(i)) {
67529                                this.unselectRow(i);
67530                            }
67531                        }
67532                    }
67533                    // Unselect all rows between target row and anchor row
67534                    else {
67535                        for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) {
67536                            if(this.isSelected(i)) {
67537                                this.unselectRow(i);
67538                            }
67539                        }
67540                    }
67541                    // Select the target row
67542                    this.selectRow(oTargetRecord);
67543                }
67544            }
67545            // Invalid anchor
67546            else {
67547                // Set anchor
67548                this._oAnchorRecord = oTargetRecord;
67549
67550                // Toggle selection of target
67551                if(this.isSelected(oTargetRecord)) {
67552                    this.unselectRow(oTargetRecord);
67553                }
67554                else {
67555                    this.selectRow(oTargetRecord);
67556                }
67557            }
67558        }
67559         // Only SHIFT
67560        else if(bSHIFT) {
67561            this.unselectAllRows();
67562
67563            // Validate anchor
67564            if(oAnchor) {
67565                // Select all rows between anchor row and target row,
67566                // including the anchor row and target row
67567                if(oAnchor.recordIndex < nTargetRecordIndex) {
67568                    for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) {
67569                        this.selectRow(i);
67570                    }
67571                }
67572                // Select all rows between target row and anchor row,
67573                // including the target row and anchor row
67574                else {
67575                    for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) {
67576                        this.selectRow(i);
67577                    }
67578                }
67579            }
67580            // Invalid anchor
67581            else {
67582                // Set anchor
67583                this._oAnchorRecord = oTargetRecord;
67584
67585                // Select target row only
67586                this.selectRow(oTargetRecord);
67587            }
67588        }
67589        // Only CTRL
67590        else if(bCTRL) {
67591            // Set anchor
67592            this._oAnchorRecord = oTargetRecord;
67593
67594            // Toggle selection of target
67595            if(this.isSelected(oTargetRecord)) {
67596                this.unselectRow(oTargetRecord);
67597            }
67598            else {
67599                this.selectRow(oTargetRecord);
67600            }
67601        }
67602        // Neither SHIFT nor CTRL
67603        else {
67604            this._handleSingleSelectionByMouse(oArgs);
67605            return;
67606        }
67607    }
67608},
67609
67610/**
67611 * Determines selection behavior resulting from a key event when selection mode
67612 * is set to "standard".
67613 *
67614 * @method _handleStandardSelectionByKey
67615 * @param e {HTMLEvent} Event object.
67616 * @private
67617 */
67618_handleStandardSelectionByKey : function(e) {
67619    var nKey = Ev.getCharCode(e);
67620
67621    if((nKey == 38) || (nKey == 40)) {
67622        var bSHIFT = e.shiftKey;
67623
67624        // Validate trigger
67625        var oTrigger = this._getSelectionTrigger();
67626        // Arrow selection only works if last selected row is on current page
67627        if(!oTrigger) {
67628            return null;
67629        }
67630
67631        Ev.stopEvent(e);
67632
67633        // Validate anchor
67634        var oAnchor = this._getSelectionAnchor(oTrigger);
67635
67636        // Determine which direction we're going to
67637        if(bSHIFT) {
67638            // Selecting down away from anchor row
67639            if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) {
67640                this.selectRow(this.getNextTrEl(oTrigger.el));
67641            }
67642            // Selecting up away from anchor row
67643            else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) {
67644                this.selectRow(this.getPreviousTrEl(oTrigger.el));
67645            }
67646            // Unselect trigger
67647            else {
67648                this.unselectRow(oTrigger.el);
67649            }
67650        }
67651        else {
67652            this._handleSingleSelectionByKey(e);
67653        }
67654    }
67655},
67656
67657/**
67658 * Determines selection behavior resulting from a mouse event when selection mode
67659 * is set to "single".
67660 *
67661 * @method _handleSingleSelectionByMouse
67662 * @param oArgs.event {HTMLEvent} Event object.
67663 * @param oArgs.target {HTMLElement} Target element.
67664 * @private
67665 */
67666_handleSingleSelectionByMouse : function(oArgs) {
67667    var elTarget = oArgs.target;
67668
67669    // Validate target row
67670    var elTargetRow = this.getTrEl(elTarget);
67671    if(elTargetRow) {
67672        var oTargetRecord = this.getRecord(elTargetRow);
67673
67674        // Set anchor
67675        this._oAnchorRecord = oTargetRecord;
67676
67677        // Select only target
67678        this.unselectAllRows();
67679        this.selectRow(oTargetRecord);
67680    }
67681},
67682
67683/**
67684 * Determines selection behavior resulting from a key event when selection mode
67685 * is set to "single".
67686 *
67687 * @method _handleSingleSelectionByKey
67688 * @param e {HTMLEvent} Event object.
67689 * @private
67690 */
67691_handleSingleSelectionByKey : function(e) {
67692    var nKey = Ev.getCharCode(e);
67693
67694    if((nKey == 38) || (nKey == 40)) {
67695        // Validate trigger
67696        var oTrigger = this._getSelectionTrigger();
67697        // Arrow selection only works if last selected row is on current page
67698        if(!oTrigger) {
67699            return null;
67700        }
67701
67702        Ev.stopEvent(e);
67703
67704        // Determine the new row to select
67705        var elNew;
67706        if(nKey == 38) { // arrow up
67707            elNew = this.getPreviousTrEl(oTrigger.el);
67708
67709            // Validate new row
67710            if(elNew === null) {
67711                //TODO: wrap around to last tr on current page
67712                //elNew = this.getLastTrEl();
67713
67714                //TODO: wrap back to last tr of previous page
67715
67716                // Top row selection is sticky
67717                elNew = this.getFirstTrEl();
67718            }
67719        }
67720        else if(nKey == 40) { // arrow down
67721            elNew = this.getNextTrEl(oTrigger.el);
67722
67723            // Validate new row
67724            if(elNew === null) {
67725                //TODO: wrap around to first tr on current page
67726                //elNew = this.getFirstTrEl();
67727
67728                //TODO: wrap forward to first tr of previous page
67729
67730                // Bottom row selection is sticky
67731                elNew = this.getLastTrEl();
67732            }
67733        }
67734
67735        // Unselect all rows
67736        this.unselectAllRows();
67737
67738        // Select the new row
67739        this.selectRow(elNew);
67740
67741        // Set new anchor
67742        this._oAnchorRecord = this.getRecord(elNew);
67743    }
67744},
67745
67746/**
67747 * Determines selection behavior resulting from a mouse event when selection mode
67748 * is set to "cellblock".
67749 *
67750 * @method _handleCellBlockSelectionByMouse
67751 * @param oArgs.event {HTMLEvent} Event object.
67752 * @param oArgs.target {HTMLElement} Target element.
67753 * @private
67754 */
67755_handleCellBlockSelectionByMouse : function(oArgs) {
67756    var elTarget = oArgs.target;
67757
67758    // Validate target cell
67759    var elTargetCell = this.getTdEl(elTarget);
67760    if(elTargetCell) {
67761        var e = oArgs.event;
67762        var bSHIFT = e.shiftKey;
67763        var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
67764
67765        var elTargetRow = this.getTrEl(elTargetCell);
67766        var nTargetTrIndex = this.getTrIndex(elTargetRow);
67767        var oTargetColumn = this.getColumn(elTargetCell);
67768        var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
67769        var oTargetRecord = this.getRecord(elTargetRow);
67770        var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
67771        var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
67772
67773        var oAnchor = this._getSelectionAnchor();
67774
67775        var allRows = this.getTbodyEl().rows;
67776        var startIndex, endIndex, currentRow, i, j;
67777
67778        // Both SHIFT and CTRL
67779        if(bSHIFT && bCTRL) {
67780
67781            // Validate anchor
67782            if(oAnchor) {
67783                // Anchor is selected
67784                if(this.isSelected(oAnchor.cell)) {
67785                    // All cells are on the same row
67786                    if(oAnchor.recordIndex === nTargetRecordIndex) {
67787                        // Select all cells between anchor cell and target cell, including target cell
67788                        if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
67789                            for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
67790                                this.selectCell(elTargetRow.cells[i]);
67791                            }
67792                        }
67793                        // Select all cells between target cell and anchor cell, including target cell
67794                        else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
67795                            for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
67796                                this.selectCell(elTargetRow.cells[i]);
67797                            }
67798                        }
67799                    }
67800                    // Anchor row is above target row
67801                    else if(oAnchor.recordIndex < nTargetRecordIndex) {
67802                        startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
67803                        endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
67804
67805                        // Select all cells from startIndex to endIndex on rows between anchor row and target row
67806                        for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
67807                            for(j=startIndex;
67807 j<=endIndex; j++) {
67808                                this.selectCell(allRows[i].cells[j]);
67809                            }
67810                        }
67811                    }
67812                    // Anchor row is below target row
67813                    else {
67814                        startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex);
67815                        endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex);
67816
67817                        // Select all cells from startIndex to endIndex on rows between target row and anchor row
67818                        for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) {
67819                            for(j=endIndex; j>=startIndex; j--) {
67820                                this.selectCell(allRows[i].cells[j]);
67821                            }
67822                        }
67823                    }
67824                }
67825                // Anchor cell is unselected
67826                else {
67827                    // All cells are on the same row
67828                    if(oAnchor.recordIndex === nTargetRecordIndex) {
67829                        // Unselect all cells between anchor cell and target cell
67830                        if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
67831                            for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
67832                                this.unselectCell(elTargetRow.cells[i]);
67833                            }
67834                        }
67835                        // Select all cells between target cell and anchor cell
67836                        else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
67837                            for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
67838                                this.unselectCell(elTargetRow.cells[i]);
67839                            }
67840                        }
67841                    }
67842                    // Anchor row is above target row
67843                    if(oAnchor.recordIndex < nTargetRecordIndex) {
67844                        // Unselect all cells from anchor cell to target cell
67845                        for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
67846                            currentRow = allRows[i];
67847                            for(j=0; j<currentRow.cells.length; j++) {
67848                                // This is the anchor row, only unselect cells after the anch
67848or cell
67849                                if(currentRow.sectionRowIndex === oAnchor.trIndex) {
67850                                    if(j>oAnchor.colKeyIndex) {
67851                                        this.unselectCell(currentRow.cells[j]);
67852                                    }
67853                                }
67854                                // This is the target row, only unelect cells before the target cell
67855                                else if(currentRow.sectionRowIndex === nTargetTrIndex) {
67856                                    if(j<nTargetColKeyIndex) {
67857                                        this.unselectCell(currentRow.cells[j]);
67858                                    }
67859                                }
67860                                // Unselect all cells on this row
67861                                else {
67862                                    this.unselectCell(currentRow.cells[j]);
67863                                }
67864                            }
67865                        }
67866                    }
67867                    // Anchor row is below target row
67868                    else {
67869                        // Unselect all cells from target cell to anchor cell
67870                        for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
67871                            currentRow = allRows[i];
67872                            for(j=0; j<currentRow.cells.length; j++) {
67873                                // This is the target row, only unselect cells after the target cell
67874                                if(currentRow.sectionRowIndex == nTargetTrIndex) {
67875                                    if(j>nTargetColKeyIndex) {
67876                                        this.unselectCell(currentRow.cells[j]);
67877                                    }
67878                                }
67879                                // This is the anchor row, only unselect cells before the anchor cell
67880                                else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
67881                                    if(j<oAnchor.colKeyIndex) {
67882                                        this.unselectCell(currentRow.cells[j]);
67883                                    }
67884                                }
67885                                // Unselect all cells on this row
67886                                else {
67887                                    this.unselectCell(currentRow.cells[j]);
67888                                }
67889                            }
67890                        }
67891                    }
67892
67893                    // Select the target cell
67894                    this.selectCell(elTargetCell);
67895                }
67896            }
67897            // Invalid anchor
67898            else {
67899                // Set anchor
67900                this._oAnchorCell = oTargetCell;
67901
67902                // Toggle selection of target
67903                if(this.isSelected(oTargetCell)) {
67904                    this.unselectCell(oTargetCell);
67905                }
67906                else {
67907                    this.selectCell(oTargetCell);
67908                }
67909            }
67910
67911        }
67912         // Only SHIFT
67913        else if(bSHIFT) {
67914            this.unselectAllCells();
67915
67916            // Validate anchor
67917            if(oAnchor) {
67918                // All cells are on the same row
67919                if(oAnchor.recordIndex === nTargetRecordIndex) {
67920                    // Select all cells between anchor cell and target cell,
67921                    // including the anchor cell and target cell
67922                    if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
67923                        for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
67924                            this.selectCell(elTargetRow.cells[i]);
67925                        }
67926                    }
67927                    // Select all cells between target cell and anchor cell
67928                    // including the target cell and anchor cell
67929                    else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
67930                        for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
67931                            this.selectCell(elTargetRow.cells[i]);
67932                        }
67933                    }
67934                }
67935                // Anchor row is above target row
67936                else if(oAnchor.recordIndex < nTargetRecordIndex) {
67937                    // Select the cellblock from anchor cell to target cell
67938                    // including the anchor cell and the target cell
67939                    startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
67940                    endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
67941
67942                    for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
67943                        for(j=startIndex; j<=endIndex; j++) {
67944                            this.selectCell(allRows[i].cells[j]);
67945                        }
67946                    }
67947                }
67948                // Anchor row is below target row
67949                else {
67950                    // Select the cellblock from target cell to anchor cell
67951                    // including the target cell and the anchor cell
67952                    startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
67953                    endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
67954
67955                    for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
67956                        for(j=startIndex;
67956 j<=endIndex; j++) {
67957                            this.selectCell(allRows[i].cells[j]);
67958                        }
67959                    }
67960                }
67961            }
67962            // Invalid anchor
67963            else {
67964                // Set anchor
67965                this._oAnchorCell = oTargetCell;
67966
67967                // Select target only
67968                this.selectCell(oTargetCell);
67969            }
67970        }
67971        // Only CTRL
67972        else if(bCTRL) {
67973
67974            // Set anchor
67975            this._oAnchorCell = oTargetCell;
67976
67977            // Toggle selection of target
67978            if(this.isSelected(oTargetCell)) {
67979                this.unselectCell(oTargetCell);
67980            }
67981            else {
67982                this.selectCell(oTargetCell);
67983            }
67984
67985        }
67986        // Neither SHIFT nor CTRL
67987        else {
67988            this._handleSingleCellSelectionByMouse(oArgs);
67989        }
67990    }
67991},
67992
67993/**
67994 * Determines selection behavior resulting from a key event when selection mode
67995 * is set to "cellblock".
67996 *
67997 * @method _handleCellBlockSelectionByKey
67998 * @param e {HTMLEvent} Event object.
67999 * @private
68000 */
68001_handleCellBlockSelectionByKey : function(e) {
68002    var nKey = Ev.getCharCode(e);
68003    var bSHIFT = e.shiftKey;
68004    if((nKey == 9) || !bSHIFT) {
68005        this._handleSingleCellSelectionByKey(e);
68006        return;
68007    }
68008
68009    if((nKey > 36) && (nKey < 41)) {
68010        // Validate trigger
68011        var oTrigger = this._getSelectionTrigger();
68012        // Arrow selection only works if last selected row is on current page
68013        if(!oTrigger) {
68014            return null;
68015        }
68016
68017        Ev.stopEvent(e);
68018
68019        // Validate anchor
68020        var oAnchor = this._getSelectionAnchor(oTrigger);
68021
68022        var i, startIndex, endIndex, elNew, elNewRow;
68023        var allRows = this.getTbodyEl().rows;
68024        var elThisRow = oTrigger.el.parentNode;
68025
68026        // Determine which direction we're going to
68027
68028        if(nKey == 40) { // arrow down
68029            // Selecting away from anchor cell
68030            if(oAnchor.recordIndex <= oTrigger.recordIndex) {
68031                // Select the horiz block on the next row...
68032                // ...making sure there is room below the trigger row
68033                elNewRow = this.getNextTrEl(oTrigger.el);
68034                if(elNewRow) {
68035                    startIndex = oAnchor.colKeyIndex;
68036                    endIndex = oTrigger.colKeyIndex;
68037                    // ...going left
68038                    if(startIndex > endIndex) {
68039                        for(i=startIndex; i>=endIndex; i--) {
68040                            elNew = elNewRow.cells[i];
68041                            this.selectCell(elNew);
68042                        }
68043                    }
68044                    // ... going right
68045                    else {
68046                        for(i=startIndex; i<=endIndex; i++) {
68047                            elNew = elNewRow.cells[i];
68048                            this.selectCell(elNew);
68049                        }
68050                    }
68051                }
68052            }
68053            // Unselecting towards anchor cell
68054            else {
68055                startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
68056                endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
68057                // Unselect the horiz block on this row towards the next row
68058                for(i=startIndex; i<=endIndex; i++) {
68059                    this.unselectCell(elThisRow.cells[i]);
68060                }
68061            }
68062        }
68063        // Arrow up
68064        else if(nKey == 38) {
68065            // Selecting away from anchor cell
68066            if(oAnchor.recordIndex >= oTrigger.recordIndex) {
68067                // Select the horiz block on the previous row...
68068                // ...making sure there is room
68069                elNewRow = this.getPreviousTrEl(oTrigger.el);
68070                if(elNewRow) {
68071                    // Select in order from anchor to trigger...
68072                    startIndex = oAnchor.colKeyIndex;
68073                    endIndex = oTrigger.colKeyIndex;
68074                    // ...going left
68075                    if(startIndex > endIndex) {
68076                        for(i=startIndex; i>=endIndex; i--) {
68077                            elNew = elNewRow.cells[i];
68078                            this.selectCell(elNew);
68079                        }
68080                    }
68081                    // ... going right
68082                    else {
68083                        for(i=startIndex; i<=endIndex; i++) {
68084                            elNew = elNewRow.cells[i];
68085                            this.selectCell(elNew);
68086                        }
68087                    }
68088                }
68089            }
68090            // Unselecting towards anchor cell
68091            else {
68092                startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
68093                endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
68094                // Unselect the horiz block on this row towards the previous row
68095                for(i=startIndex; i<=endIndex; i++) {
68096                    this.unselectCell(elThisRow.cells[i]);
68097                }
68098            }
68099        }
68100        // Arrow right
68101        else if(nKey == 39) {
68102            // Selecting away from anchor cell
68103            if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
68104                // Select the next vert block to the right...
68105                // ...making sure there is room
68106                if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
68107                    // Select in order from anchor to trigger...
68108                    startIndex = oAnchor.trIndex;
68109                    endIndex = oTrigger.trIndex;
68110                    // ...going up
68111                    if(startIndex > endIndex) {
68112                        for(i=startIndex; i>=endIndex; i--) {
68113                            elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
68114                            this.selectCell(elNew);
68115                        }
68116                    }
68117                    // ... going down
68118                    else {
68119                        for(i=startIndex; i<=endIndex; i++) {
68120                            elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
68121                            this.selectCell(elNew);
68122                        }
68123                    }
68124                }
68125            }
68126            // Unselecting towards anchor cell
68127            else {
68128                // Unselect the vert block on this column towards the right
68129                startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
68130                endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
68131                for(i=startIndex; i<=endIndex; i++) {
68132                    this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
68133                }
68134            }
68135        }
68136        // Arrow left
68137        else if(nKey == 37) {
68138            // Selecting away from anchor cell
68139            if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
68140                //Select the previous vert block to the left
68141                if(oTrigger.colKeyIndex > 0) {
68142                    // Select in order from anchor to trigger...
68143                    startIndex = oAnchor.trIndex;
68144                    endIndex = oTrigger.trIndex;
68145                    // ...going up
68146                    if(startIndex > endIndex) {
68147                        for(i=startIndex; i>=endIndex; i--) {
68148                            elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
68149                            this.selectCell(elNew);
68150                        }
68151                    }
68152                    // ... going down
68153                    else {
68154                        for(i=startIndex; i<=endIndex; i++) {
68155                            elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
68156                            this.selectCell(elNew);
68157                        }
68158                    }
68159                }
68160            }
68161            // Unselecting towards anchor cell
68162            else {
68163                // Unselect the vert block on this column towards the left
68164                startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
68165                endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
68166                for(i=startIndex; i<=endIndex; i++) {
68167                    this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
68168                }
68169            }
68170        }
68171    }
68172},
68173
68174/**
68175 * Determines selection behavior resulting from a mouse event when selection mode
68176 * is set to "cellrange".
68177 *
68178 * @method _handleCellRangeSelectionByMouse
68179 * @param oArgs.event {HTMLEvent} Event object.
68180 * @param oArgs.target {HTMLElement} Target element.
68181 * @private
68182 */
68183_handleCellRangeSelectionByMouse : function(oArgs) {
68184    var elTarget = oArgs.target;
68185
68186    // Validate target cell
68187    var elTargetCell = this.getTdEl(elTarget);
68188    if(elTargetCell) {
68189        var e = oArgs.event;
68190        var bSHIFT = e.shiftKey;
68191        var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
68192
68193        var elTargetRow = this.getTrEl(elTargetCell);
68194        var nTargetTrIndex = this.getTrIndex(elTargetRow);
68195        var oTargetColumn = this.getColumn(elTargetCell);
68196        var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
68197        var oTargetRecord = this.getRecord(elTargetRow);
68198        var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
68199        var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
68200
68201        var oAnchor = this._getSelectionAnchor();
68202
68203        var allRows = this.getTbodyEl().rows;
68204        var currentRow, i, j;
68205
68206        // Both SHIFT and CTRL
68207        if(bSHIFT && bCTRL) {
68208
68209            // Validate anchor
68210            if(oAnchor) {
68211                // Anchor is selected
68212                if(this.isSelected(oAnchor.cell)) {
68213                    // All cells are on the same row
68214                    if(oAnchor.recordIndex === nTargetRecordIndex) {
68215                        // Select all cells between anchor cell and target cell, including target cell
68216                        if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
68217                            for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
68218                                this.selectCell(elTargetRow.cells[i]);
68219                            }
68220                        }
68221                        // Select all cells between target cell and anchor cell, including target cell
68222                        else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
68223                            for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
68224                                this.selectCell(elTargetRow.cells[i]);
68225                            }
68226                        }
68227                    }
68228                    // Anchor row is above target row
68229                    else if(oAnchor.recordIndex < nTargetRecordIndex) {
68230                        // Select all cells on anchor row from anch
68230or cell to the end of the row
68231                        for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) {
68232                            this.selectCell(elTargetRow.cells[i]);
68233                        }
68234
68235                        // Select all cells on all rows between anchor row and target row
68236                        for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) {
68237                            for(j=0; j<allRows[i].cells.length; j++){
68238                                this.selectCell(allRows[i].cells[j]);
68239                            }
68240                        }
68241
68242                        // Select all cells on target row from first cell to the target cell
68243                        for(i=0; i<=nTargetColKeyIndex; i++) {
68244                            this.selectCell(elTargetRow.cells[i]);
68245                        }
68246                    }
68247                    // Anchor row is below target row
68248                    else {
68249                        // Select all cells on target row from target cell to the end of the row
68250                        for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) {
68251                            this.selectCell(elTargetRow.cells[i]);
68252                        }
68253
68254                        // Select all cells on all rows between target row and anchor row
68255                        for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) {
68256                            for(j=0; j<allRows[i].cells.length; j++){
68257                                this.selectCell(allRows[i].cells[j]);
68258                            }
68259                        }
68260
68261                        // Select all cells on anchor row from first cell to the anchor cell
68262                        for(i=0; i<oAnchor.colKeyIndex; i++) {
68263                            this.selectCell(elTargetRow.cells[i]);
68264                        }
68265                    }
68266                }
68267                // Anchor cell is unselected
68268                else {
68269                    // All cells are on the same row
68270                    if(oAnchor.recordIndex === nTargetRecordIndex) {
68271                        // Unselect all cells between anchor cell and target cell
68272                        if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
68273                            for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
68274                                this.unselectCell(elTargetRow.cells[i]);
68275                            }
68276                        }
68277                        // Select all cells between target cell and anchor cell
68278                        else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
68279                            for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
68280                                this.unselectCell(elTargetRow.cells[i]);
68281                            }
68282                        }
68283                    }
68284                    // Anchor row is above target row
68285                    if(oAnchor.recordIndex < nTargetRecordIndex) {
68286                        // Unselect all cells from anchor cell to target cell
68287                        for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
68288                            currentRow = allRows[i];
68289                            for(j=0; j<currentRow.cells.length; j++) {
68290                                // This is the anchor row, only unselect cells after the anch
68290or cell
68291                                if(currentRow.sectionRowIndex === oAnchor.trIndex) {
68292                                    if(j>oAnchor.colKeyIndex) {
68293                                        this.unselectCell(currentRow.cells[j]);
68294                                    }
68295                                }
68296                                // This is the target row, only unelect cells before the target cell
68297                                else if(currentRow.sectionRowIndex === nTargetTrIndex) {
68298                                    if(j<nTargetColKeyIndex) {
68299                                        this.unselectCell(currentRow.cells[j]);
68300                                    }
68301                                }
68302                                // Unselect all cells on this row
68303                                else {
68304                                    this.unselectCell(currentRow.cells[j]);
68305                                }
68306                            }
68307                        }
68308                    }
68309                    // Anchor row is below target row
68310                    else {
68311                        // Unselect all cells from target cell to anchor cell
68312                        for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
68313                            currentRow = allRows[i];
68314                            for(j=0; j<currentRow.cells.length; j++) {
68315                                // This is the target row, only unselect cells after the target cell
68316                                if(currentRow.sectionRowIndex == nTargetTrIndex) {
68317                                    if(j>nTargetColKeyIndex) {
68318                                        this.unselectCell(currentRow.cells[j]);
68319                                    }
68320                                }
68321                                // This is the anchor row, only unselect cells before the anchor cell
68322                                else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
68323                                    if(j<oAnchor.colKeyIndex) {
68324                                        this.unselectCell(currentRow.cells[j]);
68325                                    }
68326                                }
68327                                // Unselect all cells on this row
68328                                else {
68329                                    this.unselectCell(currentRow.cells[j]);
68330                                }
68331                            }
68332                        }
68333                    }
68334
68335                    // Select the target cell
68336                    this.selectCell(elTargetCell);
68337                }
68338            }
68339            // Invalid anchor
68340            else {
68341                // Set anchor
68342                this._oAnchorCell = oTargetCell;
68343
68344                // Toggle selection of target
68345                if(this.isSelected(oTargetCell)) {
68346                    this.unselectCell(oTargetCell);
68347                }
68348                else {
68349                    this.selectCell(oTargetCell);
68350                }
68351            }
68352        }
68353         // Only SHIFT
68354        else if(bSHIFT) {
68355
68356            this.unselectAllCells();
68357
68358            // Validate anchor
68359            if(oAnchor) {
68360                // All cells are on the same row
68361                if(oAnchor.recordIndex === nTargetRecordIndex) {
68362                    // Select all cells between anchor cell and target cell,
68363                    // including the anchor cell and target cell
68364                    if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
68365                        for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
68366                            this.selectCell(elTargetRow.cells[i]);
68367                        }
68368                    }
68369                    // Select all cells between target cell and anchor cell
68370                    // including the target cell and anchor cell
68371                    else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
68372                        for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
68373                            this.selectCell(elTargetRow.cells[i]);
68374                        }
68375                    }
68376                }
68377                // Anchor row is above target row
68378                else if(oAnchor.recordIndex < nTargetRecordIndex) {
68379                    // Select all cells from anchor cell to target cell
68380                    // including the anchor cell and target cell
68381                    for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
68382                        currentRow = allRows[i];
68383                        for(j=0; j<currentRow.cells.length; j++) {
68384                            // This is the anchor row, only select the anchor cell and after
68385                            if(currentRow.sectionRowIndex == oAnchor.trIndex) {
68386                                if(j>=oAnchor.colKeyIndex) {
68387                                    this.selectCell(currentRow.cells[j]);
68388                                }
68389                            }
68390                            // This is the target row, only select the target cell and before
68391                            else if(currentRow.sectionRowIndex == nTargetTrIndex) {
68392                                if(j<=nTargetColKeyIndex) {
68393                                    this.selectCell(currentRow.cells[j]);
68394                                }
68395                            }
68396                            // Select all cells on this row
68397                            else {
68398                                this.selectCell(currentRow.cells[j]);
68399                            }
68400                        }
68401                    }
68402                }
68403                // Anchor row is below target row
68404                else {
68405                    // Select all cells from target cell to anchor cell,
68406                    // including the target cell and anchor cell
68407                    for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
68408                        currentRow = allRows[i];
68409                        for(j=0; j<currentRow.cells.length; j++) {
68410                            // This is the target row, only select the target cell and after
68411                            if(currentRow.sectionRowIndex == nTargetTrIndex) {
68412                                if(j>=nTargetColKeyIndex) {
68413                                    this.selectCell(currentRow.cells[j]);
68414                                }
68415                            }
68416                            // This is the anchor row, only select the anchor cell and before
68417                            else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
68418                                if(j<=oAnchor.colKeyIndex) {
68419                                    this.selectCell(currentRow.cells[j]);
68420                                }
68421                            }
68422                            // Select all cells on this row
68423                            else {
68424                                this.selectCell(currentRow.cells[j]);
68425                            }
68426                        }
68427                    }
68428                }
68429            }
68430            // Invalid anchor
68431            else {
68432                // Set anchor
68433                this._oAnchorCell = oTargetCell;
68434
68435                // Select target only
68436                this.selectCell(oTargetCell);
68437            }
68438
68439
68440        }
68441        // Only CTRL
68442        else if(bCTRL) {
68443
68444            // Set anchor
68445            this._oAnchorCell = oTargetCell;
68446
68447            // Toggle selection of target
68448            if(this.isSelected(oTargetCell)) {
68449                this.unselectCell(oTargetCell);
68450            }
68451            else {
68452                this.selectCell(oTargetCell);
68453            }
68454
68455        }
68456        // Neither SHIFT nor CTRL
68457        else {
68458            this._handleSingleCellSelectionByMouse(oArgs);
68459        }
68460    }
68461},
68462
68463/**
68464 * Determines selection behavior resulting from a key event when selection mode
68465 * is set to "cellrange".
68466 *
68467 * @method _handleCellRangeSelectionByKey
68468 * @param e {HTMLEvent} Event object.
68469 * @private
68470 */
68471_handleCellRangeSelectionByKey : function(e) {
68472    var nKey = Ev.getCharCode(e);
68473    var bSHIFT = e.shiftKey;
68474    if((nKey == 9) || !bSHIFT) {
68475        this._handleSingleCellSelectionByKey(e);
68476        return;
68477    }
68478
68479    if((nKey > 36) && (nKey < 41)) {
68480        // Validate trigger
68481        var oTrigger = this._getSelectionTrigger();
68482        // Arrow selection only works if last selected row is on current page
68483        if(!oTrigger) {
68484            return null;
68485        }
68486
68487        Ev.stopEvent(e);
68488
68489        // Validate anchor
68490        var oAnchor = this._getSelectionAnchor(oTrigger);
68491
68492        var i, elNewRow, elNew;
68493        var allRows = this.getTbodyEl().rows;
68494        var elThisRow = oTrigger.el.parentNode;
68495
68496        // Arrow down
68497        if(nKey == 40) {
68498            elNewRow = this.getNextTrEl(oTrigger.el);
68499
68500            // Selecting away from anchor cell
68501            if(oAnchor.recordIndex <= oTrigger.recordIndex) {
68502                // Select all cells to the end of this row
68503                for(i=oTrigger.colKeyIndex+1; i<elThisRow.cells.length; i++){
68504                    elNew = elThisRow.cells[i];
68505                    this.selectCell(elNew);
68506                }
68507
68508                // Select some of the cells on the next row down
68509                if(elNewRow) {
68510                    for(i=0; i<=oTrigger.colKeyIndex; i++){
68511                        elNew = elNewRow.cells[i];
68512                        this.selectCell(elNew);
68513                    }
68514                }
68515            }
68516            // Unselecting towards anchor cell
68517            else {
68518                // Unselect all cells to the end of this row
68519                for(i=oTrigger.colKeyIndex; i<elThisRow.cells.length; i++){
68520                    this.unselectCell(elThisRow.cells[i]);
68521                }
68522
68523                // Unselect some of the cells on the next row down
68524                if(elNewRow) {
68525                    for(i=0; i<oTrigger.colKeyIndex; i++){
68526                        this.unselectCell(elNewRow.cells[i]);
68527                    }
68528                }
68529            }
68530        }
68531        // Arrow up
68532        else if(nKey == 38) {
68533            elNewRow = this.getPreviousTrEl(oTrigger.el);
68534
68535            // Selecting away from anchor cell
68536            if(oAnchor.recordIndex >= oTrigger.recordIndex) {
68537                // Select all the cells to the beginning of this row
68538                for(i=oTrigger.colKeyIndex-1; i>-1; i--){
68539                    elNew = elThisRow.cells[i];
68540                    this.selectCell(elNew);
68541                }
68542
68543                // Select some of the cells from the end of the previous row
68544                if(elNewRow) {
68545                    for(i=elThisRow.cells.length-1; i>=oTrigger.colKeyIndex; i--){
68546                        elNew = elNewRow.cells[i];
68547                        this.selectCell(elNew);
68548                    }
68549                }
68550            }
68551            // Unselecting towards anchor cell
68552            else {
68553                // Unselect all the cells to the beginning of this row
68554                for(i=oTrigger.colKeyIndex; i>-1; i--){
68555                    this.unselectCell(elThisRow.cells[i]);
68556                }
68557
68558                // Unselect some of the cells from the end of the previous row
68559                if(elNewRow) {
68560                    for(i=elThisRow.cells.length-1; i>oTrigger.colKeyIndex; i--){
68561                        this.unselectCell(elNewRow.cells[i]);
68562                    }
68563                }
68564            }
68565        }
68566        // Arrow right
68567        else if(nKey == 39) {
68568            elNewRow = this.getNextTrEl(oTrigger.el);
68569
68570            // Selecting away from anchor cell
68571            if(oAnchor.recordIndex < oTrigger.recordIndex) {
68572                // Select the next cell to the right
68573                if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
68574                    elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
68575                    this.selectCell(elNew);
68576                }
68577                // Select the first cell of the next row
68578                else if(elNewRow) {
68579                    elNew = elNewRow.cells[0];
68580                    this.selectCell(elNew);
68581                }
68582            }
68583            // Unselecting towards anchor cell
68584            else if(oAnchor.recordIndex > oTrigger.recordIndex) {
68585                this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
68586
68587                // Unselect this cell towards the right
68588                if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
68589                }
68590                // Unselect this cells towards the first cell of the next row
68591                else {
68592                }
68593            }
68594            // Anchor is on this row
68595            else {
68596                // Selecting away from anchor
68597                if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
68598                    // Select the next cell to the right
68599                    if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
68600                        elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
68601                        this.selectCell(elNew);
68602                    }
68603                    // Select the first cell on the next row
68604                    else if(oTrigger.trIndex < allRows.length-1){
68605                        elNew = elNewRow.cells[0];
68606                        this.selectCell(elNew);
68607                    }
68608                }
68609                // Unselecting towards anchor
68610                else {
68611                    // Unselect this cell towards the right
68612                    this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
68613                }
68614            }
68615        }
68616        // Arrow left
68617        else if(nKey == 37) {
68618            elNewRow = this.getPreviousTrEl(oTrigger.el);
68619
68620            // Unselecting towards the anchor
68621            if(oAnchor.recordIndex < oTrigger.recordIndex) {
68622                this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
68623
68624                // Unselect this cell towards the left
68625                if(oTrigger.colKeyIndex > 0) {
68626                }
68627                // Unselect this cell towards the last cell of the previous row
68628                else {
68629                }
68630            }
68631            // Selecting towards the anchor
68632            else if(oAnchor.recordIndex > oTrigger.recordIndex) {
68633                // Select the next cell to the left
68634                if(oTrigger.colKeyIndex > 0) {
68635                    elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
68636                    this.selectCell(elNew);
68637                }
68638                // Select the last cell of the previous row
68639                else if(oTrigger.trIndex > 0){
68640                    elNew = elNewRow.cells[elNewRow.cells.length-1];
68641                    this.selectCell(elNew);
68642                }
68643            }
68644            // Anchor is on this row
68645            else {
68646                // Selecting away from anchor cell
68647                if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
68648                    // Select the next cell to the left
68649                    if(oTrigger.colKeyIndex > 0) {
68650                        elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
68651                        this.selectCell(elNew);
68652                    }
68653                    // Select the last cell of the previous row
68654                    else if(oTrigger.trIndex > 0){
68655                        elNew = elNewRow.cells[elNewRow.cells.length-1];
68656                        this.selectCell(elNew);
68657                    }
68658                }
68659                // Unselecting towards anchor cell
68660                else {
68661                    this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
68662
68663                    // Unselect this cell towards the left
68664                    if(oTrigger.colKeyIndex > 0) {
68665                    }
68666                    // Unselect this cell towards the last cell of the previous row
68667                    else {
68668                    }
68669                }
68670            }
68671        }
68672    }
68673},
68674
68675/**
68676 * Determines selection behavior resulting from a mouse event when selection mode
68677 * is set to "singlecell".
68678 *
68679 * @method _handleSingleCellSelectionByMouse
68680 * @param oArgs.event {HTMLEvent} Event object.
68681 * @param oArgs.target {HTMLElement} Target element.
68682 * @private
68683 */
68684_handleSingleCellSelectionByMouse : function(oArgs) {
68685    var elTarget = oArgs.target;
68686
68687    // Validate target cell
68688    var elTargetCell = this.getTdEl(elTarget);
68689    if(elTargetCell) {
68690        var elTargetRow = this.getTrEl(elTargetCell);
68691        var oTargetRecord = this.getRecord(elTargetRow);
68692        var oTargetColumn = this.getColumn(elTargetCell);
68693        var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
68694
68695        // Set anchor
68696        this._oAnchorCell = oTargetCell;
68697
68698        // Select only target
68699        this.unselectAllCells();
68700        this.selectCell(oTargetCell);
68701    }
68702},
68703
68704/**
68705 * Determines selection behavior resulting from a key event when selection mode
68706 * is set to "singlecell".
68707 *
68708 * @method _handleSingleCellSelectionByKey
68709 * @param e {HTMLEvent} Event object.
68710 * @private
68711 */
68712_handleSingleCellSelectionByKey : function(e) {
68713    var nKey = Ev.getCharCode(e);
68714    if((nKey == 9) || ((nKey > 36) && (nKey < 41))) {
68715        var bSHIFT = e.shiftKey;
68716
68717        // Validate trigger
68718        var oTrigger = this._getSelectionTrigger();
68719        // Arrow selection only works if last selected row is on current page
68720        if(!oTrigger) {
68721            return null;
68722        }
68723
68724        // Determine the new cell to select
68725        var elNew;
68726        if(nKey == 40) { // Arrow down
68727            elNew = this.getBelowTdEl(oTrigger.el);
68728
68729            // Validate new cell
68730            if(elNew === null) {
68731                //TODO: wrap around to first tr on current page
68732
68733                //TODO: wrap forward to first tr of next page
68734
68735                // Bottom selection is sticky
68736                elNew = oTrigger.el;
68737            }
68738        }
68739        else if(nKey == 38) { // Arrow up
68740            elNew = this.getAboveTdEl(oTrigger.el);
68741
68742            // Validate new cell
68743            if(elNew === null) {
68744                //TODO: wrap around to last tr on current page
68745
68746                //TODO: wrap back to last tr of previous page
68747
68748                // Top selection is sticky
68749                elNew = oTrigger.el;
68750            }
68751        }
68752        else if((nKey == 39) || (!bSHIFT && (nKey == 9))) { // Arrow right or tab
68753            elNew = this.getNextTdEl(oTrigger.el);
68754
68755            // Validate new cell
68756            if(elNew === null) {
68757                //TODO: wrap around to first td on current page
68758
68759                //TODO: wrap forward to first td of next page
68760
68761                // Top-left selection is sticky, and release TAB focus
68762                //elNew = oTrigger.el;
68763                return;
68764            }
68765        }
68766        else if((nKey == 37) || (bSHIFT && (nKey == 9))) { // Arrow left or shift-tab
68767            elNew = this.getPreviousTdEl(oTrigger.el);
68768
68769            // Validate new cell
68770            if(elNew === null) {
68771                //TODO: wrap around to last td on current page
68772
68773                //TODO: wrap back to last td of previous page
68774
68775                // Bottom-right selection is sticky, and release TAB focus
68776                //elNew = oTrigger.el;
68777                return;
68778            }
68779        }
68780
68781        Ev.stopEvent(e);
68782        
68783        // Unselect all cells
68784        this.unselectAllCells();
68785
68786        // Select the new cell
68787        this.selectCell(elNew);
68788
68789        // Set new anchor
68790        this._oAnchorCell = {record:this.getRecord(elNew), column:this.getColumn(elNew)};
68791    }
68792},
68793
68794/**
68795 * Returns array of selected TR elements on the page.
68796 *
68797 * @method getSelectedTrEls
68798 * @return {HTMLElement[]} Array of selected TR elements.
68799 */
68800getSelectedTrEls : function() {
68801    return Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
68802},
68803
68804/**
68805 * Sets given row to the selected state.
68806 *
68807 * @method selectRow
68808 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
68809 * reference or ID string, Record instance, or RecordSet position index.
68810 */
68811selectRow : function(row) {
68812    var oRecord, elRow;
68813
68814    if(row instanceof YAHOO.widget.Record) {
68815        oRecord = this._oRecordSet.getRecord(row);
68816        elRow = this.getTrEl(oRecord);
68817    }
68818    else if(lang.isNumber(row)) {
68819        oRecord = this.getRecord(row);
68820        elRow = this.getTrEl(oRecord);
68821    }
68822    else {
68823        elRow = this.getTrEl(row);
68824        oRecord = this.getRecord(elRow);
68825    }
68826
68827    if(oRecord) {
68828        // Update selection trackers
68829        var tracker = this._aSelections || [];
68830        var sRecordId = oRecord.getId();
68831        var index = -1;
68832
68833        // Remove if already there:
68834        // Use Array.indexOf if available...
68835        /*if(tracker.indexOf && (tracker.indexOf(sRecordId) >  -1)) {
68836            tracker.splice(tracker.indexOf(sRecordId),1);
68837        }*/
68838        if(tracker.indexOf) {
68839            index = tracker.indexOf(sRecordId);
68840            
68841        }
68842        // ...or do it the old-fashioned way
68843        else {
68844            for(var j=tracker.length-1; j>-1; j--) {
68845                if(tracker[j] === sRecordId){
68846                    index = j;
68847                    break;
68848                }
68849            }
68850        }
68851        if(index > -1) {
68852            tracker.splice(index,1);
68853        }
68854        
68855        // Add to the end
68856        tracker.push(sRecordId);
68857        this._aSelections = tracker;
68858
68859        // Update trackers
68860        if(!this._oAnchorRecord) {
68861            this._oAnchorRecord = oRecord;
68862        }
68863
68864        // Update UI
68865        if(elRow) {
68866            Dom.addClass(elRow, DT.CLASS_SELECTED);
68867        }
68868
68869        this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow});
68870        YAHOO.log("Selected " + elRow, "info", this.toString());
68871    }
68872    else {
68873        YAHOO.log("Could not select row " + row, "warn", this.toString());
68874    }
68875},
68876
68877/**
68878 * Sets given row to the unselected state.
68879 *
68880 * @method unselectRow
68881 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
68882 * reference or ID string, Record instance, or RecordSet position index.
68883 */
68884unselectRow : function(row) {
68885    var elRow = this.getTrEl(row);
68886
68887    var oRecord;
68888    if(row instanceof YAHOO.widget.Record) {
68889        oRecord = this._oRecordSet.getRecord(row);
68890    }
68891    else if(lang.isNumber(row)) {
68892        oRecord = this.getRecord(row);
68893    }
68894    else {
68895        oRecord = this.getRecord(elRow);
68896    }
68897
68898    if(oRecord) {
68899        // Update selection trackers
68900        var tracker = this._aSelections || [];
68901        var sRecordId = oRecord.getId();
68902        var index = -1;
68903
68904        // Use Array.indexOf if available...
68905        if(tracker.indexOf) {
68906            index = tracker.indexOf(sRecordId);
68907        }
68908        // ...or do it the old-fashioned way
68909        else {
68910            for(var j=tracker.length-1; j>-1; j--) {
68911                if(tracker[j] === sRecordId){
68912                    index = j;
68913                    break;
68914                }
68915            }
68916        }
68917        if(index > -1) {
68918            // Update tracker
68919            tracker.splice(index,1);
68920            this._aSelections = tracker;
68921
68922            // Update the UI
68923            Dom.removeClass(elRow, DT.CLASS_SELECTED);
68924
68925            this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow});
68926            YAHOO.log("Unselected " + elRow, "info", this.toString());
68927
68928            return;
68929        }
68930    }
68931    YAHOO.log("Could not unselect row " + row, "warn", this.toString());
68932},
68933
68934/**
68935 * Clears out all row selections.
68936 *
68937 * @method unselectAllRows
68938 */
68939unselectAllRows : function() {
68940    // Remove all rows from tracker
68941    var tracker = this._aSelections || [],
68942        recId,
68943        removed = [];
68944    for(var j=tracker.length-1; j>-1; j--) {
68945       if(lang.isString(tracker[j])){
68946            recId = tracker.splice(j,1);
68947            removed[removed.length] = this.getRecord(lang.isArray(recId) ? recId[0] : recId);
68948        }
68949    }
68950
68951    // Update tracker
68952    this._aSelections = tracker;
68953
68954    // Update UI
68955    this._unselectAllTrEls();
68956
68957    this.fireEvent("unselectAllRowsEvent", {records: removed});
68958    YAHOO.log("Unselected all rows", "info", this.toString());
68959},
68960
68961/**
68962 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
68963 * from all TD elements in the internal tracker.
68964 *
68965 * @method _unselectAllTdEls
68966 * @private
68967 */
68968_unselectAllTdEls : function() {
68969    var selectedCells = Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
68970    Dom.removeClass(selectedCells, DT.CLASS_SELECTED);
68971},
68972
68973/**
68974 * Returns array of selected TD elements on the page.
68975 *
68976 * @method getSelectedTdEls
68977 * @return {HTMLElement[]} Array of selected TD elements.
68978 */
68979getSelectedTdEls : function() {
68980    return Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
68981},
68982
68983/**
68984 * Sets given cell to the selected state.
68985 *
68986 * @method selectCell
68987 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
68988 * object literal of syntax {record:oRecord, column:oColumn}.
68989 */
68990selectCell : function(cell) {
68991//TODO: accept {record} in selectCell()
68992    var elCell = this.getTdEl(cell);
68993
68994    if(elCell) {
68995        var oRecord = this.getRecord(elCell);
68996        var oColumn = this.getColumn(this.getCellIndex(elCell));
68997        var sColumnKey = oColumn.getKey();
68998
68999        if(oRecord && sColumnKey) {
69000            // Get Record ID
69001            var tracker = this._aSelections || [];
69002            var sRecordId = oRecord.getId();
69003
69004            // Remove if there
69005            for(var j=tracker.length-1; j>-1; j--) {
69006               if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
69007                    tracker.splice(j,1);
69008                    break;
69009                }
69010            }
69011
69012            // Add to the end
69013            tracker.push({recordId:sRecordId, columnKey:sColumnKey});
69014
69015            // Update trackers
69016            this._aSelections = tracker;
69017            if(!this._oAnchorCell) {
69018                this._oAnchorCell = {record:oRecord, column:oColumn};
69019            }
69020
69021            // Update the UI
69022            Dom.addClass(elCell, DT.CLASS_SELECTED);
69023
69024            this.fireEvent("cellSelectEvent", {record:oRecord, column:oColumn, key: sColumnKey, el:elCell});
69025            YAHOO.log("Selected " + elCell, "info", this.toString());
69026            return;
69027        }
69028    }
69029    YAHOO.log("Could not select cell " + cell, "warn", this.toString());
69030},
69031
69032/**
69033 * Sets given cell to the unselected state.
69034 *
69035 * @method unselectCell
69036 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
69037 * object literal of syntax {record:oRecord, column:oColumn}.
69038 * @param cell {HTMLElement | String} DOM element reference or ID string
69039 * to DataTable page element or RecordSet index.
69040 */
69041unselectCell : function(cell) {
69042    var elCell = this.getTdEl(cell);
69043
69044    if(elCell) {
69045        var oRecord = this.getRecord(elCell);
69046        var oColumn = this.getColumn(this.getCellIndex(elCell));
69047        var sColumnKey = oColumn.getKey();
69048
69049        if(oRecord && sColumnKey) {
69050            // Get Record ID
69051            var tracker = this._aSelections || [];
69052            var id = oRecord.getId();
69053
69054            // Is it selected?
69055            for(var j=tracker.length-1; j>-1; j--) {
69056                if((tracker[j].recordId === id) && (tracker[j].columnKey === sColumnKey)){
69057                    // Remove from tracker
69058                    tracker.splice(j,1);
69059
69060                    // Update tracker
69061                    this._aSelections = tracker;
69062
69063                    // Update the UI
69064                    Dom.removeClass(elCell, DT.CLASS_SELECTED);
69065
69066                    this.fireEvent("cellUnselectEvent", {record:oRecord, column: oColumn, key:sColumnKey, el:elCell});
69067                    YAHOO.log("Unselected " + elCell, "info", this.toString());
69068                    return;
69069                }
69070            }
69071        }
69072    }
69073    YAHOO.log("Could not unselect cell " + cell, "warn", this.toString());
69074},
69075
69076/**
69077 * Clears out all cell selections.
69078 *
69079 * @method unselectAllCells
69080 */
69081unselectAllCells : function() {
69082    // Remove all cells from tracker
69083    var tracker = this._aSelections || [];
69084    for(var j=tracker.length-1; j>-1; j--) {
69085       if(lang.isObject(tracker[j])){
69086            tracker.splice(j,1);
69087        }
69088    }
69089
69090    // Update tracker
69091    this._aSelections = tracker;
69092
69093    // Update UI
69094    this._unselectAllTdEls();
69095
69096    //TODO: send data to unselectAllCellsEvent handler
69097    this.fireEvent("unselectAllCellsEvent");
69098    YAHOO.log("Unselected all cells", "info", this.toString());
69099},
69100
69101/**
69102 * Returns true if given item is selected, false otherwise.
69103 *
69104 * @method isSelected
69105 * @param o {String | HTMLElement | YAHOO.widget.Record | Number
69106 * {record:YAHOO.widget.Record, column:YAHOO.widget.Column} } TR or TD element by
69107 * reference or ID string, a Record instance, a RecordSet position index,
69108 * or an object literal representation
69109 * of a cell.
69110 * @return {Boolean} True if item is selected.
69111 */
69112isSelected : function(o) {
69113    if(o && (o.ownerDocument == document)) {
69114        return (Dom.hasClass(this.getTdEl(o),DT.CLASS_SELECTED) || Dom.hasClass(this.getTrEl(o),DT.CLASS_SELECTED));
69115    }
69116    else {
69117        var oRecord, sRecordId, j;
69118        var tracker = this._aSelections;
69119        if(tracker && tracker.length > 0) {
69120            // Looking for a Record?
69121            if(o instanceof YAHOO.widget.Record) {
69122                oRecord = o;
69123            }
69124            else if(lang.isNumber(o)) {
69125                oRecord = this.getRecord(o);
69126            }
69127            if(oRecord) {
69128                sRecordId = oRecord.getId();
69129
69130                // Is it there?
69131                // Use Array.indexOf if available...
69132                if(tracker.indexOf) {
69133                    if(tracker.indexOf(sRecordId) >  -1) {
69134                        return true;
69135                    }
69136                }
69137                // ...or do it the old-fashioned way
69138                else {
69139                    for(j=tracker.length-1; j>-1; j--) {
69140                       if(tracker[j] === sRecordId){
69141                        return true;
69142                       }
69143                    }
69144                }
69145            }
69146            // Looking for a cel
69146l
69147            else if(o.record && o.column){
69148                sRecordId = o.record.getId();
69149                var sColumnKey = o.column.getKey();
69150
69151                for(j=tracker.length-1; j>-1; j--) {
69152                    if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
69153                        return true;
69154                    }
69155                }
69156            }
69157        }
69158    }
69159    return false;
69160},
69161
69162/**
69163 * Returns selected rows as an array of Record IDs.
69164 *
69165 * @method getSelectedRows
69166 * @return {String[]} Array of selected rows by Record ID.
69167 */
69168getSelectedRows : function() {
69169    var aSelectedRows = [];
69170    var tracker = this._aSelections || [];
69171    for(var j=0; j<tracker.length; j++) {
69172       if(lang.isString(tracker[j])){
69173            aSelectedRows.push(tracker[j]);
69174        }
69175    }
69176    return aSelectedRows;
69177},
69178
69179/**
69180 * Returns selected cells as an array of object literals:
69181 *     {recordId:sRecordId, columnKey:sColumnKey}.
69182 *
69183 * @method getSelectedCells
69184 * @return {Object[]} Array of selected cells by Record ID and Column ID.
69185 */
69186getSelectedCells : function() {
69187    var aSelectedCells = [];
69188    var tracker = this._aSelections || [];
69189    for(var j=0; j<tracker.length; j++) {
69190       if(tracker[j] && lang.isObject(tracker[j])){
69191            aSelectedCells.push(tracker[j]);
69192        }
69193    }
69194    return aSelectedCells;
69195},
69196
69197/**
69198 * Returns last selected Record ID.
69199 *
69200 * @method getLastSelectedRecord
69201 * @return {String} Record ID of last selected row.
69202 */
69203getLastSelectedRecord : function() {
69204    var tracker = this._aSelections;
69205    if(tracker && tracker.length > 0) {
69206        for(var i=tracker.length-1; i>-1; i--) {
69207           if(lang.isString(tracker[i])){
69208                return tracker[i];
69209            }
69210        }
69211    }
69212},
69213
69214/**
69215 * Returns last selected cell as an object literal:
69216 *     {recordId:sRecordId, columnKey:sColumnKey}.
69217 *
69218 * @method getLastSelectedCell
69219 * @return {Object} Object literal representation of a cell.
69220 */
69221getLastSelectedCell : function() {
69222    var tracker = this._aSelections;
69223    if(tracker && tracker.length > 0) {
69224        for(var i=tracker.length-1; i>-1; i--) {
69225           if(tracker[i].recordId && tracker[i].columnKey){
69226                return tracker[i];
69227            }
69228        }
69229    }
69230},
69231
69232/**
69233 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row.
69234 *
69235 * @method highlightRow
69236 * @param row {HTMLElement | String} DOM element reference or ID string.
69237 */
69238highlightRow : function(row) {
69239    var elRow = this.getTrEl(row);
69240
69241    if(elRow) {
69242        // Make sure previous row is unhighlighted
69243/*        if(this._sLastHighlightedTrElId) {
69244            Dom.removeClass(this._sLastHighlightedTrElId,DT.CLASS_HIGHLIGHTED);
69245        }*/
69246        var oRecord = this.getRecord(elRow);
69247        Dom.addClass(elRow,DT.CLASS_HIGHLIGHTED);
69248        //this._sLastHighlightedTrElId = elRow.id;
69249        this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow});
69250        YAHOO.log("Highlighted " + elRow, "info", this.toString());
69251        return;
69252    }
69253    YAHOO.log("Could not highlight row " + row, "warn", this.toString());
69254},
69255
69256/**
69257 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row.
69258 *
69259 * @method unhighlightRow
69260 * @param row {HTMLElement | String} DOM element reference or ID string.
69261 */
69262unhighlightRow : function(row) {
69263    var elRow = this.getTrEl(row);
69264
69265    if(elRow) {
69266        var oRecord = this.getRecord(elRow);
69267        Dom.removeClass(elRow,DT.CLASS_HIGHLIGHTED);
69268        this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow});
69269        YAHOO.log("Unhighlighted " + elRow, "info", this.toString());
69270        return;
69271    }
69272    YAHOO.log("Could not unhighlight row " + row, "warn", this.toString());
69273},
69274
69275/**
69276 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell.
69277 *
69278 * @method highlightCell
69279 * @param cell {HTMLElement | String} DOM element reference or ID string.
69280 */
69281highlightCell : function(cell) {
69282    var elCell = this.getTdEl(cell);
69283
69284    if(elCell) {
69285        // Make sure previous cell is unhighlighted
69286        if(this._elLastHighlightedTd) {
69287            this.unhighlightCell(this._elLastHighlightedTd);
69288        }
69289
69290        var oRecord = this.getRecord(elCell);
69291        var oColumn = this.getColumn(this.getCellIndex(elCell));
69292        var sColumnKey = oColumn.getKey();
69293        Dom.addClass(elCell,DT.CLASS_HIGHLIGHTED);
69294        this._elLastHighlightedTd = elCell;
69295        this.fireEvent("cellHighlightEvent", {record:oRecord, column:oColumn, key:sColumnKey, el:elCell});
69296        YAHOO.log("Highlighted " + elCell, "info", this.toString());
69297        return;
69298    }
69299    YAHOO.log("Could not highlight cell " + cell, "warn", this.toString());
69300},
69301
69302/**
69303 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell.
69304 *
69305 * @method unhighlightCell
69306 * @param cell {HTMLElement | String} DOM element reference or ID string.
69307 */
69308unhighlightCell : function(cell) {
69309    var elCell = this.getTdEl(cell);
69310
69311    if(elCell) {
69312        var oRecord = this.getRecord(elCell);
69313        Dom.removeClass(elCell,DT.CLASS_HIGHLIGHTED);
69314        this._elLastHighlightedTd = null;
69315        this.fireEvent("cellUnhighlightEvent", {record:oRecord, column:this.getColumn(this.getCellIndex(elCell)), key:this.getColumn(this.getCellIndex(elCell)).getKey(), el:elCell});
69316        YAHOO.log("Unhighlighted " + elCell, "info", this.toString());
69317        return;
69318    }
69319    YAHOO.log("Could not unhighlight cell " + cell, "warn", this.toString());
69320},
69321
69322
69323
69324
69325
69326
69327
69328
69329
69330
69331
69332
69333
69334
69335
69336
69337
69338
69339
69340
69341
69342
69343
69344
69345
69346
69347
69348
69349
69350
69351
69352
69353
69354
69355
69356
69357
69358
69359
69360
69361
69362
69363
69364
69365
69366// INLINE EDITING
69367
69368/**
69369 * Assigns CellEditor instance to existing Column.
69370 * @method addCellEditor
69371 * @param oColumn {YAHOO.widget.Column} Column instance.
69372 * @param oEditor {YAHOO.wdiget.CellEditor} CellEditor instance.
69373 */
69374addCellEditor : function(oColumn, oEditor) {
69375    oColumn.editor = oEditor;
69376    oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
69377    oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
69378    oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
69379    oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
69380    oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
69381    oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
69382    oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
69383    oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
69384},
69385
69386/**
69387 * Returns current CellEditor instance, or null.
69388 * @method getCellEditor
69389 * @return {YAHOO.widget.CellEditor} CellEditor instance.
69390 */
69391getCellEditor : function() {
69392    return this._oCellEditor;
69393},
69394
69395
69396/**
69397 * Activates and shows CellEditor instance for the given cell while deactivating and
69398 * canceling previous CellEditor. It is baked into DataTable that only one CellEditor
69399 * can be active at any given time. 
69400 *
69401 * @method showCellEditor
69402 * @param elCell {HTMLElement | String} Cell to edit.
69403 */
69404showCellEditor : function(elCell, oRecord, oColumn) {
69405    // Get a particular CellEditor
69406    elCell = this.getTdEl(elCell);
69407    if(elCell) {
69408        oColumn = this.getColumn(elCell);
69409        if(oColumn && oColumn.editor) {
69410            var oCellEditor = this._oCellEditor;
69411            // Clean up active CellEditor
69412            if(oCellEditor) {
69413                if(this._oCellEditor.cancel) {
69414                    this._oCellEditor.cancel();
69415                }
69416                else if(oCellEditor.isActive) {
69417                    this.cancelCellEditor();
69418                }
69419            }
69420            
69421            if(oColumn.editor instanceof YAHOO.widget.BaseCellEditor) {
69422                // Get CellEditor
69423                oCellEditor = oColumn.editor;
69424                var ok = oCellEditor.attach(this, elCell);
69425                if(ok) {
69426                    oCellEditor.render();
69427                    oCellEditor.move();
69428                    ok = this.doBeforeShowCellEditor(oCellEditor);
69429                    if(ok) {
69430                        oCellEditor.show();
69431                        this._oCellEditor = oCellEditor;
69432                    }
69433                }
69434            }
69435            // Backward compatibility
69436            else {
69437                    if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) {
69438                        oRecord = this.getRecord(elCell);
69439                    }
69440                    if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) {
69441                        oColumn = this.getColumn(elCell);
69442                    }
69443                    if(oRecord && oColumn) {
69444                        if(!this._oCellEditor || this._oCellEditor.container) {
69445                            this._initCellEditorEl();
69446                        }
69447                        
69448                        // Update Editor values
69449                        oCellEditor = this._oCellEditor;
69450                        oCellEditor.cell = elCell;
69451                        oCellEditor.record = oRecord;
69452                        oCellEditor.column = oColumn;
69453                        oCellEditor.validator = (oColumn.editorOptions &&
69454                                lang.isFunction(oColumn.editorOptions.validator)) ?
69455                                oColumn.editorOptions.validator : null;
69456                        oCellEditor.value = oRecord.getData(oColumn.key);
69457                        oCellEditor.defaultValue = null;
69458            
69459                        // Move Editor
69460                        var elContainer = oCellEditor.container;
69461                        var x = Dom.getX(elCell);
69462                        var y = Dom.getY(elCell);
69463            
69464                        // SF doesn't get xy for cells in scrolling table
69465                        // when tbody display is set to block
69466                        if(isNaN(x) || isNaN(y)) {
69467                            x = elCell.offsetLeft + // cell pos relative to table
69468                                    Dom.getX(this._elTbody.parentNode) - // plus table pos relative to document
69469                                    this._elTbody.scrollLeft; // minus tbody scroll
69470                            y = elCell.offsetTop + // cell pos relative to table
69471                                    Dom.getY(this._elTbody.parentNode) - // plus table pos relative to document
69472                                    this._elTbody.scrollTop + // minus tbody scroll
69473                                    this._elThead.offsetHeight; // account for fixed THEAD cells
69474                        }
69475            
69476                        elContainer.style.left = x + "px";
69477                        elContainer.style.top = y + "px";
69478            
69479                        // Hook to customize the UI
69480                        this.doBeforeShowCellEditor(this._oCellEditor);
69481            
69482                        //TODO: This is temporarily up here due so elements can be focused
69483                        // Show Editor
69484                        elContainer.style.display = "";
69485            
69486                        // Handle ESC key
69487                        Ev.addListener(elContainer, "keydown", function(e, oSelf) {
69488                            // ESC hides Cell Editor
69489                            if((e.keyCode == 27)) {
69490                                oSelf.cancelCellEditor();
69491                                oSelf.focusTbodyEl();
69492                            }
69493                            else {
69494                                oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e});
69495                            }
69496                        }, this);
69497            
69498                        // Render Editor markup
69499                        var fnEditor;
69500                        if(lang.isString(oColumn.editor)) {
69501                            switch(oColumn.editor) {
69502                                case "checkbox":
69503                                    fnEditor = DT.editCheckbox;
69504                                    break;
69505                                case "date":
69506                                    fnEditor = DT.editDate;
69507                                    break;
69508                                case "dropdown":
69509                                    fnEditor = DT.editDropdown;
69510                                    break;
69511                                case "radio":
69512                                    fnEditor = DT.editRadio;
69513                                    break;
69514                                case "textarea":
69515                                    fnEditor = DT.editTextarea;
69516                                    break;
69517                                case "textbox":
69518                                    fnEditor = DT.editTextbox;
69519                                    break;
69520                                default:
69521                                    fnEditor = null;
69522                            }
69523                        }
69524                        else if(lang.isFunction(oColumn.editor)) {
69525                            fnEditor = oColumn.editor;
69526                        }
69527            
69528                        if(fnEditor) {
69529                            // Create DOM input elements
69530                            fnEditor(this._oCellEditor, this);
69531            
69532                            // Show Save/Cancel buttons
69533                            if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) {
69534                                this.showCellEditorBtns(elContainer);
69535                            }
69536            
69537                            oCellEditor.isActive = true;
69538            
69539                            //TODO: verify which args to pass
69540                            this.fireEvent("editorShowEvent", {editor:oCellEditor});
69541                            YAHOO.log("Cell Editor shown for " + elCell, "info", this.toString());
69542                            return;
69543                        }
69544                    }
69545
69546
69547
69548            
69549            }
69550        }
69551    }
69552},
69553
69554/**
69555 * Backward compatibility.
69556 *
69557 * @method _initCellEditorEl
69558 * @private
69559 * @deprecated Use BaseCellEditor class.
69560 */
69561_initCellEditorEl : function() {
69562    // Attach Cell Editor container element as first child of body
69563    var elCellEditor = document.createElement("div");
69564    elCellEditor.id = this._sId + "-celleditor";
69565    elCellEditor.style.display = "none";
69566    elCellEditor.tabIndex = 0;
69567    Dom.addClass(elCellEditor, DT.CLASS_EDITOR);
69568    var elFirstChild = Dom.getFirstChild(document.body);
69569    if(elFirstChild) {
69570        elCellEditor = Dom.insertBefore(elCellEditor, elFirstChild);
69571    }
69572    else {
69573        elCellEditor = document.body.appendChild(elCellEditor);
69574    }
69575    
69576    // Internal tracker of Cell Editor values
69577    var oCellEditor = {};
69578    oCellEditor.container = elCellEditor;
69579    oCellEditor.value = null;
69580    oCellEditor.isActive = false;
69581    this._oCellEditor = oCellEditor;
69582},
69583
69584/**
69585 * Overridable abstract method to customize CellEditor before showing.
69586 *
69587 * @method doBeforeShowCellEditor
69588 * @param oCellEditor {YAHOO.widget.CellEditor} The CellEditor instance.
69589 * @return {Boolean} Return true to continue showing CellEditor.
69590 */
69591doBeforeShowCellEditor : function(oCellEditor) {
69592    return true;
69593},
69594
69595/**
69596 * Saves active CellEditor input to Record and upates DOM UI.
69597 *
69598 * @method saveCellEditor
69599 */
69600saveCellEditor : function() {
69601    if(this._oCellEditor) {
69602        if(this._oCellEditor.save) {
69603            this._oCellEditor.save();
69604        }
69605        // Backward compatibility
69606        else if(this._oCellEditor.isActive) {
69607            var newData = this._oCellEditor.value;
69608            // Copy the data to pass to the event
69609            //var oldData = YAHOO.widget.DataTable._cloneObject(this._oCellEditor.record.getData(this._oCellEditor.column.key));
69610            var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key);
69611    
69612            // Validate input data
69613            if(this._oCellEditor.validator) {
69614                newData = this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData, this._oCellEditor);
69615                if(newData === null ) {
69616                    this.resetCellEditor();
69617                    this.fireEvent("editorRevertEvent",
69618                            {editor:this._oCellEditor, oldData:oldData, newData:newData});
69619                    YAHOO.log("Could not save Cell Editor input due to invalid data " +
69620                            lang.dump(newData), "warn", this.toString());
69621                    return;
69622                }
69623            }
69624            // Update the Record
69625            this._oRecordSet.updateRecordValue(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value);
69626            // Update the UI
69627            this.formatCell(this._oCellEditor.cell.firstChild, this._oCellEditor.record, this._oCellEditor.column);
69628            
69629            // Bug fix 1764044
69630            this._oChainRender.add({
69631                method: function() {
69632                    this.validateColumnWidths();
69633                },
69634                scope: this
69635            });
69636            this._oChainRender.run();
69637            // Clear out the Cell Editor
69638            this.resetCellEditor();
69639    
69640            this.fireEvent("editorSaveEvent",
69641                    {editor:this._oCellEditor, oldData:oldData, newData:newData});
69642            YAHOO.log("Cell Editor input saved", "info", this.toString());
69643        }
69644    }   
69645},
69646
69647/**
69648 * Cancels active CellEditor.
69649 *
69650 * @method cancelCellEditor
69651 */
69652cancelCellEditor : function() {
69653    if(this._oCellEditor) {
69654        if(this._oCellEditor.cancel) {
69655            this._oCellEditor.cancel();
69656        }
69657        // Backward compatibility
69658        else if(this._oCellEditor.isActive) {
69659            this.resetCellEditor();
69660            //TODO: preserve values for the event?
69661            this.fireEvent("editorCancelEvent", {editor:this._oCellEditor});
69662            YAHOO.log("Cell Editor input canceled", "info", this.toString());
69663        }
69664
69665        YAHOO.log("CellEditor input canceled", "info", this.toString());
69666    }
69667},
69668
69669/**
69670 * Destroys active CellEditor instance and UI.
69671 *
69672 * @method destroyCellEditor
69673 */
69674destroyCellEditor : function() {
69675    if(this._oCellEditor) {
69676        this._oCellEditor.destroy();
69677        this._oCellEditor = null;
69678    }   
69679},
69680
69681/**
69682 * Passes through showEvent of the active CellEditor.
69683 *
69684 * @method _onEditorShowEvent
69685 * @param oArgs {Object}  Custom Event args.
69686 * @private 
69687 */
69688_onEditorShowEvent : function(oArgs) {
69689    this.fireEvent("editorShowEvent", oArgs);
69690},
69691
69692/**
69693 * Passes through keydownEvent of the active CellEditor.
69694 * @param oArgs {Object}  Custom Event args. 
69695 *
69696 * @method _onEditorKeydownEvent
69697 * @private 
69698 */
69699_onEditorKeydownEvent : function(oArgs) {
69700    this.fireEvent("editorKeydownEvent", oArgs);
69701},
69702
69703/**
69704 * Passes through revertEvent of the active CellEditor.
69705 *
69706 * @method _onEditorRevertEvent
69707 * @param oArgs {Object}  Custom Event args. 
69708 * @private  
69709 */
69710_onEditorRevertEvent : function(oArgs) {
69711    this.fireEvent("editorRevertEvent", oArgs);
69712},
69713
69714/**
69715 * Passes through saveEvent of the active CellEditor.
69716 *
69717 * @method _onEditorSaveEvent
69718 * @param oArgs {Object}  Custom Event args.  
69719 * @private 
69720 */
69721_onEditorSaveEvent : function(oArgs) {
69722    this.fireEvent("editorSaveEvent", oArgs);
69723},
69724
69725/**
69726 * Passes through cancelEvent of the active CellEditor.
69727 *
69728 * @method _onEditorCancelEvent
69729 * @param oArgs {Object}  Custom Event args.
69730 * @private   
69731 */
69732_onEditorCancelEvent : function(oArgs) {
69733    this.fireEvent("editorCancelEvent", oArgs);
69734},
69735
69736/**
69737 * Passes through blurEvent of the active CellEditor.
69738 *
69739 * @method _onEditorBlurEvent
69740 * @param oArgs {Object}  Custom Event args. 
69741 * @private  
69742 */
69743_onEditorBlurEvent : function(oArgs) {
69744    this.fireEvent("editorBlurEvent", oArgs);
69745},
69746
69747/**
69748 * Passes through blockEvent of the active CellEditor.
69749 *
69750 * @method _onEditorBlockEvent
69751 * @param oArgs {Object}  Custom Event args. 
69752 * @private  
69753 */
69754_onEditorBlockEvent : function(oArgs) {
69755    this.fireEvent("editorBlockEvent", oArgs);
69756},
69757
69758/**
69759 * Passes through unblockEvent of the active CellEditor.
69760 *
69761 * @method _onEditorUnblockEvent
69762 * @param oArgs {Object}  Custom Event args. 
69763 * @private  
69764 */
69765_onEditorUnblockEvent : function(oArgs) {
69766    this.fireEvent("editorUnblockEvent", oArgs);
69767},
69768
69769/**
69770 * Public handler of the editorBlurEvent. By default, saves on blur if
69771 * disableBtns is true, otherwise cancels on blur. 
69772 *
69773 * @method onEditorBlurEvent
69774 * @param oArgs {Object}  Custom Event args.  
69775 */
69776onEditorBlurEvent : function(oArgs) {
69777    if(oArgs.editor.disableBtns) {
69778        // Save on blur
69779        if(oArgs.editor.save) { // Backward incompatible
69780            oArgs.editor.save();
69781        }
69782    }      
69783    else if(oArgs.editor.cancel) { // Backward incompatible
69784        // Cancel on blur
69785        oArgs.editor.cancel();
69786    }      
69787},
69788
69789/**
69790 * Public handler of the editorBlockEvent. By default, disables DataTable UI.
69791 *
69792 * @method onEditorBlockEvent
69793 * @param oArgs {Object}  Custom Event args.  
69794 */
69795onEditorBlockEvent : function(oArgs) {
69796    this.disable();
69797},
69798
69799/**
69800 * Public handler of the editorUnblockEvent. By default, undisables DataTable UI.
69801 *
69802 * @method onEditorUnblockEvent
69803 * @param oArgs {Object}  Custom Event args.  
69804 */
69805onEditorUnblockEvent : function(oArgs) {
69806    this.undisable();
69807},
69808
69809
69810
69811
69812
69813
69814
69815
69816
69817
69818
69819
69820
69821
69822
69823
69824
69825
69826
69827
69828
69829
69830
69831
69832
69833
69834
69835
69836
69837
69838
69839
69840
69841
69842
69843
69844
69845
69846// ABSTRACT METHODS
69847
69848/**
69849 * Overridable method gives implementers a hook to access data before
69850 * it gets added to RecordSet and rendered to the TBODY.
69851 *
69852 * @method doBeforeLoadData
69853 * @param sRequest {String} Original request.
69854 * @param oResponse {Object}
69854 <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
69855 * @param oPayload {MIXED} additional arguments
69856 * @return {Boolean} Return true to continue loading data into RecordSet and
69857 * updating DataTable with new Records, false to cancel.
69858 */
69859doBeforeLoadData : function(sRequest, oResponse, oPayload) {
69860    return true;
69861},
69862
69863
69864
69865
69866
69867
69868
69869
69870
69871
69872
69873
69874
69875
69876
69877
69878
69879
69880
69881
69882
69883
69884
69885
69886
69887
69888
69889
69890
69891
69892
69893
69894
69895
69896
69897
69898
69899
69900
69901
69902
69903
69904
69905
69906
69907
69908
69909
69910
69911
69912
69913
69914
69915
69916
69917
69918
69919
69920
69921
69922
69923
69924
69925/////////////////////////////////////////////////////////////////////////////
69926//
69927// Public Custom Event Handlers
69928//
69929/////////////////////////////////////////////////////////////////////////////
69930
69931/**
69932 * Custom event handler to sort Column.
69933 *
69934 * @method onEventSortColumn
69935 * @param oArgs.event {HTMLEvent} Event object.
69936 * @param oArgs.target {HTMLElement} Target element.
69937 */
69938onEventSortColumn : function(oArgs) {
69939//TODO: support form elements in sortable columns
69940    var evt = oArgs.event;
69941    var target = oArgs.target;
69942
69943    var el = this.getThEl(target) || this.getTdEl(target);
69944    if(el) {
69945        var oColumn = this.getColumn(el);
69946        if(oColumn.sortable) {
69947            Ev.stopEvent(evt);
69948            this.sortColumn(oColumn);
69949        }
69950    }
69951    else {
69952        YAHOO.log("Could not find Column for " + target, "warn", this.toString());
69953    }
69954},
69955
69956/**
69957 * Custom event handler to select Column.
69958 *
69959 * @method onEventSelectColumn
69960 * @param oArgs.event {HTMLEvent} Event object.
69961 * @param oArgs.target {HTMLElement} Target element.
69962 */
69963onEventSelectColumn : function(oArgs) {
69964    this.selectColumn(oArgs.target);
69965},
69966
69967/**
69968 * Custom event handler to highlight Column. Accounts for spurious
69969 * caused-by-child events. 
69970 *
69971 * @method onEventHighlightColumn
69972 * @param oArgs.event {HTMLEvent} Event object.
69973 * @param oArgs.target {HTMLElement} Target element.
69974 */
69975onEventHighlightColumn : function(oArgs) {
69976    this.highlightColumn(oArgs.target);
69977},
69978
69979/**
69980 * Custom event handler to unhighlight Column. Accounts for spurious
69981 * caused-by-child events. 
69982 *
69983 * @method onEventUnhighlightColumn
69984 * @param oArgs.event {HTMLEvent} Event object.
69985 * @param oArgs.target {HTMLElement} Target element.
69986 */
69987onEventUnhighlightColumn : function(oArgs) {
69988    this.unhighlightColumn(oArgs.target);
69989},
69990
69991/**
69992 * Custom event handler to manage selection according to desktop paradigm.
69993 *
69994 * @method onEventSelectRow
69995 * @param oArgs.event {HTMLEvent} Event object.
69996 * @param oArgs.target {HTMLElement} Target element.
69997 */
69998onEventSelectRow : function(oArgs) {
69999    var sMode = this.get("selectionMode");
70000    if(sMode == "single") {
70001        this._handleSingleSelectionByMouse(oArgs);
70002    }
70003    else {
70004        this._handleStandardSelectionByMouse(oArgs);
70005    }
70006},
70007
70008/**
70009 * Custom event handler to select cell.
70010 *
70011 * @method onEventSelectCell
70012 * @param oArgs.event {HTMLEvent} Event object.
70013 * @param oArgs.target {HTMLElement} Target element.
70014 */
70015onEventSelectCell : function(oArgs) {
70016    var sMode = this.get("selectionMode");
70017    if(sMode == "cellblock") {
70018        this._handleCellBlockSelectionByMouse(oArgs);
70019    }
70020    else if(sMode == "cellrange") {
70021        this._handleCellRangeSelectionByMouse(oArgs);
70022    }
70023    else {
70024        this._handleSingleCellSelectionByMouse(oArgs);
70025    }
70026},
70027
70028/**
70029 * Custom event handler to highlight row. Accounts for spurious
70030 * caused-by-child events. 
70031 *
70032 * @method onEventHighlightRow
70033 * @param oArgs.event {HTMLEvent} Event object.
70034 * @param oArgs.target {HTMLElement} Target element.
70035 */
70036onEventHighlightRow : function(oArgs) {
70037    this.highlightRow(oArgs.target);
70038},
70039
70040/**
70041 * Custom event handler to unhighlight row. Accounts for spurious
70042 * caused-by-child events. 
70043 *
70044 * @method onEventUnhighlightRow
70045 * @param oArgs.event {HTMLEvent} Event object.
70046 * @param oArgs.target {HTMLElement} Target element.
70047 */
70048onEventUnhighlightRow : function(oArgs) {
70049    this.unhighlightRow(oArgs.target);
70050},
70051
70052/**
70053 * Custom event handler to highlight cell. Accounts for spurious
70054 * caused-by-child events. 
70055 *
70056 * @method onEventHighlightCell
70057 * @param oArgs.event {HTMLEvent} Event object.
70058 * @param oArgs.target {HTMLElement} Target element.
70059 */
70060onEventHighlightCell : function(oArgs) {
70061    this.highlightCell(oArgs.target);
70062},
70063
70064/**
70065 * Custom event handler to unhighlight cell. Accounts for spurious
70066 * caused-by-child events. 
70067 *
70068 * @method onEventUnhighlightCell
70069 * @param oArgs.event {HTMLEvent} Event object.
70070 * @param oArgs.target {HTMLElement} Target element.
70071 */
70072onEventUnhighlightCell : function(oArgs) {
70073    this.unhighlightCell(oArgs.target);
70074},
70075
70076/**
70077 * Custom event handler to format cell.
70078 *
70079 * @method onEventFormatCell
70080 * @param oArgs.event {HTMLEvent} Event object.
70081 * @param oArgs.target {HTMLElement} Target element.
70082 */
70083onEventFormatCell : function(oArgs) {
70084    var target = oArgs.target;
70085
70086    var elCell = this.getTdEl(target);
70087    if(elCell) {
70088        var oColumn = this.getColumn(this.getCellIndex(elCell));
70089        this.formatCell(elCell.firstChild, this.getRecord(elCell), oColumn);
70090    }
70091    else {
70092        YAHOO.log("Could not format cell " + target, "warn", this.toString());
70093    }
70094},
70095
70096/**
70097 * Custom event handler to edit cell.
70098 *
70099 * @method onEventShowCellEditor
70100 * @param oArgs.event {HTMLEvent} Event object.
70101 * @param oArgs.target {HTMLElement} Target element.
70102 */
70103onEventShowCellEditor : function(oArgs) {
70104    if(!this.isDisabled()) {
70105        this.showCellEditor(oArgs.target);
70106    }
70107},
70108
70109/**
70110 * Custom event handler to save active CellEditor input.
70111 *
70112 * @method onEventSaveCellEditor
70113 */
70114onEventSaveCellEditor : function(oArgs) {
70115    if(this._oCellEditor) {
70116        if(this._oCellEditor.save) {
70117            this._oCellEditor.save();
70118        }
70119        // Backward compatibility
70120        else {
70121            this.saveCellEditor();
70122        }
70123    }
70124},
70125
70126/**
70127 * Custom event handler to cancel active CellEditor.
70128 *
70129 * @method onEventCancelCellEditor
70130 */
70131onEventCancelCellEditor : function(oArgs) {
70132    if(this._oCellEditor) {
70133        if(this._oCellEditor.cancel) {
70134            this._oCellEditor.cancel();
70135        }
70136        // Backward compatibility
70137        else {
70138            this.cancelCellEditor();
70139        }
70140    }
70141},
70142
70143/**
70144 * Callback function receives data from DataSource and populates an entire
70145 * DataTable with Records and TR elements, clearing previous Records, if any.
70146 *
70147 * @method onDataReturnInitializeTable
70148 * @param sRequest {String} Original request.
70149 * @param oResponse {Object}
70149 <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
70150 * @param oPayload {MIXED} (optional) Additional argument(s)
70151 */
70152onDataReturnInitializeTable : function(sRequest, oResponse, oPayload) {
70153    if((this instanceof DT) && this._sId) {
70154        this.initializeTable();
70155    
70156        this.onDataReturnSetRows(sRequest,oResponse,oPayload);
70157    }
70158},
70159
70160/**
70161 * Callback function receives reponse from DataSource, replaces all existing
70162 * Records in  RecordSet, updates TR elements with new data, and updates state
70163 * UI for pagination and sorting from payload data, if necessary. 
70164 *  
70165 * @method onDataReturnReplaceRows
70166 * @param oRequest {MIXED} Original generated request.
70167 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
70168 * @param oPayload {MIXED} (optional) Additional argument(s)
70169 */
70170onDataReturnReplaceRows : function(oRequest, oResponse, oPayload) {
70171    if((this instanceof DT) && this._sId) {
70172        this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
70173    
70174        // Pass data through abstract method for any transformations
70175        var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
70176            pag   = this.get('paginator'),
70177            index = 0;
70178    
70179        // Data ok to set
70180        if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
70181            // Update Records
70182            this._oRecordSet.reset();
70183    
70184            if (this.get('dynamicData')) {
70185                if (oPayload && oPayload.pagination &&
70186                    lang.isNumber(oPayload.pagination.recordOffset)) {
70187                    index = oPayload.pagination.recordOffset;
70188                } else if (pag) {
70189                    index = pag.getStartIndex();
70190                }
70191            }
70192    
70193            this._oRecordSet.setRecords(oResponse.results, index | 0);
70194            
70195            // Update state
70196            this._handleDataReturnPayload(oRequest, oResponse, oPayload);
70197            
70198            // Update UI
70199            this.render();    
70200        }
70201        // Error
70202        else if(ok && oResponse.error) {
70203            this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
70204        }
70205    }
70206},
70207
70208/**
70209 * Callback function receives data from DataSource and appends to an existing
70210 * DataTable new Records and, if applicable, creates or updates
70211 * corresponding TR elements.
70212 *
70213 * @method onDataReturnAppendRows
70214 * @param sRequest {String} Original request.
70215 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
70216 * @param oPayload {MIXED} (optional) Additional argument(s)
70217 */
70218onDataReturnAppendRows : function(sRequest, oResponse, oPayload) {
70219    if((this instanceof DT) && this._sId) {
70220        this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
70221    
70222        // Pass data through abstract method for any transformations
70223        var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
70224    
70225        // Data ok to append
70226        if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {        
70227            // Append rows
70228            this.addRows(oResponse.results);
70229    
70230            // Update state
70231            this._handleDataReturnPayload(sRequest, oResponse, oPayload);
70232        }
70233        // Error
70234        else if(ok && oResponse.error) {
70235            this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
70236        }
70237    }
70238},
70239
70240/**
70241 * Callback function receives data from DataSource and inserts new records
70242 * starting at the index specified in oPayload.insertIndex. The value for
70243 * oPayload.insertIndex can be populated when sending the request to the DataSource,
70244 * or by accessing oPayload.insertIndex with the doBeforeLoadData() method at runtime.
70245 * If applicable, creates or updates corresponding TR elements.
70246 *
70247 * @method onDataReturnInsertRows
70248 * @param sRequest {String} Original request.
70249 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
70250 * @param oPayload {MIXED} Argument payload, looks in oPayload.insertIndex.
70251 */
70252onDataReturnInsertRows : function(sRequest, oResponse, oPayload) {
70253    if((this instanceof DT) && this._sId) {
70254        this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
70255    
70256        // Pass data through abstract method for any transformations
70257        var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
70258    
70259        // Data ok to append
70260        if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
70261            // Insert rows
70262            this.addRows(oResponse.results, (oPayload ? oPayload.insertIndex : 0));
70263    
70264            // Update state
70265            this._handleDataReturnPayload(sRequest, oResponse, oPayload);
70266        }
70267        // Error
70268        else if(ok && oResponse.error) {
70269            this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
70270        }
70271    }
70272},
70273
70274/**
70275 * Callback function receives data from DataSource and incrementally updates Records
70276 * starting at the index specified in oPayload.updateIndex. The value for
70277 * oPayload.updateIndex can be populated when sending the request to the DataSource,
70278 * or by accessing oPayload.updateIndex with the doBeforeLoadData() method at runtime.
70279 * If applicable, creates or updates corresponding TR elements.
70280 *
70281 * @method onDataReturnUpdateRows
70282 * @param sRequest {String} Original request.
70283 * @param oResponse {Object}
70283 <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
70284 * @param oPayload {MIXED} Argument payload, looks in oPayload.updateIndex.
70285 */
70286onDataReturnUpdateRows : function(sRequest, oResponse, oPayload) {
70287    if((this instanceof DT) && this._sId) {
70288        this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
70289    
70290        // Pass data through abstract method for any transformations
70291        var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
70292    
70293        // Data ok to append
70294        if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
70295            // Insert rows
70296            this.updateRows((oPayload ? oPayload.updateIndex : 0), oResponse.results);
70297    
70298            // Update state
70299            this._handleDataReturnPayload(sRequest, oResponse, oPayload);
70300        }
70301        // Error
70302        else if(ok && oResponse.error) {
70303            this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
70304        }
70305    }
70306},
70307
70308/**
70309 * Callback function receives reponse from DataSource and populates the
70310 * RecordSet with the results.
70311 *  
70312 * @method onDataReturnSetRows
70313 * @param oRequest {MIXED} Original generated request.
70314 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
70315 * @param oPayload {MIXED} (optional) Additional argument(s)
70316 */
70317onDataReturnSetRows : function(oRequest, oResponse, oPayload) {
70318    if((this instanceof DT) && this._sId) {
70319        this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
70320    
70321        // Pass data through abstract method for any transformations
70322        var ok    = this.doBeforeLoadData(oRequest, oResponse, oPayload),
70323            pag   = this.get('paginator'),
70324            index = 0;
70325    
70326        // Data ok to set
70327        if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
70328            // Update Records
70329            if (this.get('dynamicData')) {
70330                if (oPayload && oPayload.pagination &&
70331                    lang.isNumber(oPayload.pagination.recordOffset)) {
70332                    index = oPayload.pagination.recordOffset;
70333                } else if (pag) {
70334                    index = pag.getStartIndex();
70335                }
70336                
70337                this._oRecordSet.reset(); // Bug 2290604: dyanmic data shouldn't keep accumulating by default
70338            }
70339    
70340            this._oRecordSet.setRecords(oResponse.results, index | 0);
70341    
70342            // Update state
70343            this._handleDataReturnPayload(oRequest, oResponse, oPayload);
70344            
70345            // Update UI
70346            this.render();
70347        }
70348        // Error
70349        else if(ok && oResponse.error) {
70350            this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
70351        }
70352    }
70353    else {
70354        YAHOO.log("Instance destroyed before data returned.","info",this.toString());
70355    }
70356},
70357
70358/**
70359 * Hook to update oPayload before consumption.
70360 *  
70361 * @method handleDataReturnPayload
70362 * @param oRequest {MIXED} Original generated request.
70363 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
70364 * @param oPayload {MIXED} State values.
70365 * @return oPayload {MIXED} State values.
70366 */
70367handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
70368    return oPayload || {};
70369},
70370
70371/**
70372 * Updates the DataTable with state data sent in an onDataReturn* payload.
70373 *  
70374 * @method _handleDataReturnPayload
70375 * @param oRequest {MIXED} Original generated request.
70376 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
70377 * @param oPayload {MIXED} State values
70378 * @private
70379 */
70380_handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
70381    oPayload = this.handleDataReturnPayload(oRequest, oResponse, oPayload);
70382    if(oPayload) {
70383        // Update pagination
70384        var oPaginator = this.get('paginator');
70385        if (oPaginator) {
70386            // Update totalRecords
70387            if(this.get("dynamicData")) {
70388                if (widget.Paginator.isNumeric(oPayload.totalRecords)) {
70389                    oPaginator.set('totalRecords',oPayload.totalRecords);
70390                }
70391            }
70392            else {
70393                oPaginator.set('totalRecords',this._oRecordSet.getLength());
70394            }
70395            // Update other paginator values
70396            if (lang.isObject(oPayload.pagination)) {
70397                oPaginator.set('rowsPerPage',oPayload.pagination.rowsPerPage);
70398                oPaginator.set('recordOffset',oPayload.pagination.recordOffset);
70399            }
70400        }
70401
70402        // Update sorting
70403        if (oPayload.sortedBy) {
70404            // Set the sorting values in preparation for refresh
70405            this.set('sortedBy', oPayload.sortedBy);
70406        }
70407        // Backwards compatibility for sorting
70408        else if (oPayload.sorting) {
70409            // Set the sorting values in preparation for refresh
70410            this.set('sortedBy', oPayload.sorting);
70411        }
70412    }
70413},
70414
70415
70416
70417
70418
70419
70420
70421
70422
70423
70424
70425
70426
70427
70428
70429
70430
70431
70432
70433
70434
70435
70436
70437
70438
70439
70440
70441
70442
70443
70444
70445
70446
70447    /////////////////////////////////////////////////////////////////////////////
70448    //
70449    // Custom Events
70450    //
70451    /////////////////////////////////////////////////////////////////////////////
70452
70453    /**
70454     * Fired when the DataTable's rows are rendered from an initialized state.
70455     *
70456     * @event initEvent
70457     */
70458
70459    /**
70460     * Fired before the DataTable's DOM is rendered or modified.
70461     *
70462     * @event beforeRenderEvent
70463     */
70464
70465    /**
70466     * Fired when the DataTable's DOM is rendered or modified.
70467     *
70468     * @event renderEvent
70469     */
70470
70471    /**
70472     * Fired when the DataTable's post-render routine is complete, including
70473     * Column width validations.
70474     *
70475     * @event postRenderEvent
70476     */
70477
70478    /**
70479     * Fired when the DataTable is disabled.
70480     *
70481     * @event disableEvent
70482     */
70483
70484    /**
70485     * Fired when the DataTable is undisabled.
70486     *
70487     * @event undisableEvent
70488     */
70489
70490    /**
70491     * Fired when data is returned from DataSource but before it is consumed by
70492     * DataTable.
70493     *
70494     * @event dataReturnEvent
70495     * @param oArgs.request {String} Original request.
70496     * @param oArgs.response {Object} Response object.
70497     */
70498
70499    /**
70500     * Fired when the DataTable has a focus event.
70501     *
70502     * @event tableFocusEvent
70503     */
70504
70505    /**
70506     * Fired when the DataTable THEAD element has a focus event.
70507     *
70508     * @event theadFocusEvent
70509     */
70510
70511    /**
70512     * Fired when the DataTable TBODY element has a focus event.
70513     *
70514     * @event tbodyFocusEvent
70515     */
70516
70517    /**
70518     * Fired when the DataTable has a blur event.
70519     *
70520     * @event tableBlurEvent
70521     */
70522
70523    /*TODO implement theadBlurEvent
70524     * Fired when the DataTable THEAD element has a blur event.
70525     *
70526     * @event theadBlurEvent
70527     */
70528
70529    /*TODO: implement tbodyBlurEvent
70530     * Fired when the DataTable TBODY element has a blur event.
70531     *
70532     * @event tbodyBlurEvent
70533     */
70534
70535    /**
70536     * Fired when the DataTable has a key event.
70537     *
70538     * @event tableKeyEvent
70539     * @param oArgs.event {HTMLEvent} The event object.
70540     * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
70541     */
70542
70543    /**
70544     * Fired when the DataTable THEAD element has a key event.
70545     *
70546     * @event theadKeyEvent
70547     * @param oArgs.event {HTMLEvent} The event object.
70548     * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
70549     */
70550
70551    /**
70552     * Fired when the DataTable TBODY element has a key event.
70553     *
70554     * @event tbodyKeyEvent
70555     * @param oArgs.event {HTMLEvent} The event object.
70556     * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
70557     */
70558
70559    /**
70560     * Fired when the DataTable has a mouseover.
70561     *
70562     * @event tableMouseoverEvent
70563     * @param oArgs.event {HTMLEvent} The event object.
70564     * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
70565     *
70566     */
70567
70568    /**
70569     * Fired when the DataTable has a mouseout.
70570     *
70571     * @event tableMouseoutEvent
70572     * @param oArgs.event {HTMLEvent} The event object.
70573     * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
70574     *
70575     */
70576
70577    /**
70578     * Fired when the DataTable has a mousedown.
70579     *
70580     * @event tableMousedownEvent
70581     * @param oArgs.event {HTMLEvent} The event object.
70582     * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
70583     *
70584     */
70585
70586    /**
70587     * Fired when the DataTable has a mouseup.
70588     *
70589     * @event tableMouseupEvent
70590     * @param oArgs.event {HTMLEvent} The event object.
70591     * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
70592     *
70593     */
70594
70595    /**
70596     * Fired when the DataTable has a click.
70597     *
70598     * @event tableClickEvent
70599     * @param oArgs.event {HTMLEvent} The event object.
70600     * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
70601     *
70602     */
70603
70604    /**
70605     * Fired when the DataTable has a dblclick.
70606     *
70607     * @event tableDblclickEvent
70608     * @param oArgs.event {HTMLEvent} The event object.
70609     * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
70610     *
70611     */
70612
70613    /**
70614     * Fired when a message is shown in the DataTable's message element.
70615     *
70616     * @event tableMsgShowEvent
70617     * @param oArgs.html {HTML} The HTML displayed.
70618     * @param oArgs.className {String} The className assigned.
70619     *
70620     */
70621
70622    /**
70623     * Fired when the DataTable's message element is hidden.
70624     *
70625     * @event tableMsgHideEvent
70626     */
70627
70628    /**
70629     * Fired when a THEAD row has a mouseover.
70630     *
70631     * @event theadRowMouseoverEvent
70632     * @param oArgs.event {HTMLEvent} The event object.
70633     * @param oArgs.target {HTMLElement} The TR element.
70634     */
70635
70636    /**
70637     * Fired when a THEAD row has a mouseout.
70638     *
70639     * @event theadRowMouseoutEvent
70640     * @param oArgs.event {HTMLEvent} The event object.
70641     * @param oArgs.target {HTMLElement} The TR element.
70642     */
70643
70644    /**
70645     * Fired when a THEAD row has a mousedown.
70646     *
70647     * @event theadRowMousedownEvent
70648     * @param oArgs.event {HTMLEvent} The event object.
70649     * @param oArgs.target {HTMLElement} The TR element.
70650     */
70651
70652    /**
70653     * Fired when a THEAD row has a mouseup.
70654     *
70655     * @event theadRowMouseupEvent
70656     * @param oArgs.event {HTMLEvent} The event object.
70657     * @param oArgs.target {HTMLElement} The TR element.
70658     */
70659
70660    /**
70661     * Fired when a THEAD row has a click.
70662     *
70663     * @event theadRowClickEvent
70664     * @param oArgs.event {HTMLEvent} The event object.
70665     * @param oArgs.target {HTMLElement} The TR element.
70666     */
70667
70668    /**
70669     * Fired when a THEAD row has a dblclick.
70670     *
70671     * @event theadRowDblclickEvent
70672     * @param oArgs.event {HTMLEvent} The event object.
70673     * @param oArgs.target {HTMLElement} The TR element.
70674     */
70675
70676    /**
70677     * Fired when a THEAD cell has a mouseover.
70678     *
70679     * @event theadCellMouseoverEvent
70680     * @param oArgs.event {HTMLEvent} The event object.
70681     * @param oArgs.target {HTMLElement} The TH element.
70682     *
70683     */
70684
70685    /**
70686     * Fired when a THEAD cell has a mouseout.
70687     *
70688     * @event theadCellMouseoutEvent
70689     * @param oArgs.event {HTMLEvent} The event object.
70690     * @param oArgs.target {HTMLElement} The TH element.
70691     *
70692     */
70693
70694    /**
70695     * Fired when a THEAD cell has a mousedown.
70696     *
70697     * @event theadCellMousedownEvent
70698     * @param oArgs.event {HTMLEvent} The event object.
70699     * @param oArgs.target {HTMLElement} The TH element.
70700     */
70701
70702    /**
70703     * Fired when a THEAD cell has a mouseup.
70704     *
70705     * @event theadCellMouseupEvent
70706     * @param oArgs.event {HTMLEvent} The event object.
70707     * @param oArgs.target {HTMLElement} The TH element.
70708     */
70709
70710    /**
70711     * Fired when a THEAD cell has a click.
70712     *
70713     * @event theadCellClickEvent
70714     * @param oArgs.event {HTMLEvent} The event object.
70715     * @param oArgs.target {HTMLElement} The TH element.
70716     */
70717
70718    /**
70719     * Fired when a THEAD cell has a dblclick.
70720     *
70721     * @event theadCellDblclickEvent
70722     * @param oArgs.event {HTMLEvent} The event object.
70723     * @param oArgs.target {HTMLElement} The TH element.
70724     */
70725
70726    /**
70727     * Fired when a THEAD label has a mouseover.
70728     *
70729     * @event theadLabelMouseoverEvent
70730     * @param oArgs.event {HTMLEvent} The event object.
70731     * @param oArgs.target {HTMLElement} The SPAN element.
70732     *
70733     */
70734
70735    /**
70736     * Fired when a THEAD label has a mouseout.
70737     *
70738     * @event theadLabelMouseoutEvent
70739     * @param oArgs.event {HTMLEvent} The event object.
70740     * @param oArgs.target {HTMLElement} The SPAN element.
70741     *
70742     */
70743
70744    /**
70745     * Fired when a THEAD label has a mousedown.
70746     *
70747     * @event theadLabelMousedownEvent
70748     * @param oArgs.event {HTMLEvent} The event object.
70749     * @param oArgs.target {HTMLElement} The SPAN element.
70750     */
70751
70752    /**
70753     * Fired when a THEAD label has a mouseup.
70754     *
70755     * @event theadLabelMouseupEvent
70756     * @param oArgs.event {HTMLEvent} The event object.
70757     * @param oArgs.target {HTMLElement} The SPAN element.
70758     */
70759
70760    /**
70761     * Fired when a THEAD label has a click.
70762     *
70763     * @event theadLabelClickEvent
70764     * @param oArgs.event {HTMLEvent} The event object.
70765     * @param oArgs.target {HTMLElement} The SPAN element.
70766     */
70767
70768    /**
70769     * Fired when a THEAD label has a dblclick.
70770     *
70771     * @event theadLabelDblclickEvent
70772     * @param oArgs.event {HTMLEvent} The event object.
70773     * @param oArgs.target {HTMLElement} The SPAN element.
70774     */
70775
70776    /**
70777     * Fired when a column is sorted.
70778     *
70779     * @event columnSortEvent
70780     * @param oArgs.column {YAHOO.widget.Column} The Column instance.
70781     * @param oArgs.dir {String} Sort direction: YAHOO.widget.DataTable.CLASS_ASC
70782     * or YAHOO.widget.DataTable.CLASS_DESC.
70783     */
70784
70785    /**
70786     * Fired when a column width is set.
70787     *
70788     * @event columnSetWidthEvent
70789     * @param oArgs.column {YAHOO.widget.Column} The Column instance.
70790     * @param oArgs.width {Number} The width in pixels.
70791     */
70792
70793    /**
70794     * Fired when a column width is unset.
70795     *
70796     * @event columnUnsetWidthEvent
70797     * @param oArgs.column {YAHOO.widget.Column} The Column instance.
70798     */
70799
70800    /**
70801     * Fired when a column is drag-resized.
70802     *
70803     * @event columnResizeEvent
70804     * @param oArgs.column {YAHOO.widget.Column} The Column instance.
70805     * @param oArgs.target {HTMLElement} The TH element.
70806     * @param oArgs.width {Number} Width in pixels.     
70807     */
70808
70809    /**
70810     * Fired when a Column is moved to a new index.
70811     *
70812     * @event columnReorderEvent
70813     * @param oArgs.column {YAHOO.widget.Column} The Column instance.
70814     * @param oArgs.oldIndex {Number} The previous tree index position.
70815     */
70816
70817    /**
70818     * Fired when a column is hidden.
70819     *
70820     * @event columnHideEvent
70821     * @param oArgs.column {YAHOO.widget.Column} The Column instance.
70822     */
70823
70824    /**
70825     * Fired when a column is shown.
70826     *
70827     * @event columnShowEvent
70828     * @param oArgs.column {YAHOO.widget.Column} The Column instance.
70829     */
70830
70831    /**
70832     * Fired when a column is selected.
70833     *
70834     * @event columnSelectEvent
70835     * @param oArgs.column {YAHOO.widget.Column} The Column instance.
70836     */
70837
70838    /**
70839     * Fired when a column is unselected.
70840     *
70841     * @event columnUnselectEvent
70842     * @param oArgs.column {YAHOO.widget.Column} The Column instance.
70843     */
70844    /**
70845     * Fired when a column is removed.
70846     *
70847     * @event columnRemoveEvent
70848     * @param oArgs.column {YAHOO.widget.Column} The Column instance.
70849     */
70850
70851    /**
70852     * Fired when a column is inserted.
70853     *
70854     * @event columnInsertEvent
70855     * @param oArgs.column {YAHOO.widget.Column} The Column instance.
70856     * @param oArgs.index {Number} The index position.
70857     */
70858
70859    /**
70860     * Fired when a column is highlighted.
70861     *
70862     * @event columnHighlightEvent
70863     * @param oArgs.column {YAHOO.widget.Column} The highlighted Column.
70864     */
70865
70866    /**
70867     * Fired when a column is unhighlighted.
70868     *
70869     * @event columnUnhighlightEvent
70870     * @param oArgs.column {YAHOO.widget.Column} The unhighlighted Column.
70871     */
70872
70873
70874    /**
70875     * Fired when a row has a mouseover.
70876     *
70877     * @event rowMouseoverEvent
70878     * @param oArgs.event {HTMLEvent} The event object.
70879     * @param oArgs.target {HTMLElement} The TR element.
70880     */
70881
70882    /**
70883     * Fired when a row has a mouseout.
70884     *
70885     * @event rowMouseoutEvent
70886     * @param oArgs.event {HTMLEvent} The event object.
70887     * @param oArgs.target {HTMLElement} The TR element.
70888     */
70889
70890    /**
70891     * Fired when a row has a mousedown.
70892     *
70893     * @event rowMousedownEvent
70894     * @param oArgs.event {HTMLEvent} The event object.
70895     * @param oArgs.target {HTMLElement} The TR element.
70896     */
70897
70898    /**
70899     * Fired when a row has a mouseup.
70900     *
70901     * @event rowMouseupEvent
70902     * @param oArgs.event {HTMLEvent} The event object.
70903     * @param oArgs.target {HTMLElement} The TR element.
70904     */
70905
70906    /**
70907     * Fired when a row has a click.
70908     *
70909     * @event rowClickEvent
70910     * @param oArgs.event {HTMLEvent} The event object.
70911     * @param oArgs.target {HTMLElement} The TR element.
70912     */
70913
70914    /**
70915     * Fired when a row has a dblclick.
70916     *
70917     * @event rowDblclickEvent
70918     * @param oArgs.event {HTMLEvent} The event object.
70919     * @param oArgs.target {HTMLElement} The TR element.
70920     */
70921
70922    /**
70923     * Fired when a row is added.
70924     *
70925     * @event rowAddEvent
70926     * @param oArgs.record {YAHOO.widget.Record} The added Record.
70927     */
70928     
70929    /**
70930     * Fired when rows are added.
70931     *
70932     * @event rowsAddEvent
70933     * @param oArgs.record {YAHOO.widget.Record[]} The added Records.
70934     */
70935
70936    /**
70937     * Fired when a row is updated.
70938     *
70939     * @event rowUpdateEvent
70940     * @param oArgs.record {YAHOO.widget.Record} The updated Record.
70941     * @param oArgs.oldData {Object} Object literal of the old data.
70942     */
70943
70944    /**
70945     * Fired when a row is deleted.
70946     *
70947     * @event rowDeleteEvent
70948     * @param oArgs.oldData {Object} Object literal of the deleted data.
70949     * @param oArgs.recordIndex {Number} Index of the deleted Record.
70950     * @param oArgs.trElIndex {Number} Index of the deleted TR element, if on current page.
70951     */
70952     
70953    /**
70954     * Fired when rows are deleted.
70955     *
70956     * @event rowsDeleteEvent
70957     * @param oArgs.oldData {Object[]} Array of object literals of the deleted data.
70958     * @param oArgs.recordIndex {Number} Index of the first deleted Record.
70959     * @param oArgs.count {Number} Number of deleted Records.
70960     */
70961
70962    /**
70963     * Fired when a row is selected.
70964     *
70965     * @event rowSelectEvent
70966     * @param oArgs.el {HTMLElement} The selected TR element, if applicable.
70967     * @param oArgs.record {YAHOO.widget.Record} The selected Record.
70968     */
70969
70970    /**
70971     * Fired when a row is unselected.
70972     *
70973     * @event rowUnselectEvent
70974     * @param oArgs.el {HTMLElement} The unselected TR element, if applicable.
70975     * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
70976     */
70977
70978    /**
70979     * Fired when all row selections are cleared.
70980     *
70981     * @event unselectAllRowsEvent
70982     */
70983
70984    /**
70985     * Fired when a row is highlighted.
70986     *
70987     * @event rowHighlightEvent
70988     * @param oArgs.el {HTMLElement} The highlighted TR element.
70989     * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
70990     */
70991
70992    /**
70993     * Fired when a row is unhighlighted.
70994     *
70995     * @event rowUnhighlightEvent
70996     * @param oArgs.el {HTMLElement} The highlighted TR element.
70997     * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
70998     */
70999
71000    /**
71001     * Fired when a cell is updated.
71002     *
71003     * @event cellUpdateEvent
71004     * @param oArgs.record {YAHOO.widget.Record} The updated Record.
71005     * @param oArgs.column {YAHOO.widget.Column} The updated Column.
71006     * @param oArgs.oldData {Object} Original data value of the updated cell.
71007     */
71008
71009    /**
71010     * Fired when a cell has a mouseover.
71011     *
71012     * @event cellMouseoverEvent
71013     * @param oArgs.event {HTMLEvent} The event object.
71014     * @param oArgs.target {HTMLElement} The TD element.
71015     */
71016
71017    /**
71018     * Fired when a cell has a mouseout.
71019     *
71020     * @event cellMouseoutEvent
71021     * @param oArgs.event {HTMLEvent} The event object.
71022     * @param oArgs.target {HTMLElement} The TD element.
71023     */
71024
71025    /**
71026     * Fired when a cell has a mousedown.
71027     *
71028     * @event cellMousedownEvent
71029     * @param oArgs.event {HTMLEvent} The event object.
71030     * @param oArgs.target {HTMLElement} The TD element.
71031     */
71032
71033    /**
71034     * Fired when a cell has a mouseup.
71035     *
71036     * @event cellMouseupEvent
71037     * @param oArgs.event {HTMLEvent} The event object.
71038     * @param oArgs.target {HTMLElement} The TD element.
71039     */
71040
71041    /**
71042     * Fired when a cell has a click.
71043     *
71044     * @event cellClickEvent
71045     * @param oArgs.event {HTMLEvent} The event object.
71046     * @param oArgs.target {HTMLElement} The TD element.
71047     */
71048
71049    /**
71050     * Fired when a cell has a dblclick.
71051     *
71052     * @event cellDblclickEvent
71053     * @param oArgs.event {HTMLEvent} The event object.
71054     * @param oArgs.target {HTMLElement} The TD element.
71055     */
71056
71057    /**
71058     * Fired when a cell is formatted.
71059     *
71060     * @event cellFormatEvent
71061     * @param oArgs.el {HTMLElement} The formatted TD element.
71062     * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
71063     * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
71064     * @param oArgs.key {String} (deprecated) The key of the formatted cell.
71065     */
71066
71067    /**
71068     * Fired when a cell is selected.
71069     *
71070     * @event cellSelectEvent
71071     * @param oArgs.el {HTMLElement} The selected TD element.
71072     * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
71073     * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
71074     * @param oArgs.key {String} (deprecated) The key of the selected cell.
71075     */
71076
71077    /**
71078     * Fired when a cell is unselected.
71079     *
71080     * @event cellUnselectEvent
71081     * @param oArgs.el {HTMLElement} The unselected TD element.
71082     * @param oArgs.record {YAHOO.widget.Record} The associated Record.
71083     * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
71084     * @param oArgs.key {String} (deprecated) The key of the unselected cell.
71085
71086     */
71087
71088    /**
71089     * Fired when a cell is highlighted.
71090     *
71091     * @event cellHighlightEvent
71092     * @param oArgs.el {HTMLElement} The highlighted TD element.
71093     * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
71094     * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
71095     * @param oArgs.key {String} (deprecated) The key of the highlighted cell.
71096
71097     */
71098
71099    /**
71100     * Fired when a cell is unhighlighted.
71101     *
71102     * @event cellUnhighlightEvent
71103     * @param oArgs.el {HTMLElement} The unhighlighted TD element.
71104     * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
71105     * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
71106     * @param oArgs.key {String} (deprecated) The key of the unhighlighted cell.
71107
71108     */
71109
71110    /**
71111     * Fired when all cell selections are cleared.
71112     *
71113     * @event unselectAllCellsEvent
71114     */
71115
71116    /**
71117     * Fired when a CellEditor is shown.
71118     *
71119     * @event editorShowEvent
71120     * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
71121     */
71122
71123    /**
71124     * Fired when a CellEditor has a keydown.
71125     *
71126     * @event editorKeydownEvent
71127     * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
71128     * @param oArgs.event {HTMLEvent} The event object.
71129     */
71130
71131    /**
71132     * Fired when a CellEditor input is reverted.
71133     *
71134     * @event editorRevertEvent
71135     * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
71136     * @param oArgs.newData {Object} New data value from form input field.
71137     * @param oArgs.oldData {Object} Old data value.
71138     */
71139
71140    /**
71141     * Fired when a CellEditor input is saved.
71142     *
71143     * @event editorSaveEvent
71144     * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
71145     * @param oArgs.newData {Object} New data value from form input field.
71146     * @param oArgs.oldData {Object} Old data value.
71147     */
71148
71149    /**
71150     * Fired when a CellEditor input is canceled.
71151     *
71152     * @event editorCancelEvent
71153     * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
71154     */
71155
71156    /**
71157     * Fired when a CellEditor has a blur event.
71158     *
71159     * @event editorBlurEvent
71160     * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
71161     */
71162
71163    /**
71164     * Fired when a CellEditor is blocked.
71165     *
71166     * @event editorBlockEvent
71167     * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
71168     */
71169
71170    /**
71171     * Fired when a CellEditor is unblocked.
71172     *
71173     * @event editorUnblockEvent
71174     * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
71175     */
71176
71177
71178
71179
71180
71181    /**
71182     * Fired when a link is clicked.
71183     *
71184     * @event linkClickEvent
71185     * @param oArgs.event {HTMLEvent} The event object.
71186     * @param oArgs.target {HTMLElement} The A element.
71187     */
71188
71189    /**
71190     * Fired when a BUTTON element or INPUT element of type "button", "image",
71191     * "submit", "reset" is clicked.
71192     *
71193     * @event buttonClickEvent
71194     * @param oArgs.event {HTMLEvent} The event object.
71195     * @param oArgs.target {HTMLElement} The BUTTON element.
71196     */
71197
71198    /**
71199     * Fired when a CHECKBOX element is clicked.
71200     *
71201     * @event checkboxClickEvent
71202     * @param oArgs.event {HTMLEvent} The event object.
71203     * @param oArgs.target {HTMLElement} The CHECKBOX element.
71204     */
71205
71206    /**
71207     * Fired when a SELECT element is changed.
71208     *
71209     * @event dropdownChangeEvent
71210     * @param oArgs.event {HTMLEvent} The event object.
71211     * @param oArgs.target {HTMLElement} The SELECT element.
71212     */
71213
71214    /**
71215     * Fired when a RADIO element is clicked.
71216     *
71217     * @event radioClickEvent
71218     * @param oArgs.event {HTMLEvent} The event object.
71219     * @param oArgs.target {HTMLElement} The RADIO element.
71220     */
71221
71222
71223
71224
71225
71226
71227
71228
71229
71230
71231
71232
71233
71234
71235
71236
71237
71238
71239
71240
71241
71242
71243
71244
71245
71246
71247/////////////////////////////////////////////////////////////////////////////
71248//
71249// Deprecated APIs
71250//
71251/////////////////////////////////////////////////////////////////////////////
71252  
71253/*
71254 * @method showCellEditorBtns
71255 * @deprecated Use CellEditor.renderBtns() 
71256 */
71257showCellEditorBtns : function(elContainer) {
71258    // Buttons
71259    var elBtnsDiv = elContainer.appendChild(document.createElement("div"));
71260    Dom.addClass(elBtnsDiv, DT.CLASS_BUTTON);
71261
71262    // Save button
71263    var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
71264    Dom.addClass(elSaveBtn, DT.CLASS_DEFAULT);
71265    elSaveBtn.innerHTML = "OK";
71266    Ev.addListener(elSaveBtn, "click", function(oArgs, oSelf) {
71267        oSelf.onEventSaveCellEditor(oArgs, oSelf);
71268        oSelf.focusTbodyEl();
71269    }, this, true);
71270
71271    // Cancel button
71272    var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
71273    elCancelBtn.innerHTML = "Cancel";
71274    Ev.addListener(elCancelBtn, "click", function(oArgs, oSelf) {
71275        oSelf.onEventCancelCellEditor(oArgs, oSelf);
71276        oSelf.focusTbodyEl();
71277    }, this, true);
71278
71279    YAHOO.log("The method showCellEditorBtns() has been deprecated." +
71280            " Please use the CellEditor class.", "warn", this.toString());
71281},
71282
71283/**
71284 * @method resetCellEditor
71285 * @deprecated Use destroyCellEditor 
71286 */
71287resetCellEditor : function() {
71288    var elContainer = this._oCellEditor.container;
71289    elContainer.style.display = "none";
71290    Ev.purgeElement(elContainer, true);
71291    elContainer.innerHTML = "";
71292    this._oCellEditor.value = null;
71293    this._oCellEditor.isActive = false;
71294
71295    YAHOO.log("The method resetCellEditor() has been deprecated." +
71296            " Please use the CellEditor class.", "warn", this.toString());
71297},
71298
71299/**
71300 * @event editorUpdateEvent
71301 * @deprecated Use CellEditor class.
71302 */
71303
71304/**
71305 * @method getBody
71306 * @deprecated Use getTbodyEl().
71307 */
71308getBody : function() {
71309    // Backward compatibility
71310    YAHOO.log("The method getBody() has been deprecated" +
71311            " in favor of getTbodyEl()", "warn", this.toString());
71312    return this.getTbodyEl();
71313},
71314
71315/**
71316 * @method getCell
71317 * @deprecated Use getTdEl().
71318 */
71319getCell : function(index) {
71320    // Backward compatibility
71321    YAHOO.log("The method getCell() has been deprecated" +
71322            " in favor of getTdEl()", "warn", this.toString());
71323    return this.getTdEl(index);
71324},
71325
71326/**
71327 * @method getRow
71328 * @deprecated Use getTrEl().
71329 */
71330getRow : function(index) {
71331    // Backward compatibility
71332    YAHOO.log("The method getRow() has been deprecated" +
71333            " in favor of getTrEl()", "warn", this.toString());
71334    return this.getTrEl(index);
71335},
71336
71337/**
71338 * @method refreshView
71339 * @deprecated Use render.
71340 */
71341refreshView : function() {
71342    // Backward compatibility
71343    YAHOO.log("The method refreshView() has been deprecated" +
71344            " in favor of render()", "warn", this.toString());
71345    this.render();
71346},
71347
71348/**
71349 * @method select
71350 * @deprecated Use selectRow.
71351 */
71352select : function(els) {
71353    // Backward compatibility
71354    YAHOO.log("The method select() has been deprecated" +
71355            " in favor of selectRow()", "warn", this.toString());
71356    if(!lang.isArray(els)) {
71357        els = [els];
71358    }
71359    for(var i=0; i<els.length; i++) {
71360        this.selectRow(els[i]);
71361    }
71362},
71363
71364/**
71365 * @method onEventEditCell
71366 * @deprecated Use onEventShowCellEditor.
71367 */
71368onEventEditCell : function(oArgs) {
71369    // Backward compatibility
71370    YAHOO.log("The method onEventEditCell() has been deprecated" +
71371        " in favor of onEventShowCellEditor()", "warn", this.toString());
71372    this.onEventShowCellEditor(oArgs);
71373},
71374
71375/**
71376 * @method _syncColWidths
71377 * @deprecated Use validateColumnWidths.
71378 */
71379_syncColWidths : function() {
71380    // Backward compatibility
71381    YAHOO.log("The method _syncColWidths() has been deprecated" +
71382        " in favor of validateColumnWidths()", "warn", this.toString());
71383    this.validateColumnWidths();
71384}
71385
71386/**
71387 * @event headerRowMouseoverEvent
71388 * @deprecated Use theadRowMouseoverEvent.
71389 */
71390
71391/**
71392 * @event headerRowMouseoutEvent
71393 * @deprecated Use theadRowMouseoutEvent.
71394 */
71395
71396/**
71397 * @event headerRowMousedownEvent
71398 * @deprecated Use theadRowMousedownEvent.
71399 */
71400
71401/**
71402 * @event headerRowClickEvent
71403 * @deprecated Use theadRowClickEvent.
71404 */
71405
71406/**
71407 * @event headerRowDblclickEvent
71408 * @deprecated Use theadRowDblclickEvent.
71409 */
71410
71411/**
71412 * @event headerCellMouseoverEvent
71413 * @deprecated Use theadCellMouseoverEvent.
71414 */
71415
71416/**
71417 * @event headerCellMouseoutEvent
71418 * @deprecated Use theadCellMouseoutEvent.
71419 */
71420
71421/**
71422 * @event headerCellMousedownEvent
71423 * @deprecated Use theadCellMousedownEvent.
71424 */
71425
71426/**
71427 * @event headerCellClickEvent
71428 * @deprecated Use theadCellClickEvent.
71429 */
71430
71431/**
71432 * @event headerCellDblclickEvent
71433 * @deprecated Use theadCellDblclickEvent.
71434 */
71435
71436/**
71437 * @event headerLabelMouseoverEvent
71438 * @deprecated Use theadLabelMouseoverEvent.
71439 */
71440
71441/**
71442 * @event headerLabelMouseoutEvent
71443 * @deprecated Use theadLabelMouseoutEvent.
71444 */
71445
71446/**
71447 * @event headerLabelMousedownEvent
71448 * @deprecated Use theadLabelMousedownEvent.
71449 */
71450
71451/**
71452 * @event headerLabelClickEvent
71453 * @deprecated Use theadLabelClickEvent.
71454 */
71455
71456/**
71457 * @event headerLabelDbllickEvent
71458 * @deprecated Use theadLabelDblclickEvent.
71459 */
71460
71461});
71462
71463/**
71464 * Alias for onDataReturnSetRows for backward compatibility
71465 * @method onDataReturnSetRecords
71466 * @deprecated Use onDataReturnSetRows
71467 */
71468DT.prototype.onDataReturnSetRecords = DT.prototype.onDataReturnSetRows;
71469
71470/**
71471 * Alias for onPaginatorChange for backward compatibility
71472 * @method onPaginatorChange
71473 * @deprecated Use onPaginatorChangeRequest
71474 */
71475DT.prototype.onPaginatorChange = DT.prototype.onPaginatorChangeRequest;
71476
71477/////////////////////////////////////////////////////////////////////////////
71478//
71479// Deprecated static APIs
71480//
71481/////////////////////////////////////////////////////////////////////////////
71482/**
71483 * @method DataTable.editCheckbox
71484 * @deprecated  Use YAHOO.widget.CheckboxCellEditor.
71485 */
71486DT.editCheckbox = function() {};
71487
71488/**
71489 * @method DataTable.editDate
71490 * @deprecated Use YAHOO.widget.DateCellEditor.
71491 */
71492DT.editDate = function() {};
71493
71494/**
71495 * @method DataTable.editDropdown
71496 * @deprecated Use YAHOO.widget.DropdownCellEditor.
71497 */
71498DT.editDropdown = function() {};
71499
71500/**
71501 * @method DataTable.editRadio
71502 * @deprecated Use YAHOO.widget.RadioCellEditor.
71503 */
71504DT.editRadio = function() {};
71505
71506/**
71507 * @method DataTable.editTextarea
71508 * @deprecated Use YAHOO.widget.TextareaCellEditor
71509 */
71510DT.editTextarea = function() {};
71511
71512/**
71513 * @method DataTable.editTextbox
71514 * @deprecated Use YAHOO.widget.TextboxCellEditor
71515 */
71516DT.editTextbox= function() {};
71517
71518})();
71519
71520(function () {
71521
71522var lang   = YAHOO.lang,
71523    util   = YAHOO.util,
71524    widget = YAHOO.widget,
71525    ua     = YAHOO.env.ua,
71526    
71527    Dom    = util.Dom,
71528    Ev     = util.Event,
71529    DS     = util.DataSourceBase,
71530    DT     = widget.DataTable,
71531    Pag    = widget.Paginator;
71532    
71533/**
71534 * The ScrollingDataTable class extends the DataTable class to provide
71535 * functionality for x-scrolling, y-scrolling, and xy-scrolling.
71536 *
71537 * @namespace YAHOO.widget
71538 * @class ScrollingDataTable
71539 * @extends YAHOO.widget.DataTable
71540 * @constructor
71541 * @param elContainer {HTMLElement} Container element for the TABLE.
71542 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
71543 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
71544 * @param oConfigs {object} (optional) Object literal of configuration values.
71545 */
71546widget.ScrollingDataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
71547    oConfigs = oConfigs || {};
71548    
71549    // Prevent infinite loop
71550    if(oConfigs.scrollable) {
71551        oConfigs.scrollable = false;
71552    }
71553
71554    this._init();
71555
71556    widget.ScrollingDataTable.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs); 
71557
71558    // Once per instance
71559    this.subscribe("columnShowEvent", this._onColumnChange);
71560};
71561
71562var SDT = widget.ScrollingDataTable;
71563
71564/////////////////////////////////////////////////////////////////////////////
71565//
71566// Public constants
71567//
71568/////////////////////////////////////////////////////////////////////////////
71569lang.augmentObject(SDT, {
71570
71571    /**
71572     * Class name assigned to inner DataTable header container.
71573     *
71574     * @property DataTable.CLASS_HEADER
71575     * @type String
71576     * @static
71577     * @final
71578     * @default "yui-dt-hd"
71579     */
71580    CLASS_HEADER : "yui-dt-hd",
71581    
71582    /**
71583     * Class name assigned to inner DataTable body container.
71584     *
71585     * @property DataTable.CLASS_BODY
71586     * @type String
71587     * @static
71588     * @final
71589     * @default "yui-dt-bd"
71590     */
71591    CLASS_BODY : "yui-dt-bd"
71592});
71593
71594lang.extend(SDT, DT, {
71595
71596/**
71597 * Container for fixed header TABLE element.
71598 *
71599 * @property _elHdContainer
71600 * @type HTMLElement
71601 * @private
71602 */
71603_elHdContainer : null,
71604
71605/**
71606 * Fixed header TABLE element.
71607 *
71608 * @property _elHdTable
71609 * @type HTMLElement
71610 * @private
71611 */
71612_elHdTable : null,
71613
71614/**
71615 * Container for scrolling body TABLE element.
71616 *
71617 * @property _elBdContainer
71618 * @type HTMLElement
71619 * @private
71620 */
71621_elBdContainer : null,
71622
71623/**
71624 * Body THEAD element.
71625 *
71626 * @property _elBdThead
71627 * @type HTMLElement
71628 * @private
71629 */
71630_elBdThead : null,
71631
71632/**
71633 * Offscreen container to temporarily clone SDT for auto-width calculation.
71634 *
71635 * @property _elTmpContainer
71636 * @type HTMLElement
71637 * @private
71638 */
71639_elTmpContainer : null,
71640
71641/**
71642 * Offscreen TABLE element for auto-width calculation.
71643 *
71644 * @property _elTmpTable
71645 * @type HTMLElement
71646 * @private
71647 */
71648_elTmpTable : null,
71649
71650/**
71651 * True if x-scrollbar is currently visible.
71652 * @property _bScrollbarX
71653 * @type Boolean
71654 * @private 
71655 */
71656_bScrollbarX : null,
71657
71658
71659
71660
71661
71662
71663
71664
71665
71666
71667
71668
71669
71670
71671
71672/////////////////////////////////////////////////////////////////////////////
71673//
71674// Superclass methods
71675//
71676/////////////////////////////////////////////////////////////////////////////
71677
71678/**
71679 * Implementation of Element's abstract method. Sets up config values.
71680 *
71681 * @method initAttributes
71682 * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
71683 * @private
71684 */
71685
71686initAttributes : function(oConfigs) {
71687    oConfigs = oConfigs || {};
71688    SDT.superclass.initAttributes.call(this, oConfigs);
71689
71690    /**
71691    * @attribute width
71692    * @description Table width for scrollable tables (e.g., "40em").
71693    * @type String
71694    */
71695    this.setAttributeConfig("width", {
71696        value: null,
71697        validator: lang.isString,
71698        method: function(oParam) {
71699            if(this._elHdContainer && this._elBdContainer) {
71700                this._elHdContainer.style.width = oParam;
71701                this._elBdContainer.style.width = oParam;            
71702                this._syncScrollX();      
71703                this._syncScrollOverhang();
71704            }
71705        }
71706    });
71707
71708    /**
71709    * @attribute height
71710    * @description Table body height for scrollable tables, not including headers (e.g., "40em").
71711    * @type String
71712    */
71713    this.setAttributeConfig("height", {
71714        value: null,
71715        validator: lang.isString,
71716        method: function(oParam) {
71717            if(this._elHdContainer && this._elBdContainer) {
71718                this._elBdContainer.style.height = oParam;    
71719                this._syncScrollX();   
71720                this._syncScrollY();
71721                this._syncScrollOverhang();
71722            }
71723        }
71724    });
71725
71726    /**
71727    * @attribute COLOR_COLUMNFILLER
71728    * @description CSS color value assigned to header filler on scrollable tables.  
71729    * @type String
71730    * @default "#F2F2F2"
71731    */
71732    this.setAttributeConfig("COLOR_COLUMNFILLER", {
71733        value: "#F2F2F2",
71734        validator: lang.isString,
71735        method: function(oParam) {
71736            if(this._elHdContainer) {
71737                this._elHdContainer.style.backgroundColor = oParam;
71738            }
71739        }
71740    });
71741},
71742
71743/**
71744 * Initializes internal variables.
71745 *
71746 * @method _init
71747 * @private
71748 */
71749_init : function() {
71750    this._elHdContainer = null;
71751    this._elHdTable = null;
71752    this._elBdContainer = null;
71753    this._elBdThead = null;
71754    this._elTmpContainer = null;
71755    this._elTmpTable = null;
71756},
71757
71758/**
71759 * Initializes DOM elements for a ScrollingDataTable, including creation of
71760 * two separate TABLE elements.
71761 *
71762 * @method _initDomElements
71763 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 
71764 * return {Boolean} False in case of error, otherwise true 
71765 * @private
71766 */
71767_initDomElements : function(elContainer) {
71768    // Outer and inner containers
71769    this._initContainerEl(elContainer);
71770    if(this._elContainer && this._elHdContainer && this._elBdContainer) {
71771        // TABLEs
71772        this._initTableEl();
71773        
71774        if(this._elHdTable && this._elTable) {
71775            // COLGROUPs
71776            ///this._initColgroupEl(this._elHdTable, this._elTable);  
71777            this._initColgroupEl(this._elHdTable);        
71778            
71779            // THEADs
71780            this._initTheadEl(this._elHdTable, this._elTable);
71781            
71782            // Primary TBODY
71783            this._initTbodyEl(this._elTable);
71784            // Message TBODY
71785            this._initMsgTbodyEl(this._elTable);            
71786        }
71787    }
71788    if(!this._elContainer || !this._elTable || !this._elColgroup ||  !this._elThead || !this._elTbody || !this._elMsgTbody ||
71789            !this._elHdTable || !this._elBdThead) {
71790        YAHOO.log("Could not instantiate DataTable due to an invalid DOM elements", "error", this.toString());
71791        return false;
71792    }
71793    else {
71794        return true;
71795    }
71796},
71797
71798/**
71799 * Destroy's the DataTable outer and inner container elements, if available.
71800 *
71801 * @method _destroyContainerEl
71802 * @param elContainer {HTMLElement} Reference to the container element. 
71803 * @private
71804 */
71805_destroyContainerEl : function(elContainer) {
71806    Dom.removeClass(elContainer, DT.CLASS_SCROLLABLE);
71807    SDT.superclass._destroyContainerEl.call(this, elContainer);
71808    this._elHdContainer = null;
71809    this._elBdContainer = null;
71810},
71811
71812/**
71813 * Initializes the DataTable outer container element and creates inner header
71814 * and body container elements.
71815 *
71816 * @method _initContainerEl
71817 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
71818 * @private
71819 */
71820_initContainerEl : function(elContainer) {
71821    SDT.superclass._initContainerEl.call(this, elContainer);
71822    
71823    if(this._elContainer) {
71824        elContainer = this._elContainer; // was constructor input, now is DOM ref
71825        Dom.addClass(elContainer, DT.CLASS_SCROLLABLE);
71826        
71827        // Container for header TABLE
71828        var elHdContainer = document.createElement("div");
71829        elHdContainer.style.width = this.get("width") || "";
71830        elHdContainer.style.backgroundColor = this.get("COLOR_COLUMNFILLER");
71831        Dom.addClass(elHdContainer, SDT.CLASS_HEADER);
71832        this._elHdContainer = elHdContainer;
71833        elContainer.appendChild(elHdContainer);
71834    
71835        // Container for body TABLE
71836        var elBdContainer = document.createElement("div");
71837        elBdContainer.style.width = this.get("width") || "";
71838        elBdContainer.style.height = this.get("height") || "";
71839        Dom.addClass(elBdContainer, SDT.CLASS_BODY);
71840        Ev.addListener(elBdContainer, "scroll", this._onScroll, this); // to sync horiz scroll headers
71841        this._elBdContainer = elBdContainer;
71842        elContainer.appendChild(elBdContainer);
71843    }
71844},
71845
71846/**
71847 * Creates HTML markup CAPTION element.
71848 *
71849 * @method _initCaptionEl
71850 * @param sCaption {String} Text for caption.
71851 * @private
71852 */
71853_initCaptionEl : function(sCaption) {
71854    // Not yet supported
71855    /*if(this._elHdTable && sCaption) {
71856        // Create CAPTION element
71857        if(!this._elCaption) { 
71858            this._elCaption = this._elHdTable.createCaption();
71859        }
71860        // Set CAPTION value
71861        this._elCaption.innerHTML = sCaption;
71862    }
71863    else if(this._elCaption) {
71864        this._elCaption.parentNode.removeChild(this._elCaption);
71865    }*/
71866},
71867
71868/**
71869 * Destroy's the DataTable head TABLE element, if available.
71870 *
71871 * @method _destroyHdTableEl
71872 * @private
71873 */
71874_destroyHdTableEl : function() {
71875    var elTable = this._elHdTable;
71876    if(elTable) {
71877        Ev.purgeElement(elTable, true);
71878        elTable.parentNode.removeChild(elTable);
71879        
71880        // A little out of place, but where else can we null out these extra elements?
71881        ///this._elBdColgroup = null;
71882        this._elBdThead = null;
71883    }
71884},
71885
71886/**
71887 * Initializes ScrollingDataTable TABLE elements into the two inner containers.
71888 *
71889 * @method _initTableEl
71890 * @private
71891 */
71892_initTableEl : function() {
71893    // Head TABLE
71894    if(this._elHdContainer) {
71895        this._destroyHdTableEl();
71896    
71897        // Create TABLE
71898        this._elHdTable = this._elHdContainer.appendChild(document.createElement("table"));   
71899
71900        // Set up mouseover/mouseout events via mouseenter/mouseleave delegation
71901        Ev.delegate(this._elHdTable, "mouseenter", this._onTableMouseover, "thead ."+DT.CLASS_LABEL, this);
71902        Ev.delegate(this._elHdTable, "mouseleave", this._onTableMouseout, "thead ."+DT.CLASS_LABEL, this);
71903    }
71904    // Body TABLE
71905    SDT.superclass._initTableEl.call(this, this._elBdContainer);
71906},
71907
71908/**
71909 * Initializes ScrollingDataTable THEAD elements into the two inner containers.
71910 *
71911 * @method _initTheadEl
71912 * @param elHdTable {HTMLElement} (optional) Fixed header TABLE element reference.
71913 * @param elTable {HTMLElement} (optional) TABLE element reference.
71914 * @private
71915 */
71916_initTheadEl : function(elHdTable, elTable) {
71917    elHdTable = elHdTable || this._elHdTable;
71918    elTable = elTable || this._elTable;
71919    
71920    // Scrolling body's THEAD
71921    this._initBdTheadEl(elTable);
71922    // Standard fixed head THEAD
71923    SDT.superclass._initTheadEl.call(this, elHdTable);
71924},
71925
71926/**
71927 * SDT changes ID so as not to duplicate the accessibility TH IDs.
71928 *
71929 * @method _initThEl
71930 * @param elTh {HTMLElement} TH element reference.
71931 * @param oColumn {YAHOO.widget.Column} Column object.
71932 * @private
71933 */
71934_initThEl : function(elTh, oColumn) {
71935    SDT.superclass._initThEl.call(this, elTh, oColumn);
71936    elTh.id = this.getId() +"-fixedth-" + oColumn.getSanitizedKey(); // Needed for getColumn by TH and ColumnDD
71937},
71938
71939/**
71940 * Destroy's the DataTable body THEAD element, if available.
71941 *
71942 * @method _destroyBdTheadEl
71943 * @private
71944 */
71945_destroyBdTheadEl : function() {
71946    var elBdThead = this._elBdThead;
71947    if(elBdThead) {
71948        var elTable = elBdThead.parentNode;
71949        Ev.purgeElement(elBdThead, true);
71950        elTable.removeChild(elBdThead);
71951        this._elBdThead = null;
71952
71953        this._destroyColumnHelpers();
71954    }
71955},
71956
71957/**
71958 * Initializes body THEAD element.
71959 *
71960 * @method _initBdTheadEl
71961 * @param elTable {HTMLElement} TABLE element into which to create THEAD.
71962 * @return {HTMLElement} Initialized THEAD element. 
71963 * @private
71964 */
71965_initBdTheadEl : function(elTable) {
71966    if(elTable) {
71967        // Destroy previous
71968        this._destroyBdTheadEl();
71969
71970        var elThead = elTable.insertBefore(document.createElement("thead"), elTable.firstChild);
71971        
71972        // Add TRs to the THEAD;
71973        var oColumnSet = this._oColumnSet,
71974            colTree = oColumnSet.tree,
71975            elTh, elTheadTr, oColumn, i, j, k, len;
71976
71977        for(i=0, k=colTree.length; i<k; i++) {
71978            elTheadTr = elThead.appendChild(document.createElement("tr"));
71979    
71980            // ...and create TH cells
71981            for(j=0, len=colTree[i].length; j<len; j++) {
71982                oColumn = colTree[i][j];
71983                elTh = elTheadTr.appendChild(document.createElement("th"));
71984                this._initBdThEl(elTh,oColumn,i,j);
71985            }
71986        }
71987        this._elBdThead = elThead;
71988        YAHOO.log("Accessibility TH cells for " + this._oColumnSet.keys.length + " keys created","info",this.toString());
71989    }
71990},
71991
71992/**
71993 * Populates TH element for the body THEAD element.
71994 *
71995 * @method _initBdThEl
71996 * @param elTh {HTMLElement} TH element reference.
71997 * @param oColumn {YAHOO.widget.Column} Column object.
71998 * @private
71999 */
72000_initBdThEl : function(elTh, oColumn) {
72001    elTh.id = this.getId()+"-th-" + oColumn.getSanitizedKey(); // Needed for accessibility
72002    elTh.rowSpan = oColumn.getRowspan();
72003    elTh.colSpan = oColumn.getColspan();
72004    // Assign abbr attribute
72005    if(oColumn.abbr) {
72006        elTh.abbr = oColumn.abbr;
72007    }
72008
72009    // TODO: strip links and form elements
72010    var sKey = oColumn.getKey();
72011    var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
72012    elTh.innerHTML = sLabel;
72013},
72014
72015/**
72016 * Initializes ScrollingDataTable TBODY element for data
72017 *
72018 * @method _initTbodyEl
72019 * @param elTable {HTMLElement} TABLE element into which to create TBODY .
72020 * @private
72021 */
72022_initTbodyEl : function(elTable) {
72023    SDT.superclass._initTbodyEl.call(this, elTable);
72024    
72025    // Bug 2105534 - Safari 3 gap
72026    // Bug 2492591 - IE8 offsetTop
72027    elTable.style.marginTop = (this._elTbody.offsetTop > 0) ?
72028            "-"+this._elTbody.offsetTop+"px" : 0;
72029},
72030
72031
72032
72033
72034
72035
72036
72037
72038
72039
72040
72041
72042
72043
72044
72045
72046
72047
72048
72049
72050
72051
72052
72053
72054
72055
72056
72057
72058
72059/**
72060 * Sets focus on the given element.
72061 *
72062 * @method _focusEl
72063 * @param el {HTMLElement} Element.
72064 * @private
72065 */
72066_focusEl : function(el) {
72067    el = el || this._elTbody;
72068    var oSelf = this;
72069    this._storeScrollPositions();
72070    // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
72071    // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
72072    // strange unexpected things as the user clicks on buttons and other controls.
72073    
72074    // Bug 1921135: Wrap the whole thing in a setTimeout
72075    setTimeout(function() {
72076        setTimeout(function() {
72077            try {
72078                el.focus();
72079                oSelf._restoreScrollPositions();
72080            }
72081            catch(e) {
72082            }
72083        },0);
72084    }, 0);
72085},
72086
72087
72088
72089
72090
72091
72092
72093
72094
72095
72096
72097
72098
72099
72100
72101
72102
72103
72104
72105/**
72106 * Internal wrapper calls run() on render Chain instance.
72107 *
72108 * @method _runRenderChain
72109 * @private 
72110 */
72111_runRenderChain : function() {
72112    this._storeScrollPositions();
72113    this._oChainRender.run();
72114},
72115
72116/**
72117 * Stores scroll positions so they can be restored after a render.
72118 *
72119 * @method _storeScrollPositions
72120 * @private
72121 */
72122 _storeScrollPositions : function() {
72123    this._nScrollTop = this._elBdContainer.scrollTop;
72124    this._nScrollLeft = this._elBdContainer.scrollLeft;
72125},
72126
72127/**
72128 * Clears stored scroll positions to interrupt the automatic restore mechanism.
72129 * Useful for setting scroll positions programmatically rather than as part of
72130 * the post-render cleanup process.
72131 *
72132 * @method clearScrollPositions
72133 * @private
72134 */
72135 clearScrollPositions : function() {
72136    this._nScrollTop = 0;
72137    this._nScrollLeft = 0;
72138},
72139
72140/**
72141 * Restores scroll positions to stored value. 
72142 *
72143 * @method _retoreScrollPositions
72144 * @private 
72145 */
72146 _restoreScrollPositions : function() {
72147    // Reset scroll positions
72148    if(this._nScrollTop) {
72149        this._elBdContainer.scrollTop = this._nScrollTop;
72150        this._nScrollTop = null;
72151    } 
72152    if(this._nScrollLeft) {
72153        this._elBdContainer.scrollLeft = this._nScrollLeft;
72154        // Bug 2529024
72155        this._elHdContainer.scrollLeft = this._nScrollLeft; 
72156        this._nScrollLeft = null;
72157    } 
72158},
72159
72160/**
72161 * Helper function calculates and sets a validated width for a Column in a ScrollingDataTable.
72162 *
72163 * @method _validateColumnWidth
72164 * @param oColumn {YAHOO.widget.Column} Column instance.
72165 * @param elTd {HTMLElement} TD element to validate against.
72166 * @private
72167 */
72168_validateColumnWidth : function(oColumn, elTd) {
72169    // Only Columns without widths that are not hidden
72170    if(!oColumn.width && !oColumn.hidden) {
72171        var elTh = oColumn.getThEl();
72172        // Unset a calculated auto-width
72173        if(oColumn._calculatedWidth) {
72174            this._setColumnWidth(oColumn, "auto", "visible");
72175        }
72176        // Compare auto-widths
72177        if(elTh.offsetWidth !== elTd.offsetWidth) {
72178            var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
72179                    oColumn.getThLinerEl() : elTd.firstChild;               
72180
72181            // Grab the wider liner width, unless the minWidth is wider
72182            var newWidth = Math.max(0,
72183                (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
72184                oColumn.minWidth);
72185                
72186            var sOverflow = 'visible';
72187            
72188            // Now validate against maxAutoWidth
72189            if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
72190                newWidth = oColumn.maxAutoWidth;
72191                sOverflow = "hidden";
72192            }
72193
72194            // Set to the wider auto-width
72195            this._elTbody.style.display = "none";
72196            this._setColumnWidth(oColumn, newWidth+'px', sOverflow);
72197            oColumn._calculatedWidth = newWidth;
72198            this._elTbody.style.display = "";
72199        }
72200    }
72201},
72202
72203/**
72204 * For one or all Columns of a ScrollingDataTable, when Column is not hidden,
72205 * and width is not set, syncs widths of header and body cells and 
72206 * validates that width against minWidth and/or maxAutoWidth as necessary.
72207 *
72208 * @method validateColumnWidths
72209 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
72210 */
72211validateColumnWidths : function(oColumn) {
72212    // Validate there is at least one TR with proper TDs
72213    var allKeys   = this._oColumnSet.keys,
72214        allKeysLength = allKeys.length,
72215        elRow     = this.getFirstTrEl();
72216
72217    // Reset overhang for IE
72218    if(ua.ie) {
72219        this._setOverhangValue(1);
72220    }
72221
72222    if(allKeys && elRow && (elRow.childNodes.length === allKeysLength)) {
72223        // Temporarily unsnap container since it causes inaccurate calculations
72224        var sWidth = this.get("width");
72225        if(sWidth) {
72226            this._elHdContainer.style.width = "";
72227            this._elBdContainer.style.width = "";
72228        }
72229        this._elContainer.style.width = "";
72230        
72231        //Validate just one Column
72232        if(oColumn && lang.isNumber(oColumn.getKeyIndex())) {
72233            this._validateColumnWidth(oColumn, elRow.childNodes[oColumn.getKeyIndex()]);
72234        }
72235        // Iterate through all Columns to unset calculated widths in one pass
72236        else {
72237            var elTd, todos = [], thisTodo, i, len;
72238            for(i=0; i<allKeysLength; i++) {
72239                oColumn = allKeys[i];
72240                // Only Columns without widths that are not hidden, unset a calculated auto-width
72241                if(!oColumn.width && !oColumn.hidden && oColumn._calculatedWidth) {
72242                    todos[todos.length] = oColumn;      
72243                }
72244            }
72245            
72246            this._elTbody.style.display = "none";
72247            for(i=0, len=todos.length; i<len; i++) {
72248                this._setColumnWidth(todos[i], "auto", "visible");
72249            }
72250            this._elTbody.style.display = "";
72251            
72252            todos = [];
72253
72254            // Iterate through all Columns and make the store the adjustments to make in one pass
72255            for(i=0; i<allKeysLength; i++) {
72256                oColumn = allKeys[i];
72257                elTd = elRow.childNodes[i];
72258                // Only Columns without widths that are not hidden
72259                if(!oColumn.width && !oColumn.hidden) {
72260                    var elTh = oColumn.getThEl();
72261
72262                    // Compare auto-widths
72263                    if(elTh.offsetWidth !== elTd.offsetWidth) {
72264                        var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
72265                                oColumn.getThLinerEl() : elTd.firstChild;               
72266                
72267                        // Grab the wider liner width, unless the minWidth is wider
72268                        var newWidth = Math.max(0,
72269                            (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
72270                            oColumn.minWidth);
72271                            
72272                        var sOverflow = 'visible';
72273                        
72274                        // Now validate against maxAutoWidth
72275                        if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
72276                            newWidth = oColumn.maxAutoWidth;
72277                            sOverflow = "hidden";
72278                        }
72279                
72280                        todos[todos.length] = [oColumn, newWidth, sOverflow];
72281                    }
72282                }
72283            }
72284            
72285            this._elTbody.style.display = "none";
72286            for(i=0, len=todos.length; i<len; i++) {
72287                thisTodo = todos[i];
72288                // Set to the wider auto-width
72289                this._setColumnWidth(thisTodo[0], thisTodo[1]+"px", thisTodo[2]);
72290                thisTodo[0]._calculatedWidth = thisTodo[1];
72291            }
72292            this._elTbody.style.display = "";
72293        }
72294    
72295        // Resnap unsnapped containers
72296        if(sWidth) {
72297            this._elHdContainer.style.width = sWidth;
72298            this._elBdContainer.style.width = sWidth;
72299        } 
72300    }
72301    
72302    this._syncScroll();
72303    this._restoreScrollPositions();
72304},
72305
72306/**
72307 * Syncs padding around scrollable tables, including Column header right-padding
72308 * and container width and height.
72309 *
72310 * @method _syncScroll
72311 * @private 
72312 */
72313_syncScroll : function() {
72314    this._syncScrollX();
72315    this._syncScrollY();
72316    this._syncScrollOverhang();
72317    if(ua.opera) {
72318        // Bug 1925874
72319        this._elHdContainer.scrollLeft = this._elBdContainer.scrollLeft;
72320        if(!this.get("width")) {
72321            // Bug 1926125
72322            document.body.style += '';
72323        }
72324    }
72325 },
72326
72327/**
72328 * Snaps container width for y-scrolling tables.
72329 *
72330 * @method _syncScrollY
72331 * @private
72332 */
72333_syncScrollY : function() {
72334    var elTbody = this._elTbody,
72335        elBdContainer = this._elBdContainer;
72336    
72337    // X-scrolling not enabled
72338    if(!this.get("width")) {
72339        // Snap outer container width to content
72340        this._elContainer.style.width = 
72341                (elBdContainer.scrollHeight > elBdContainer.clientHeight) ?
72342                // but account for y-scrollbar since it is visible
72343                (elTbody.parentNode.clientWidth + 19) + "px" :
72344                // no y-scrollbar, just borders
72345                (elTbody.parentNode.clientWidth + 2) + "px";
72346    }
72347},
72348
72349/**
72350 * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
72351 *
72352 * @method _syncScrollX
72353 * @private
72354 */
72355_syncScrollX : function() {
72356    var elTbody = this._elTbody,
72357        elBdContainer = this._elBdContainer;
72358    
72359    // IE 6 and 7 only when y-scrolling not enabled
72360    if(!this.get("height") && (ua.ie)) {
72361        // Snap outer container height to content
72362        elBdContainer.style.height = 
72363                // but account for x-scrollbar if it is visible
72364                (elBdContainer.scrollWidth > elBdContainer.offsetWidth ) ?
72365                (elTbody.parentNode.offsetHeight + 18) + "px" : 
72366                elTbody.parentNode.offsetHeight + "px";
72367    }
72368
72369    // Sync message tbody
72370    if(this._elTbody.rows.length === 0) {
72371        this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
72372    }
72373    else {
72374        this._elMsgTbody.parentNode.style.width = "";
72375    }
72376},
72377
72378/**
72379 * Adds/removes Column header overhang as necesary.
72380 *
72381 * @method _syncScrollOverhang
72382 * @private
72383 */
72384_syncScrollOverhang : function() {
72385    var elBdContainer = this._elBdContainer,
72386        // Overhang should be either 1 (default) or 18px, depending on the location of the right edge of the table
72387        nPadding = 1;
72388    
72389    // Y-scrollbar is visible, which is when the overhang needs to jut out
72390    if((elBdContainer.scrollHeight > elBdContainer.clientHeight) &&
72391        // X-scrollbar is also visible, which means the right is jagged, not flush with the Column
72392        (elBdContainer.scrollWidth > elBdContainer.clientWidth)) {
72393        nPadding = 18;
72394    }
72395    
72396    this._setOverhangValue(nPadding);
72397    
72398},
72399
72400/**
72401 * Sets Column header overhang to given width.
72402 *
72403 * @method _setOverhangValue
72404 * @param nBorderWidth {Number} Value of new border for overhang. 
72405 * @private
72406 */
72407_setOverhangValue : function(nBorderWidth) {
72408    var aLastHeaders = this._oColumnSet.headers[this._oColumnSet.headers.length-1] || [],
72409        len = aLastHeaders.length,
72410        sPrefix = this._sId+"-fixedth-",
72411        sValue = nBorderWidth + "px solid " + this.get("COLOR_COLUMNFILLER");
72412
72413    this._elThead.style.display = "none";
72414    for(var i=0; i<len; i++) {
72415        Dom.get(sPrefix+aLastHeaders[i]).style.borderRight = sValue;
72416    }
72417    this._elThead.style.display = "";
72418},
72419
72420
72421
72422
72423
72424
72425
72426
72427
72428
72429
72430
72431
72432
72433
72434
72435
72436
72437
72438
72439
72440
72441
72442
72443
72444
72445
72446
72447
72448
72449
72450
72451
72452
72453
72454
72455
72456
72457/**
72458 * Returns DOM reference to the DataTable's fixed header container element.
72459 *
72460 * @method getHdContainerEl
72461 * @return {HTMLElement} Reference to DIV element.
72462 */
72463getHdContainerEl : function() {
72464    return this._elHdContainer;
72465},
72466
72467/**
72468 * Returns DOM reference to the DataTable's scrolling body container element.
72469 *
72470 * @method getBdContainerEl
72471 * @return {HTMLElement} Reference to DIV element.
72472 */
72473getBdContainerEl : function() {
72474    return this._elBdContainer;
72475},
72476
72477/**
72478 * Returns DOM reference to the DataTable's fixed header TABLE element.
72479 *
72480 * @method getHdTableEl
72481 * @return {HTMLElement} Reference to TABLE element.
72482 */
72483getHdTableEl : function() {
72484    return this._elHdTable;
72485},
72486
72487/**
72488 * Returns DOM reference to the DataTable's scrolling body TABLE element.
72489 *
72490 * @method getBdTableEl
72491 * @return {HTMLElement} Reference to TABLE element.
72492 */
72493getBdTableEl : function() {
72494    return this._elTable;
72495},
72496
72497/**
72498 * Disables ScrollingDataTable UI.
72499 *
72500 * @method disable
72501 */
72502disable : function() {
72503    var elMask = this._elMask;
72504    elMask.style.width = this._elBdContainer.offsetWidth + "px";
72505    elMask.style.height = this._elHdContainer.offsetHeight + this._elBdContainer.offsetHeight + "px";
72506    elMask.style.display = "";
72507    this.fireEvent("disableEvent");
72508},
72509
72510/**
72511 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
72512 * non-nested Columns, and top-level parent Columns (which will remove all
72513 * children Columns).
72514 *
72515 * @method removeColumn
72516 * @param oColumn {YAHOO.widget.Column} Column instance.
72517 * @return oColumn {YAHOO.widget.Column} Removed Column instance.
72518 */
72519removeColumn : function(oColumn) {
72520    // Store scroll pos
72521    var hdPos = this._elHdContainer.scrollLeft;
72522    var bdPos = this._elBdContainer.scrollLeft;
72523    
72524    // Call superclass method
72525    oColumn = SDT.superclass.removeColumn.call(this, oColumn);
72526    
72527    // Restore scroll pos
72528    this._elHdContainer.scrollLeft = hdPos;
72529    this._elBdContainer.scrollLeft = bdPos;
72530    
72531    return oColumn;
72532},
72533
72534/**
72535 * Inserts given Column at the index if given, otherwise at the end. NOTE: You
72536 * can only add non-nested Columns and top-level parent Columns. You cannot add
72537 * a nested Column to an existing parent.
72538 *
72539 * @method insertColumn
72540 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
72541 * definition or a Column instance.
72542 * @param index {Number} (optional) New tree index.
72543 * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 
72544 */
72545insertColumn : function(oColumn, index) {
72546    // Store scroll pos
72547    var hdPos = this._elHdContainer.scrollLeft;
72548    var bdPos = this._elBdContainer.scrollLeft;
72549    
72550    // Call superclass method
72551    var oNewColumn = SDT.superclass.insertColumn.call(this, oColumn, index);
72552    
72553    // Restore scroll pos
72554    this._elHdContainer.scrollLeft = hdPos;
72555    this._elBdContainer.scrollLeft = bdPos;
72556    
72557    return oNewColumn;
72558},
72559
72560/**
72561 * Removes given Column and inserts into given tree index. NOTE: You
72562 * can only reorder non-nested Columns and top-level parent Columns. You cannot
72563 * reorder a nested Column to an existing parent.
72564 *
72565 * @method reorderColumn
72566 * @param oColumn {YAHOO.widget.Column} Column instance.
72567 * @param index {Number} New tree index.
72568 */
72569reorderColumn : function(oColumn, index) {
72570    // Store scroll pos
72571    var hdPos = this._elHdContainer.scrollLeft;
72572    var bdPos = this._elBdContainer.scrollLeft;
72573    
72574    // Call superclass method
72575    var oNewColumn = SDT.superclass.reorderColumn.call(this, oColumn, index);
72576    
72577    // Restore scroll pos
72578    this._elHdContainer.scrollLeft = hdPos;
72579    this._elBdContainer.scrollLeft = bdPos;
72580
72581    return oNewColumn;
72582},
72583
72584/**
72585 * Sets given Column to given pixel width. If new width is less than minWidth
72586 * width, sets to minWidth. Updates oColumn.width value.
72587 *
72588 * @method setColumnWidth
72589 * @param oColumn {YAHOO.widget.Column} Column instance.
72590 * @param nWidth {Number} New width in pixels.
72591 */
72592setColumnWidth : function(oColumn, nWidth) {
72593    oColumn = this.getColumn(oColumn);
72594    if(oColumn) {
72595        this._storeScrollPositions();
72596
72597        // Validate new width against minWidth
72598        if(lang.isNumber(nWidth)) {
72599            nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
72600
72601            // Save state
72602            oColumn.width = nWidth;
72603            
72604            // Resize the DOM elements
72605            this._setColumnWidth(oColumn, nWidth+"px");
72606            this._syncScroll();
72607            
72608            this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
72609            YAHOO.log("Set width of Column " + oColumn + " to " + nWidth + "px", "info", this.toString());
72610        }
72611        // Unsets a width to auto-size
72612        else if(nWidth === null) {
72613            // Save state
72614            oColumn.width = nWidth;
72615            
72616            // Resize the DOM elements
72617            this._setColumnWidth(oColumn, "auto");
72618            this.validateColumnWidths(oColumn);
72619            this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
72620            YAHOO.log("Column " + oColumn + " width unset", "info", this.toString());
72621        }
72622        
72623        // Bug 2339454: resize then sort misaligment
72624        this._clearTrTemplateEl();
72625    }
72626    else {
72627        YAHOO.log("Could not set width of Column " + oColumn + " to " + nWidth + "px", "warn", this.toString());
72628    }
72629},
72630
72631/**
72632 * Scrolls to given row or cell
72633 *
72634 * @method scrollTo
72635 * @param to {YAHOO.widget.Record | HTMLElement } Itme to scroll to.
72636 */
72637scrollTo : function(to) {
72638        var td = this.getTdEl(to);
72639        if(td) {
72640            this.clearScrollPositions();
72641            this.getBdContainerEl().scrollLeft = td.offsetLeft;
72642            this.getBdContainerEl().scrollTop = td.parentNode.offsetTop;
72643        }
72644        else {
72645            var tr = this.getTrEl(to);
72646            if(tr) {
72647                this.clearScrollPositions();
72648                this.getBdContainerEl().scrollTop = tr.offsetTop;
72649            }
72650        }
72651},
72652
72653/**
72654 * Displays message within secondary TBODY.
72655 *
72656 * @method showTableMessage
72657 * @param sHTML {String} (optional) Value for innerHTMlang.
72658 * @param sClassName {String} (optional) Classname.
72659 */
72660showTableMessage : function(sHTML, sClassName) {
72661    var elCell = this._elMsgTd;
72662    if(lang.isString(sHTML)) {
72663        elCell.firstChild.innerHTML = sHTML;
72664    }
72665    if(lang.isString(sClassName)) {
72666        Dom.addClass(elCell.firstChild, sClassName);
72667    }
72668
72669    // Needed for SDT only
72670    var elThead = this.getTheadEl();
72671    var elTable = elThead.parentNode;
72672    var newWidth = elTable.offsetWidth;
72673    this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
72674
72675    this._elMsgTbody.style.display = "";
72676
72677    this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
72678    YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString());
72679},
72680
72681
72682
72683
72684
72685
72686
72687
72688
72689
72690
72691
72692
72693/////////////////////////////////////////////////////////////////////////////
72694//
72695// Private Custom Event Handlers
72696//
72697/////////////////////////////////////////////////////////////////////////////
72698
72699/**
72700 * Handles Column mutations
72701 *
72702 * @method onColumnChange
72703 * @param oArgs {Object} Custom Event data.
72704 */
72705_onColumnChange : function(oArg) {
72706    // Figure out which Column changed
72707    var oColumn = (oArg.column) ? oArg.column :
72708            (oArg.editor) ? oArg.editor.column : null;
72709    this._storeScrollPositions();
72710    this.validateColumnWidths(oColumn);
72711},
72712
72713
72714
72715
72716
72717
72718
72719
72720
72721
72722
72723
72724
72725
72726
72727/////////////////////////////////////////////////////////////////////////////
72728//
72729// Private DOM Event Handlers
72730//
72731/////////////////////////////////////////////////////////////////////////////
72732
72733/**
72734 * Syncs scrolltop and scrollleft of all TABLEs.
72735 *
72736 * @method _onScroll
72737 * @param e {HTMLEvent} The scroll event.
72738 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
72739 * @private
72740 */
72741_onScroll : function(e, oSelf) {
72742    oSelf._elHdContainer.scrollLeft = oSelf._elBdContainer.scrollLeft;
72743
72744    if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
72745        oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
72746        oSelf.cancelCellEditor();
72747    }
72748
72749    var elTarget = Ev.getTarget(e);
72750    var elTag = elTarget.nodeName.toLowerCase();
72751    oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget});
72752},
72753
72754/**
72755 * Handles keydown events on the THEAD element.
72756 *
72757 * @method _onTheadKeydown
72758 * @param e {HTMLEvent} The key event.
72759 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
72760 * @private
72761 */
72762_onTheadKeydown : function(e, oSelf) {
72763    // If tabbing to next TH label link causes THEAD to scroll,
72764    // need to sync scrollLeft with TBODY
72765    if(Ev.getCharCode(e) === 9) {
72766        setTimeout(function() {
72767            if((oSelf instanceof SDT) && oSelf._sId) {
72768                oSelf._elBdContainer.scrollLeft = oSelf._elHdContainer.scrollLeft;
72769            }
72770        },0);
72771    }
72772    
72773    var elTarget = Ev.getTarget(e);
72774    var elTag = elTarget.nodeName.toLowerCase();
72775    var bKeepBubbling = true;
72776    while(elTarget && (elTag != "table")) {
72777        switch(elTag) {
72778            case "body":
72779                return;
72780            case "input":
72781            case "textarea":
72782                // TODO: implement textareaKeyEvent
72783                break;
72784            case "thead":
72785                bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
72786                break;
72787            default:
72788                break;
72789        }
72790        if(bKeepBubbling === false) {
72791            return;
72792        }
72793        else {
72794            elTarget = elTarget.parentNode;
72795            if(elTarget) {
72796                elTag = elTarget.nodeName.toLowerCase();
72797            }
72798        }
72799    }
72800    oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
72801}
72802
72803
72804
72805
72806/**
72807 * Fired when a fixed scrolling DataTable has a scroll.
72808 *
72809 * @event tableScrollEvent
72810 * @param oArgs.event {HTMLEvent} The event object.
72811 * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE)
72812 * or the DataTable's TBODY element (everyone else).
72813 *
72814 */
72815
72816
72817
72818
72819});
72820
72821})();
72822
72823(function () {
72824
72825var lang   = YAHOO.lang,
72826    util   = YAHOO.util,
72827    widget = YAHOO.widget,
72828    ua     = YAHOO.env.ua,
72829    
72830    Dom    = util.Dom,
72831    Ev     = util.Event,
72832    
72833    DT     = widget.DataTable;
72834/****************************************************************************/
72835/****************************************************************************/
72836/****************************************************************************/
72837    
72838/**
72839 * The BaseCellEditor class provides base functionality common to all inline cell
72840 * editors for a DataTable widget.
72841 *
72842 * @namespace YAHOO.widget
72843 * @class BaseCellEditor
72844 * @uses YAHOO.util.EventProvider 
72845 * @constructor
72846 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
72847 * @param oConfigs {Object} (Optional) Object literal of configs.
72848 */
72849widget.BaseCellEditor = function(sType, oConfigs) {
72850    this._sId = this._sId || Dom.generateId(null, "yui-ceditor"); // "yui-ceditor" + YAHOO.widget.BaseCellEditor._nCount++;
72851    YAHOO.widget.BaseCellEditor._nCount++;
72852    this._sType = sType;
72853    
72854    // Validate inputs
72855    this._initConfigs(oConfigs); 
72856    
72857    // Create Custom Events
72858    this._initEvents();
72859             
72860    // UI needs to be drawn
72861    this._needsRender = true;
72862};
72863
72864var BCE = widget.BaseCellEditor;
72865
72866/////////////////////////////////////////////////////////////////////////////
72867//
72868// Static members
72869//
72870/////////////////////////////////////////////////////////////////////////////
72871lang.augmentObject(BCE, {
72872
72873/**
72874 * Global instance counter.
72875 *
72876 * @property CellEditor._nCount
72877 * @type Number
72878 * @static
72879 * @default 0
72880 * @private 
72881 */
72882_nCount : 0,
72883
72884/**
72885 * Class applied to CellEditor container.
72886 *
72887 * @property CellEditor.CLASS_CELLEDITOR
72888 * @type String
72889 * @static
72890 * @default "yui-ceditor"
72891 */
72892CLASS_CELLEDITOR : "yui-ceditor"
72893
72894});
72895
72896BCE.prototype = {
72897/////////////////////////////////////////////////////////////////////////////
72898//
72899// Private members
72900//
72901/////////////////////////////////////////////////////////////////////////////
72902/**
72903 * Unique id assigned to instance "yui-ceditorN", useful prefix for generating unique
72904 * DOM ID strings and log messages.
72905 *
72906 * @property _sId
72907 * @type String
72908 * @private
72909 */
72910_sId : null,
72911
72912/**
72913 * Editor type.
72914 *
72915 * @property _sType
72916 * @type String
72917 * @private
72918 */
72919_sType : null,
72920
72921/**
72922 * DataTable instance.
72923 *
72924 * @property _oDataTable
72925 * @type YAHOO.widget.DataTable
72926 * @private 
72927 */
72928_oDataTable : null,
72929
72930/**
72931 * Column instance.
72932 *
72933 * @property _oColumn
72934 * @type YAHOO.widget.Column
72935 * @default null
72936 * @private 
72937 */
72938_oColumn : null,
72939
72940/**
72941 * Record instance.
72942 *
72943 * @property _oRecord
72944 * @type YAHOO.widget.Record
72945 * @default null
72946 * @private 
72947 */
72948_oRecord : null,
72949
72950/**
72951 * TD element.
72952 *
72953 * @property _elTd
72954 * @type HTMLElement
72955 * @default null
72956 * @private
72957 */
72958_elTd : null,
72959
72960/**
72961 * Container for inline editor.
72962 *
72963 * @property _elContainer
72964 * @type HTMLElement
72965 * @private 
72966 */
72967_elContainer : null,
72968
72969/**
72970 * Reference to Cancel button, if available.
72971 *
72972 * @property _elCancelBtn
72973 * @type HTMLElement
72974 * @default null
72975 * @private 
72976 */
72977_elCancelBtn : null,
72978
72979/**
72980 * Reference to Save button, if available.
72981 *
72982 * @property _elSaveBtn
72983 * @type HTMLElement
72984 * @default null
72985 * @private 
72986 */
72987_elSaveBtn : null,
72988
72989
72990
72991
72992
72993
72994
72995
72996/////////////////////////////////////////////////////////////////////////////
72997//
72998// Private methods
72999//
73000/////////////////////////////////////////////////////////////////////////////
73001
73002/**
73003 * Initialize configs.
73004 *
73005 * @method _initConfigs
73006 * @private   
73007 */
73008_initConfigs : function(oConfigs) {
73009    // Object literal defines CellEditor configs
73010    if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
73011        for(var sConfig in oConfigs) {
73012            if(sConfig) {
73013                this[sConfig] = oConfigs[sConfig];
73014            }
73015        }
73016    }
73017},
73018
73019/**
73020 * Initialize Custom Events.
73021 *
73022 * @method _initEvents
73023 * @private   
73024 */
73025_initEvents : function() {
73026    this.createEvent("showEvent");
73027    this.createEvent("keydownEvent");
73028    this.createEvent("invalidDataEvent");
73029    this.createEvent("revertEvent");
73030    this.createEvent("saveEvent");
73031    this.createEvent("cancelEvent");
73032    this.createEvent("blurEvent");
73033    this.createEvent("blockEvent");
73034    this.createEvent("unblockEvent");
73035},
73036
73037/**
73038 * Initialize container element.
73039 *
73040 * @method _initContainerEl
73041 * @private
73042 */
73043_initContainerEl : function() {
73044    if(this._elContainer) {
73045        YAHOO.util.Event.purgeElement(this._elContainer, true);
73046        this._elContainer.innerHTML = "";
73047    }
73048
73049    var elContainer = document.createElement("div");
73050    elContainer.id = this.getId() + "-container"; // Needed for tracking blur event
73051    elContainer.style.display = "none";
73052    elContainer.tabIndex = 0;
73053    
73054    this.className = lang.isArray(this.className) ? this.className : this.className ? [this.className] : [];
73055    this.className[this.className.length] = DT.CLASS_EDITOR;
73056    elContainer.className = this.className.join(" ");
73057    
73058    document.body.insertBefore(elContainer, document.body.firstChild);
73059    this._elContainer = elContainer;
73060},
73061
73062/**
73063 * Initialize container shim element.
73064 *
73065 * @method _initShimEl
73066 * @private
73067 */
73068_initShimEl : function() {
73069    // Iframe shim
73070    if(this.useIFrame) {
73071        if(!this._elIFrame) {
73072            var elIFrame = document.createElement("iframe");
73073            elIFrame.src = "javascript:false";
73074            elIFrame.frameBorder = 0;
73075            elIFrame.scrolling = "no";
73076            elIFrame.style.display = "none";
73077            elIFrame.className = DT.CLASS_EDITOR_SHIM;
73078            elIFrame.tabIndex = -1;
73079            elIFrame.role = "presentation";
73080            elIFrame.title = "Presentational iframe shim";
73081            document.body.insertBefore(elIFrame, document.body.firstChild);
73082            this._elIFrame = elIFrame;
73083        }
73084    }
73085},
73086
73087/**
73088 * Hides CellEditor UI at end of interaction.
73089 *
73090 * @method _hide
73091 */
73092_hide : function() {
73093    this.getContainerEl().style.display = "none";
73094    if(this._elIFrame) {
73095        this._elIFrame.style.display = "none";
73096    }
73097    this.isActive = false;
73098    this.getDataTable()._oCellEditor =  null;
73099},
73100
73101
73102
73103
73104
73105
73106
73107
73108
73109
73110
73111/////////////////////////////////////////////////////////////////////////////
73112//
73113// Public properties
73114//
73115/////////////////////////////////////////////////////////////////////////////
73116/**
73117 * Implementer defined function that can submit the input value to a server. This
73118 * function must accept the arguments fnCallback and oNewValue. When the submission
73119 * is complete, the function must also call fnCallback(bSuccess, oNewValue) to 
73120 * finish the save routine in the CellEditor. This function can also be used to 
73121 * perform extra validation or input value manipulation. 
73122 *
73123 * @property asyncSubmitter
73124 * @type HTMLFunction
73125 */
73126asyncSubmitter : null,
73127
73128/**
73129 * Current value.
73130 *
73131 * @property value
73132 * @type MIXED
73133 */
73134value : null,
73135
73136/**
73137 * Default value in case Record data is undefined. NB: Null values will not trigger
73138 * the default value.
73139 *
73140 * @property defaultValue
73141 * @type MIXED
73142 * @default null
73143 */
73144defaultValue : null,
73145
73146/**
73147 * Validator function for input data, called from the DataTable instance scope,
73148 * receives the arguments (inputValue, currentValue, editorInstance) and returns
73149 * either the validated (or type-converted) value or undefined.
73150 *
73151 * @property validator
73152 * @type HTMLFunction
73153 * @default null
73154 */
73155validator : null,
73156
73157/**
73158 * If validation is enabled, resets input field of invalid data.
73159 *
73160 * @property resetInvalidData
73161 * @type Boolean
73162 * @default true
73163 */
73164resetInvalidData : true,
73165
73166/**
73167 * True if currently active.
73168 *
73169 * @property isActive
73170 * @type Boolean
73171 */
73172isActive : false,
73173
73174/**
73175 * Text to display on Save button.
73176 *
73177 * @property LABEL_SAVE
73178 * @type HTML
73179 * @default "Save"
73180 */
73181LABEL_SAVE : "Save",
73182
73183/**
73184 * Text to display on Cancel button.
73185 *
73186 * @property LABEL_CANCEL
73187 * @type HTML
73188 * @default "Cancel"
73189 */
73190LABEL_CANCEL : "Cancel",
73191
73192/**
73193 * True if Save/Cancel buttons should not be displayed in the CellEditor.
73194 *
73195 * @property disableBtns
73196 * @type Boolean
73197 * @default false
73198 */
73199disableBtns : false,
73200
73201/**
73202 * True if iframe shim for container element should be enabled.
73203 *
73204 * @property useIFrame
73205 * @type Boolean
73206 * @default false
73207 */
73208useIFrame : false,
73209
73210/**
73211 * Custom CSS class or array of classes applied to the container element.
73212 *
73213 * @property className
73214 * @type String || String[]
73215 */
73216className : null,
73217
73218
73219
73220
73221
73222/////////////////////////////////////////////////////////////////////////////
73223//
73224// Public methods
73225//
73226/////////////////////////////////////////////////////////////////////////////
73227/**
73228 * CellEditor instance name, for logging.
73229 *
73230 * @method toString
73231 * @return {String} Unique name of the CellEditor instance.
73232 */
73233
73234toString : function() {
73235    return "CellEditor instance " + this._sId;
73236},
73237
73238/**
73239 * CellEditor unique ID.
73240 *
73241 * @method getId
73242 * @return {String} Unique ID of the CellEditor instance.
73243 */
73244
73245getId : function() {
73246    return this._sId;
73247},
73248
73249/**
73250 * Returns reference to associated DataTable instance.
73251 *
73252 * @method getDataTable
73253 * @return {YAHOO.widget.DataTable} DataTable instance.
73254 */
73255
73256getDataTable : function() {
73257    return this._oDataTable;
73258},
73259
73260/**
73261 * Returns reference to associated Column instance.
73262 *
73263 * @method getColumn
73264 * @return {YAHOO.widget.Column} Column instance.
73265 */
73266
73267getColumn : function() {
73268    return this._oColumn;
73269},
73270
73271/**
73272 * Returns reference to associated Record instance.
73273 *
73274 * @method getRecord
73275 * @return {YAHOO.widget.Record} Record instance.
73276 */
73277
73278getRecord : function() {
73279    return this._oRecord;
73280},
73281
73282
73283
73284/**
73285 * Returns reference to associated TD element.
73286 *
73287 * @method getTdEl
73288 * @return {HTMLElement} TD element.
73289 */
73290
73291getTdEl : function() {
73292    return this._elTd;
73293},
73294
73295/**
73296 * Returns container element.
73297 *
73298 * @method getContainerEl
73299 * @return {HTMLElement} Reference to container element.
73300 */
73301
73302getContainerEl : function() {
73303    return this._elContainer;
73304},
73305
73306/**
73307 * Nulls out the entire CellEditor instance and related objects, removes attached
73308 * event listeners, and clears out DOM elements inside the container, removes
73309 * container from the DOM.
73310 *
73311 * @method destroy
73312 */
73313destroy : function() {
73314    this.unsubscribeAll();
73315    
73316    // Column is late-binding in attach()
73317    var oColumn = this.getColumn();
73318    if(oColumn) {
73319        oColumn.editor = null;
73320    }
73321    
73322    var elContainer = this.getContainerEl();
73323    if (elContainer) {
73324        Ev.purgeElement(elContainer, true);
73325        elContainer.parentNode.removeChild(elContainer);
73326    }
73327},
73328
73329/**
73330 * Renders DOM elements and attaches event listeners.
73331 *
73332 * @method render
73333 */
73334render : function() {
73335    if (!this._needsRender) {
73336        return;
73337    }
73338
73339    this._initContainerEl();
73340    this._initShimEl();
73341
73342    // Handle ESC key
73343    Ev.addListener(this.getContainerEl(), "keydown", function(e, oSelf) {
73344        // ESC cancels Cell Editor
73345        if((e.keyCode == 27)) {
73346            var target = Ev.getTarget(e);
73347            // workaround for Mac FF3 bug that disabled clicks when ESC hit when
73348            // select is open. [bug 2273056]
73349            if (target.nodeName && target.nodeName.toLowerCase() === 'select') {
73350                target.blur();
73351            }
73352            oSelf.cancel();
73353        }
73354        // Pass through event
73355        oSelf.fireEvent("keydownEvent", {editor:oSelf, event:e});
73356    }, this);
73357
73358    this.renderForm();
73359
73360    // Show Save/Cancel buttons
73361    if(!this.disableBtns) {
73362        this.renderBtns();
73363    }
73364    
73365    this.doAfterRender();
73366    this._needsRender = false;
73367},
73368
73369/**
73370 * Renders Save/Cancel buttons.
73371 *
73372 * @method renderBtns
73373 */
73374renderBtns : function() {
73375    // Buttons
73376    var elBtnsDiv = this.getContainerEl().appendChild(document.createElement("div"));
73377    elBtnsDiv.className = DT.CLASS_BUTTON;
73378
73379    // Save button
73380    var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
73381    elSaveBtn.className = DT.CLASS_DEFAULT;
73382    elSaveBtn.innerHTML = this.LABEL_SAVE;
73383    Ev.addListener(elSaveBtn, "click", function(oArgs) {
73384        this.save();
73385    }, this, true);
73386    this._elSaveBtn = elSaveBtn;
73387
73388    // Cancel button
73389    var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
73390    elCancelBtn.innerHTML = this.LABEL_CANCEL;
73391    Ev.addListener(elCancelBtn, "click", function(oArgs) {
73392        this.cancel();
73393    }, this, true);
73394    this._elCancelBtn = elCancelBtn;
73395},
73396
73397/**
73398 * Attach CellEditor for a new interaction.
73399 *
73400 * @method attach
73401 * @param oDataTable {YAHOO.widget.DataTable} Associated DataTable instance.
73402 * @param elCell {HTMLElement} Cell to edit.  
73403 */
73404attach : function(oDataTable, elCell) {
73405    // Validate 
73406    if(oDataTable instanceof YAHOO.widget.DataTable) {
73407        this._oDataTable = oDataTable;
73408        
73409        // Validate cell
73410        elCell = oDataTable.getTdEl(elCell);
73411        if(elCell) {
73412            this._elTd = elCell;
73413
73414            // Validate Column
73415            var oColumn = oDataTable.getColumn(elCell);
73416            if(oColumn) {
73417                this._oColumn = oColumn;
73418                
73419                // Validate Record
73420                var oRecord = oDataTable.getRecord(elCell);
73421                if(oRecord) {
73422                    this._oRecord = oRecord;
73423                    var value = oRecord.getData(this.getColumn().getField());
73424                    this.value = (value !== undefined) ? value : this.defaultValue;
73425                    return true;
73426                }
73427            }            
73428        }
73429    }
73430    YAHOO.log("Could not attach CellEditor","error",this.toString());
73431    return false;
73432},
73433
73434/**
73435 * Moves container into position for display.
73436 *
73437 * @method move
73438 */
73439move : function() {
73440    // Move Editor
73441    var elContainer = this.getContainerEl(),
73442        elTd = this.getTdEl(),
73443        x = Dom.getX(elTd),
73444        y = Dom.getY(elTd);
73445
73446    //TODO: remove scrolling logic
73447    // SF doesn't get xy for cells in scrolling table
73448    // when tbody display is set to block
73449    if(isNaN(x) || isNaN(y)) {
73450        var elTbody = this.getDataTable().getTbodyEl();
73451        x = elTd.offsetLeft + // cell pos relative to table
73452                Dom.getX(elTbody.parentNode) - // plus table pos relative to document
73453                elTbody.scrollLeft; // minus tbody scroll
73454        y = elTd.offsetTop + // cell pos relative to table
73455                Dom.getY(elTbody.parentNode) - // plus table pos relative to document
73456                elTbody.scrollTop + // minus tbody scroll
73457                this.getDataTable().getTheadEl().offsetHeight; // account for fixed THEAD cells
73458    }
73459
73460    elContainer.style.left = x + "px";
73461    elContainer.style.top = y + "px";
73462
73463    if(this._elIFrame) {
73464        this._elIFrame.style.left = x + "px";
73465        this._elIFrame.style.top = y + "px";
73466    }
73467},
73468
73469/**
73470 * Displays CellEditor UI in the correct position.
73471 *
73472 * @method show
73473 */
73474show : function() {
73475    var elContainer = this.getContainerEl(),
73476        elIFrame = this._elIFrame;
73477    this.resetForm();
73478    this.isActive = true;
73479    elContainer.style.display = "";
73480    if(elIFrame) {
73481        elIFrame.style.width = elContainer.offsetWidth + "px";
73482        elIFrame.style.height = elContainer.offsetHeight + "px";
73483        elIFrame.style.display = "";
73484    }
73485    this.focus();
73486    this.fireEvent("showEvent", {editor:this});
73487    YAHOO.log("CellEditor shown", "info", this.toString()); 
73488},
73489
73490/**
73491 * Fires blockEvent
73492 *
73493 * @method block
73494 */
73495block : function() {
73496    this.fireEvent("blockEvent", {editor:this});
73497    YAHOO.log("CellEditor blocked", "info", this.toString()); 
73498},
73499
73500/**
73501 * Fires unblockEvent
73502 *
73503 * @method unblock
73504 */
73505unblock : function() {
73506    this.fireEvent("unblockEvent", {editor:this});
73507    YAHOO.log("CellEditor unblocked", "info", this.toString()); 
73508},
73509
73510/**
73511 * Saves value of CellEditor and hides UI.
73512 *
73513 * @method save
73514 */
73515save : function() {
73516    // Get new value
73517    var inputValue = this.getInputValue();
73518    var validValue = inputValue;
73519    
73520    // Validate new value
73521    if(this.validator) {
73522        validValue = this.validator.call(this.getDataTable(), inputValue, this.value, this);
73523        if(validValue === undefined ) {
73524            if(this.resetInvalidData) {
73525                this.resetForm();
73526            }
73527            this.fireEvent("invalidDataEvent",
73528                    {editor:this, oldData:this.value, newData:inputValue});
73529            YAHOO.log("Could not save Cell Editor input due to invalid data " +
73530                    lang.dump(inputValue), "warn", this.toString());
73531            return;
73532        }
73533    }
73534        
73535    var oSelf = this;
73536    var finishSave = function(bSuccess, oNewValue) {
73537        var oOrigValue = oSelf.value;
73538        if(bSuccess) {
73539            // Update new value
73540            oSelf.value = oNewValue;
73541            oSelf.getDataTable().updateCell(oSelf.getRecord(), oSelf.getColumn(), oNewValue);
73542            
73543            // Hide CellEditor
73544            oSelf._hide();
73545            
73546            oSelf.fireEvent("saveEvent",
73547                    {editor:oSelf, oldData:oOrigValue, newData:oSelf.value});
73548            YAHOO.log("Cell Editor input saved", "info", this.toString());
73549        }
73550        else {
73551            oSelf.resetForm();
73552            oSelf.fireEvent("revertEvent",
73553                    {editor:oSelf, oldData:oOrigValue, newData:oNewValue});
73554            YAHOO.log("Could not save Cell Editor input " +
73555                    lang.dump(oNewValue), "warn", oSelf.toString());
73556        }
73557        oSelf.unblock();
73558    };
73559    
73560    this.block();
73561    if(lang.isFunction(this.asyncSubmitter)) {
73562        this.asyncSubmitter.call(this, finishSave, validValue);
73563    } 
73564    else {   
73565        finishSave(true, validValue);
73566    }
73567},
73568
73569/**
73570 * Cancels CellEditor input and hides UI.
73571 *
73572 * @method cancel
73573 */
73574cancel : function() {
73575    if(this.isActive) {
73576        this._hide();
73577        this.fireEvent("cancelEvent", {editor:this});
73578        YAHOO.log("CellEditor canceled", "info", this.toString());
73579    }
73580    else {
73581        YAHOO.log("Unable to cancel CellEditor", "warn", this.toString());
73582    }
73583},
73584
73585/**
73586 * Renders form elements.
73587 *
73588 * @method renderForm
73589 */
73590renderForm : function() {
73591    // To be implemented by subclass
73592},
73593
73594/**
73595 * Access to add additional event listeners.
73596 *
73597 * @method doAfterRender
73598 */
73599doAfterRender : function() {
73600    // To be implemented by subclass
73601},
73602
73603
73604/**
73605 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
73606 * to save input without them. 
73607 *
73608 * @method handleDisabledBtns
73609 */
73610handleDisabledBtns : function() {
73611    // To be implemented by subclass
73612},
73613
73614/**
73615 * Resets CellEditor UI to initial state.
73616 *
73617 * @method resetForm
73618 */
73619resetForm : function() {
73620    // To be implemented by subclass
73621},
73622
73623/**
73624 * Sets focus in CellEditor.
73625 *
73626 * @method focus
73627 */
73628focus : function() {
73629    // To be implemented by subclass
73630},
73631
73632/**
73633 * Retrieves input value from CellEditor.
73634 *
73635 * @method getInputValue
73636 */
73637getInputValue : function() {
73638    // To be implemented by subclass
73639}
73640
73641};
73642
73643lang.augmentProto(BCE, util.EventProvider);
73644
73645
73646/////////////////////////////////////////////////////////////////////////////
73647//
73648// Custom Events
73649//
73650/////////////////////////////////////////////////////////////////////////////
73651
73652/**
73653 * Fired when a CellEditor is shown.
73654 *
73655 * @event showEvent
73656 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
73657 */
73658
73659/**
73660 * Fired when a CellEditor has a keydown.
73661 *
73662 * @event keydownEvent
73663 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
73664 * @param oArgs.event {HTMLEvent} The event object.
73665 */
73666
73667/**
73668 * Fired when a CellEditor input is reverted due to invalid data.
73669 *
73670 * @event invalidDataEvent
73671 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
73672 * @param oArgs.newData {Object} New data value from form input field.
73673 * @param oArgs.oldData {Object} Old data value.
73674 */
73675
73676/**
73677 * Fired when a CellEditor input is reverted due to asyncSubmitter failure.
73678 *
73679 * @event revertEvent
73680 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
73681 * @param oArgs.newData {Object} New data value from form input field.
73682 * @param oArgs.oldData {Object} Old data value.
73683 */
73684
73685/**
73686 * Fired when a CellEditor input is saved.
73687 *
73688 * @event saveEvent
73689 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
73690 * @param oArgs.newData {Object} New data value from form input field.
73691 * @param oArgs.oldData {Object} Old data value.
73692 */
73693
73694/**
73695 * Fired when a CellEditor input is canceled.
73696 *
73697 * @event cancelEvent
73698 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
73699 */
73700
73701/**
73702 * Fired when a CellEditor has a blur event.
73703 *
73704 * @event blurEvent
73705 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 
73706 */
73707
73708
73709
73710
73711
73712
73713
73714
73715
73716
73717
73718
73719
73720
73721/****************************************************************************/
73722/****************************************************************************/
73723/****************************************************************************/
73724    
73725/**
73726 * The CheckboxCellEditor class provides functionality for inline editing
73727 * DataTable cell data with checkboxes.
73728 *
73729 * @namespace YAHOO.widget
73730 * @class CheckboxCellEditor
73731 * @extends YAHOO.widget.BaseCellEditor
73732 * @constructor
73733 * @param oConfigs {Object} (Optional) Object literal of configs.
73734 */
73735widget.CheckboxCellEditor = function(oConfigs) {
73736    oConfigs = oConfigs || {};
73737    this._sId = this._sId || Dom.generateId(null, "yui-checkboxceditor"); // "yui-checkboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
73738    YAHOO.widget.BaseCellEditor._nCount++;
73739    widget.CheckboxCellEditor.superclass.constructor.call(this, oConfigs.type || "checkbox", oConfigs);
73740};
73741
73742// CheckboxCellEditor extends BaseCellEditor
73743lang.extend(widget.CheckboxCellEditor, BCE, {
73744
73745/////////////////////////////////////////////////////////////////////////////
73746//
73747// CheckboxCellEditor public properties
73748//
73749/////////////////////////////////////////////////////////////////////////////
73750/**
73751 * Array of checkbox values. Can either be a simple array (e.g., ["red","green","blue"])
73752 * or a an array of objects (e.g., [{label:"red", value:"#FF0000"},
73753 * {label:"green", value:"#00FF00"}, {label:"blue", value:"#0000FF"}]). String
73754 * values are treated as markup and inserted into the DOM as innerHTML.
73755 *
73756 * @property checkboxOptions
73757 * @type HTML[] | Object[]
73758 */
73759checkboxOptions : null,
73760
73761/**
73762 * Reference to the checkbox elements.
73763 *
73764 * @property checkboxes
73765 * @type HTMLElement[] 
73766 */
73767checkboxes : null,
73768
73769/**
73770 * Array of checked values
73771 *
73772 * @property value
73773 * @type String[] 
73774 */
73775value : null,
73776
73777/////////////////////////////////////////////////////////////////////////////
73778//
73779// CheckboxCellEditor public methods
73780//
73781/////////////////////////////////////////////////////////////////////////////
73782
73783/**
73784 * Render a form with input(s) type=checkbox.
73785 *
73786 * @method renderForm
73787 */
73788renderForm : function() {
73789    if(lang.isArray(this.checkboxOptions)) {
73790        var checkboxOption, checkboxValue, checkboxId, elLabel, j, len;
73791        
73792        // Create the checkbox buttons in an IE-friendly way...
73793        for(j=0,len=this.checkboxOptions.length; j<len; j++) {
73794            checkboxOption = this.checkboxOptions[j];
73795            checkboxValue = lang.isValue(checkboxOption.value) ?
73796                    checkboxOption.value : checkboxOption;
73797
73798            checkboxId = this.getId() + "-chk" + j;
73799            this.getContainerEl().innerHTML += "<input type=\"checkbox\"" +
73800                    " id=\"" + checkboxId + "\"" + // Needed for label
73801                    " value=\"" + checkboxValue + "\" />";
73802            
73803            // Create the labels in an IE-friendly way
73804            elLabel = this.getContainerEl().appendChild(document.createElement("label"));
73805            elLabel.htmlFor = checkboxId;
73806            elLabel.innerHTML = lang.isValue(checkboxOption.label) ?
73807                    checkboxOption.label : checkboxOption;
73808        }
73809        
73810        // Store the reference to the checkbox elements
73811        var allCheckboxes = [];
73812        for(j=0; j<len; j++) {
73813            allCheckboxes[allCheckboxes.length] = this.getContainerEl().childNodes[j*2];
73814        }
73815        this.checkboxes = allCheckboxes;
73816
73817        if(this.disableBtns) {
73818            this.handleDisabledBtns();
73819        }
73820    }
73821    else {
73822        YAHOO.log("Could not find checkboxOptions", "error", this.toString());
73823    }
73824},
73825
73826/**
73827 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
73828 * to save input without them. 
73829 *
73830 * @method handleDisabledBtns
73831 */
73832handleDisabledBtns : function() {
73833    Ev.addListener(this.getContainerEl(), "click", function(v){
73834        if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
73835            // Save on blur
73836            this.save();
73837        }
73838    }, this, true);
73839},
73840
73841/**
73842 * Resets CheckboxCellEditor UI to initial state.
73843 *
73844 * @method resetForm
73845 */
73846resetForm : function() {
73847    // Normalize to array
73848    var originalValues = lang.isArray(this.value) ? this.value : [this.value];
73849    
73850    // Match checks to value
73851    for(var i=0, j=this.checkboxes.length; i<j; i++) {
73852        this.checkboxes[i].checked = false;
73853        for(var k=0, len=originalValues.length; k<len; k++) {
73854            if(this.checkboxes[i].value == originalValues[k]) {
73855                this.checkboxes[i].checked = true;
73856            }
73857        }
73858    }
73859},
73860
73861/**
73862 * Sets focus in CheckboxCellEditor.
73863 *
73864 * @method focus
73865 */
73866focus : function() {
73867    this.checkboxes[0].focus();
73868},
73869
73870/**
73871 * Retrieves input value from CheckboxCellEditor.
73872 *
73873 * @method getInputValue
73874 */
73875getInputValue : function() {
73876    var checkedValues = [];
73877    for(var i=0, j=this.checkboxes.length; i<j; i++) {
73878        if(this.checkboxes[i].checked) {
73879            checkedValues[checkedValues.length] = this.checkboxes[i].value;
73880        }
73881    }  
73882    return checkedValues;
73883}
73884
73885});
73886
73887// Copy static members to CheckboxCellEditor class
73888lang.augmentObject(widget.CheckboxCellEditor, BCE);
73889
73890
73891
73892
73893
73894
73895
73896
73897/****************************************************************************/
73898/****************************************************************************/
73899/****************************************************************************/
73900    
73901/**
73902 * The DataCellEditor class provides functionality for inline editing
73903 * DataTable cell data with a YUI Calendar.
73904 *
73905 * @namespace YAHOO.widget
73906 * @class DateCellEditor
73907 * @extends YAHOO.widget.BaseCellEditor 
73908 * @constructor
73909 * @param oConfigs {Object} (Optional) Object literal of configs.
73910 */
73911widget.DateCellEditor = function(oConfigs) {
73912    oConfigs = oConfigs || {};
73913    this._sId = this._sId || Dom.generateId(null, "yui-dateceditor"); // "yui-dateceditor" + YAHOO.widget.BaseCellEditor._nCount++;
73914    YAHOO.widget.BaseCellEditor._nCount++;
73915    widget.DateCellEditor.superclass.constructor.call(this, oConfigs.type || "date", oConfigs);
73916};
73917
73918// CheckboxCellEditor extends BaseCellEditor
73919lang.extend(widget.DateCellEditor, BCE, {
73920
73921/////////////////////////////////////////////////////////////////////////////
73922//
73923// DateCellEditor public properties
73924//
73925/////////////////////////////////////////////////////////////////////////////
73926/**
73927 * Reference to Calendar instance.
73928 *
73929 * @property calendar
73930 * @type YAHOO.widget.Calendar
73931 */
73932calendar : null,
73933
73934/**
73935 * Configs for the calendar instance, to be passed to Calendar constructor.
73936 *
73937 * @property calendarOptions
73938 * @type Object
73939 */
73940calendarOptions : null,
73941
73942/**
73943 * Default value.
73944 *
73945 * @property defaultValue
73946 * @type Date
73947 * @default new Date()
73948 */
73949defaultValue : new Date(),
73950
73951
73952/////////////////////////////////////////////////////////////////////////////
73953//
73954// DateCellEditor public methods
73955//
73956/////////////////////////////////////////////////////////////////////////////
73957
73958/**
73959 * Render a Calendar.
73960 *
73961 * @method renderForm
73962 */
73963renderForm : function() {
73964    // Calendar widget
73965    if(YAHOO.widget.Calendar) {
73966        var calContainer = this.getContainerEl().appendChild(document.createElement("div"));
73967        calContainer.id = this.getId() + "-dateContainer"; // Needed for Calendar constructor
73968        var calendar =
73969                new YAHOO.widget.Calendar(this.getId() + "-date",
73970                calContainer.id, this.calendarOptions);
73971        calendar.render();
73972        calContainer.style.cssFloat = "none";
73973        
73974        // Bug 2528576
73975        calendar.hideEvent.subscribe(function() {this.cancel();}, this, true);
73976
73977        if(ua.ie) {
73978            var calFloatClearer = this.getContainerEl().appendChild(document.createElement("div"));
73979            calFloatClearer.style.clear = "both";
73980        }
73981        
73982        this.calendar = calendar;
73983
73984        if(this.disableBtns) {
73985            this.handleDisabledBtns();
73986        }
73987    }
73988    else {
73989        YAHOO.log("Could not find YUI Calendar", "error", this.toString());
73990    }
73991    
73992},
73993
73994/**
73995 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
73996 * to save input without them. 
73997 *
73998 * @method handleDisabledBtns
73999 */
74000handleDisabledBtns : function() {
74001    this.calendar.selectEvent.subscribe(function(v){
74002        // Save on select
74003        this.save();
74004    }, this, true);
74005},
74006
74007/**
74008 * Resets DateCellEditor UI to initial state.
74009 *
74010 * @method resetForm
74011 */
74012resetForm : function() {
74013    var value = this.value || (new Date());
74014    this.calendar.select(value);
74015    this.calendar.cfg.setProperty("pagedate",value,false);
74016	this.calendar.render();
74017	// Bug 2528576
74018	this.calendar.show();
74019},
74020
74021/**
74022 * Sets focus in DateCellEditor.
74023 *
74024 * @method focus
74025 */
74026focus : function() {
74027    // To be impmlemented by subclass
74028},
74029
74030/**
74031 * Retrieves input value from DateCellEditor.
74032 *
74033 * @method getInputValue
74034 */
74035getInputValue : function() {
74036    return this.calendar.getSelectedDates()[0];
74037}
74038
74039});
74040
74041// Copy static members to DateCellEditor class
74042lang.augmentObject(widget.DateCellEditor, BCE);
74043
74044
74045
74046
74047
74048
74049
74050
74051
74052/****************************************************************************/
74053/****************************************************************************/
74054/****************************************************************************/
74055    
74056/**
74057 * The DropdownCellEditor class provides functionality for inline editing
74058 * DataTable cell data a SELECT element.
74059 *
74060 * @namespace YAHOO.widget
74061 * @class DropdownCellEditor
74062 * @extends YAHOO.widget.BaseCellEditor 
74063 * @constructor
74064 * @param oConfigs {Object} (Optional) Object literal of configs.
74065 */
74066widget.DropdownCellEditor = function(oConfigs) {
74067    oConfigs = oConfigs || {};
74068    this._sId = this._sId || Dom.generateId(null, "yui-dropdownceditor"); // "yui-dropdownceditor" + YAHOO.widget.BaseCellEditor._nCount++;
74069    YAHOO.widget.BaseCellEditor._nCount++;
74070    widget.DropdownCellEditor.superclass.constructor.call(this, oConfigs.type || "dropdown", oConfigs);
74071};
74072
74073// DropdownCellEditor extends BaseCellEditor
74074lang.extend(widget.DropdownCellEditor, BCE, {
74075
74076/////////////////////////////////////////////////////////////////////////////
74077//
74078// DropdownCellEditor public properties
74079//
74080/////////////////////////////////////////////////////////////////////////////
74081/**
74082 * Array of dropdown values. Can either be a simple array (e.g.,
74083 * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g., 
74084 * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
74085 * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]). String
74086 * values are treated as markup and inserted into the DOM as innerHTML.
74087 *
74088 * @property dropdownOptions
74089 * @type HTML[] | Object[]
74090 */
74091dropdownOptions : null,
74092
74093/**
74094 * Reference to Dropdown element.
74095 *
74096 * @property dropdown
74097 * @type HTMLElement
74098 */
74099dropdown : null,
74100
74101/**
74102 * Enables multi-select.
74103 *
74104 * @property multiple
74105 * @type Boolean
74106 */
74107multiple : false,
74108
74109/**
74110 * Specifies number of visible options.
74111 *
74112 * @property size
74113 * @type Number
74114 */
74115size : null,
74116
74117/////////////////////////////////////////////////////////////////////////////
74118//
74119// DropdownCellEditor public methods
74120//
74121/////////////////////////////////////////////////////////////////////////////
74122
74123/**
74124 * Render a form with select element.
74125 *
74126 * @method renderForm
74127 */
74128renderForm : function() {
74129    var elDropdown = this.getContainerEl().appendChild(document.createElement("select"));
74130    elDropdown.style.zoom = 1;
74131    if(this.multiple) {
74132        elDropdown.multiple = "multiple";
74133    }
74134    if(lang.isNumber(this.size)) {
74135        elDropdown.size = this.size;
74136    }
74137    this.dropdown = elDropdown;
74138    
74139    if(lang.isArray(this.dropdownOptions)) {
74140        var dropdownOption, elOption;
74141        for(var i=0, j=this.dropdownOptions.length; i<j; i++) {
74142            dropdownOption = this.dropdownOptions[i];
74143            elOption = document.createElement("option");
74144            elOption.value = (lang.isValue(dropdownOption.value)) ?
74145                    dropdownOption.value : dropdownOption;
74146            elOption.innerHTML = (lang.isValue(dropdownOption.label)) ?
74147                    dropdownOption.label : dropdownOption;
74148            elOption = elDropdown.appendChild(elOption);
74149        }
74150        
74151        if(this.disableBtns) {
74152            this.handleDisabledBtns();
74153        }
74154    }
74155},
74156
74157/**
74158 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
74159 * to save input without them. 
74160 *
74161 * @method handleDisabledBtns
74162 */
74163handleDisabledBtns : function() {
74164    // Save on blur for multi-select
74165    if(this.multiple) {
74166        Ev.addListener(this.dropdown, "blur", function(v){
74167            // Save on change
74168            this.save();
74169        }, this, true);
74170    }
74171    // Save on change for single-select
74172    else {
74173        if(!ua.ie) {
74174            Ev.addListener(this.dropdown, "change", function(v){
74175                // Save on change
74176                this.save();
74177            }, this, true);
74178        }
74179        else {
74180            // Bug 2529274: "change" event is not keyboard accessible in IE6
74181            Ev.addListener(this.dropdown, "blur", function(v){
74182                this.save();
74183            }, this, true);
74184            Ev.addListener(this.dropdown, "click", function(v){
74185                this.save();
74186            }, this, true);
74187        }
74188    }
74189},
74190
74191/**
74192 * Resets DropdownCellEditor UI to initial state.
74193 *
74194 * @method resetForm
74195 */
74196resetForm : function() {
74197    var allOptions = this.dropdown.options,
74198        i=0, j=allOptions.length;
74199
74200    // Look for multi-select selections
74201    if(lang.isArray(this.value)) {
74202        var allValues = this.value,
74203            m=0, n=allValues.length,
74204            hash = {};
74205        // Reset all selections and stash options in a value hash
74206        for(; i<j; i++) {
74207            allOptions[i].selected = false;
74208            hash[allOptions[i].value] = allOptions[i];
74209        }
74210        for(; m<n; m++) {
74211            if(hash[allValues[m]]) {
74212                hash[allValues[m]].selected = true;
74213            }
74214        }
74215    }
74216    // Only need to look for a single selection
74217    else {
74218        for(; i<j; i++) {
74219            if(this.value == allOptions[i].value) {
74220                allOptions[i].selected = true;
74221            }
74222        }
74223    }
74224},
74225
74226/**
74227 * Sets focus in DropdownCellEditor.
74228 *
74229 * @method focus
74230 */
74231focus : function() {
74232    this.getDataTable()._focusEl(this.dropdown);
74233},
74234
74235/**
74236 * Retrieves input value from DropdownCellEditor.
74237 *
74238 * @method getInputValue
74239 */
74240getInputValue : function() {
74241    var allOptions = this.dropdown.options;
74242    
74243    // Look for multiple selections
74244    if(this.multiple) {
74245        var values = [],
74246            i=0, j=allOptions.length;
74247        for(; i<j; i++) {
74248            if(allOptions[i].selected) {
74249                values.push(allOptions[i].value);
74250            }
74251        }
74252        return values;
74253    }
74254    // Only need to look for single selection
74255    else {
74256        return allOptions[allOptions.selectedIndex].value;
74257    }
74258}
74259
74260});
74261
74262// Copy static members to DropdownCellEditor class
74263lang.augmentObject(widget.DropdownCellEditor, BCE);
74264
74265
74266
74267
74268
74269
74270/****************************************************************************/
74271/****************************************************************************/
74272/****************************************************************************/
74273    
74274/**
74275 * The RadioCellEditor class provides functionality for inline editing
74276 * DataTable cell data with radio buttons.
74277 *
74278 * @namespace YAHOO.widget
74279 * @class RadioCellEditor
74280 * @extends YAHOO.widget.BaseCellEditor 
74281 * @constructor
74282 * @param oConfigs {Object} (Optional) Object literal of configs.
74283 */
74284widget.RadioCellEditor = function(oConfigs) {
74285    oConfigs = oConfigs || {};
74286    this._sId = this._sId || Dom.generateId(null, "yui-radioceditor"); // "yui-radioceditor" + YAHOO.widget.BaseCellEditor._nCount++;
74287    YAHOO.widget.BaseCellEditor._nCount++;
74288    widget.RadioCellEditor.superclass.constructor.call(this, oConfigs.type || "radio", oConfigs);
74289};
74290
74291// RadioCellEditor extends BaseCellEditor
74292lang.extend(widget.RadioCellEditor, BCE, {
74293
74294/////////////////////////////////////////////////////////////////////////////
74295//
74296// RadioCellEditor public properties
74297//
74298/////////////////////////////////////////////////////////////////////////////
74299/**
74300 * Reference to radio elements.
74301 *
74302 * @property radios
74303 * @type HTMLElement[]
74304 */
74305radios : null,
74306
74307/**
74308 * Array of radio values. Can either be a simple array (e.g., ["yes","no","maybe"])
74309 * or a an array of objects (e.g., [{label:"yes", value:1}, {label:"no", value:-1},
74310 * {label:"maybe", value:0}]). String values are treated as markup and inserted
74311 * into the DOM as innerHTML.
74312 *
74313 * @property radioOptions
74314 * @type HTML[] | Object[]
74315 */
74316radioOptions : null,
74317
74318/////////////////////////////////////////////////////////////////////////////
74319//
74320// RadioCellEditor public methods
74321//
74322/////////////////////////////////////////////////////////////////////////////
74323
74324/**
74325 * Render a form with input(s) type=radio.
74326 *
74327 * @method renderForm
74328 */
74329renderForm : function() {
74330    if(lang.isArray(this.radioOptions)) {
74331        var radioOption, radioValue, radioId, elLabel;
74332        
74333        // Create the radio buttons in an IE-friendly way
74334        for(var i=0, len=this.radioOptions.length; i<len; i++) {
74335            radioOption = this.radioOptions[i];
74336            radioValue = lang.isValue(radioOption.value) ?
74337                    radioOption.value : radioOption;
74338            radioId = this.getId() + "-radio" + i;
74339            this.getContainerEl().innerHTML += "<input type=\"radio\"" +
74340                    " name=\"" + this.getId() + "\"" +
74341                    " value=\"" + radioValue + "\"" +
74342                    " id=\"" +  radioId + "\" />"; // Needed for label
74343            
74344            // Create the labels in an IE-friendly way
74345            elLabel = this.getContainerEl().appendChild(document.createElement("label"));
74346            elLabel.htmlFor = radioId;
74347            elLabel.innerHTML = (lang.isValue(radioOption.label)) ?
74348                    radioOption.label : radioOption;
74349        }
74350        
74351        // Store the reference to the checkbox elements
74352        var allRadios = [],
74353            elRadio;
74354        for(var j=0; j<len; j++) {
74355            elRadio = this.getContainerEl().childNodes[j*2];
74356            allRadios[allRadios.length] = elRadio;
74357        }
74358        this.radios = allRadios;
74359
74360        if(this.disableBtns) {
74361            this.handleDisabledBtns();
74362        }
74363    }
74364    else {
74365        YAHOO.log("Could not find radioOptions", "error", this.toString());
74366    }
74367},
74368
74369/**
74370 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
74371 * to save input without them. 
74372 *
74373 * @method handleDisabledBtns
74374 */
74375handleDisabledBtns : function() {
74376    Ev.addListener(this.getContainerEl(), "click", function(v){
74377        if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
74378            // Save on blur
74379            this.save();
74380        }
74381    }, this, true);
74382},
74383
74384/**
74385 * Resets RadioCellEditor UI to initial state.
74386 *
74387 * @method resetForm
74388 */
74389resetForm : function() {
74390    for(var i=0, j=this.radios.length; i<j; i++) {
74391        var elRadio = this.radios[i];
74392        if(this.value == elRadio.value) {
74393            elRadio.checked = true;
74394            return;
74395        }
74396    }
74397},
74398
74399/**
74400 * Sets focus in RadioCellEditor.
74401 *
74402 * @method focus
74403 */
74404focus : function() {
74405    for(var i=0, j=this.radios.length; i<j; i++) {
74406        if(this.radios[i].checked) {
74407            this.radios[i].focus();
74408            return;
74409        }
74410    }
74411},
74412
74413/**
74414 * Retrieves input value from RadioCellEditor.
74415 *
74416 * @method getInputValue
74417 */
74418getInputValue : function() {
74419    for(var i=0, j=this.radios.length; i<j; i++) {
74420        if(this.radios[i].checked) {
74421            return this.radios[i].value;
74422        }
74423    }
74424}
74425
74426});
74427
74428// Copy static members to RadioCellEditor class
74429lang.augmentObject(widget.RadioCellEditor, BCE);
74430
74431
74432
74433
74434
74435
74436/****************************************************************************/
74437/****************************************************************************/
74438/****************************************************************************/
74439    
74440/**
74441 * The TextareaCellEditor class provides functionality for inline editing
74442 * DataTable cell data with a TEXTAREA element.
74443 *
74444 * @namespace YAHOO.widget
74445 * @class TextareaCellEditor
74446 * @extends YAHOO.widget.BaseCellEditor 
74447 * @constructor
74448 * @param oConfigs {Object} (Optional) Object literal of configs.
74449 */
74450widget.TextareaCellEditor = function(oConfigs) {
74451    oConfigs = oConfigs || {};
74452    this._sId = this._sId || Dom.generateId(null, "yui-textareaceditor");// "yui-textareaceditor" + ;
74453    YAHOO.widget.BaseCellEditor._nCount++;
74454    widget.TextareaCellEditor.superclass.constructor.call(this, oConfigs.type || "textarea", oConfigs);
74455};
74456
74457// TextareaCellEditor extends BaseCellEditor
74458lang.extend(widget.TextareaCellEditor, BCE, {
74459
74460/////////////////////////////////////////////////////////////////////////////
74461//
74462// TextareaCellEditor public properties
74463//
74464/////////////////////////////////////////////////////////////////////////////
74465/**
74466 * Reference to textarea element.
74467 *
74468 * @property textarea
74469 * @type HTMLElement
74470 */
74471textarea : null,
74472
74473
74474/////////////////////////////////////////////////////////////////////////////
74475//
74476// TextareaCellEditor public methods
74477//
74478/////////////////////////////////////////////////////////////////////////////
74479
74480/**
74481 * Render a form with textarea.
74482 *
74483 * @method renderForm
74484 */
74485renderForm : function() {
74486    var elTextarea = this.getContainerEl().appendChild(document.createElement("textarea"));
74487    this.textarea = elTextarea;
74488
74489    if(this.disableBtns) {
74490        this.handleDisabledBtns();
74491    }
74492},
74493
74494/**
74495 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
74496 * to save input without them. 
74497 *
74498 * @method handleDisabledBtns
74499 */
74500handleDisabledBtns : function() {
74501    Ev.addListener(this.textarea, "blur", function(v){
74502        // Save on blur
74503        this.save();
74504    }, this, true);        
74505},
74506
74507/**
74508 * Moves TextareaCellEditor UI to a cell.
74509 *
74510 * @method move
74511 */
74512move : function() {
74513    this.textarea.style.width = this.getTdEl().offsetWidth + "px";
74514    this.textarea.style.height = "3em";
74515    YAHOO.widget.TextareaCellEditor.superclass.move.call(this);
74516},
74517
74518/**
74519 * Resets TextareaCellEditor UI to initial state.
74520 *
74521 * @method resetForm
74522 */
74523resetForm : function() {
74524    this.textarea.value = this.value;
74525},
74526
74527/**
74528 * Sets focus in TextareaCellEditor.
74529 *
74530 * @method focus
74531 */
74532focus : function() {
74533    // Bug 2303181, Bug 2263600
74534    this.getDataTable()._focusEl(this.textarea);
74535    this.textarea.select();
74536},
74537
74538/**
74539 * Retrieves input value from TextareaCellEditor.
74540 *
74541 * @method getInputValue
74542 */
74543getInputValue : function() {
74544    return this.textarea.value;
74545}
74546
74547});
74548
74549// Copy static members to TextareaCellEditor class
74550lang.augmentObject(widget.TextareaCellEditor, BCE);
74551
74552
74553
74554
74555
74556
74557
74558
74559
74560/****************************************************************************/
74561/****************************************************************************/
74562/****************************************************************************/
74563    
74564/**
74565 * The TextboxCellEditor class provides functionality for inline editing
74566 * DataTable cell data with an INPUT TYPE=TEXT element.
74567 *
74568 * @namespace YAHOO.widget
74569 * @class TextboxCellEditor
74570 * @extends YAHOO.widget.BaseCellEditor 
74571 * @constructor
74572 * @param oConfigs {Object} (Optional) Object literal of configs.
74573 */
74574widget.TextboxCellEditor = function(oConfigs) {
74575    oConfigs = oConfigs || {};
74576    this._sId = this._sId || Dom.generateId(null, "yui-textboxceditor");// "yui-textboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
74577    YAHOO.widget.BaseCellEditor._nCount++;
74578    widget.TextboxCellEditor.superclass.constructor.call(this, oConfigs.type || "textbox", oConfigs);
74579};
74580
74581// TextboxCellEditor extends BaseCellEditor
74582lang.extend(widget.TextboxCellEditor, BCE, {
74583
74584/////////////////////////////////////////////////////////////////////////////
74585//
74586// TextboxCellEditor public properties
74587//
74588/////////////////////////////////////////////////////////////////////////////
74589/**
74590 * Reference to the textbox element.
74591 *
74592 * @property textbox
74593 */
74594textbox : null,
74595
74596/////////////////////////////////////////////////////////////////////////////
74597//
74598// TextboxCellEditor public methods
74599//
74600/////////////////////////////////////////////////////////////////////////////
74601
74602/**
74603 * Render a form with input type=text.
74604 *
74605 * @method renderForm
74606 */
74607renderForm : function() {
74608    var elTextbox;
74609    // Bug 1802582: SF3/Mac needs a form element wrapping the input
74610    if(ua.webkit>420) {
74611        elTextbox = this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));
74612    }
74613    else {
74614        elTextbox = this.getContainerEl().appendChild(document.createElement("input"));
74615    }
74616    elTextbox.type = "text";
74617    this.textbox = elTextbox;
74618
74619    // Save on enter by default
74620    // Bug: 1802582 Set up a listener on each textbox to track on keypress
74621    // since SF/OP can't preventDefault on keydown
74622    Ev.addListener(elTextbox, "keypress", function(v){
74623        if((v.keyCode === 13)) {
74624            // Prevent form submit
74625            YAHOO.util.Event.preventDefault(v);
74626            this.save();
74627        }
74628    }, this, true);
74629
74630    if(this.disableBtns) {
74631        // By default this is no-op since enter saves by default
74632        this.handleDisabledBtns();
74633    }
74634},
74635
74636/**
74637 * Moves TextboxCellEditor UI to a cell.
74638 *
74639 * @method move
74640 */
74641move : function() {
74642    this.textbox.style.width = this.getTdEl().offsetWidth + "px";
74643    widget.TextboxCellEditor.superclass.move.call(this);
74644},
74645
74646/**
74647 * Resets TextboxCellEditor UI to initial state.
74648 *
74649 * @method resetForm
74650 */
74651resetForm : function() {
74652    this.textbox.value = lang.isValue(this.value) ? this.value.toString() : "";
74653},
74654
74655/**
74656 * Sets focus in TextboxCellEditor.
74657 *
74658 * @method focus
74659 */
74660focus : function() {
74661    // Bug 2303181, Bug 2263600
74662    this.getDataTable()._focusEl(this.textbox);
74663    this.textbox.select();
74664},
74665
74666/**
74667 * Returns new value for TextboxCellEditor.
74668 *
74669 * @method getInputValue
74670 */
74671getInputValue : function() {
74672    return this.textbox.value;
74673}
74674
74675});
74676
74677// Copy static members to TextboxCellEditor class
74678lang.augmentObject(widget.TextboxCellEditor, BCE);
74679
74680
74681
74682
74683
74684
74685
74686/////////////////////////////////////////////////////////////////////////////
74687//
74688// DataTable extension
74689//
74690/////////////////////////////////////////////////////////////////////////////
74691
74692/**
74693 * CellEditor subclasses.
74694 * @property DataTable.Editors
74695 * @type Object
74696 * @static
74697 */
74698DT.Editors = {
74699    checkbox : widget.CheckboxCellEditor,
74700    "date"   : widget.DateCellEditor,
74701    dropdown : widget.DropdownCellEditor,
74702    radio    : widget.RadioCellEditor,
74703    textarea : widget.TextareaCellEditor,
74704    textbox  : widget.TextboxCellEditor
74705};
74706
74707/****************************************************************************/
74708/****************************************************************************/
74709/****************************************************************************/
74710    
74711/**
74712 * Factory class for instantiating a BaseCellEditor subclass.
74713 *
74714 * @namespace YAHOO.widget
74715 * @class CellEditor
74716 * @extends YAHOO.widget.BaseCellEditor 
74717 * @constructor
74718 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
74719 * @param oConfigs {Object} (Optional) Object literal of configs.
74720 */
74721widget.CellEditor = function(sType, oConfigs) {
74722    // Point to one of the subclasses
74723    if(sType && DT.Editors[sType]) {
74724        lang.augmentObject(BCE, DT.Editors[sType]);
74725        return new DT.Editors[sType](oConfigs);
74726    }
74727    else {
74728        return new BCE(null, oConfigs);
74729    }
74730};
74731
74732var CE = widget.CellEditor;
74733
74734// Copy static members to CellEditor class
74735lang.augmentObject(CE, BCE);
74736
74737
74738})();
74739
74740YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.9.0", build: "2800"});
74741/*
74742Copyright (c) 2011, Yahoo! Inc. All rights reserved.
74743Code licensed under the BSD License:
74744http://developer.yahoo.com/yui/license.html
74745version: 2.9.0
74746*/
74747(function () {
74748    var Dom = YAHOO.util.Dom,
74749        Event = YAHOO.util.Event,
74750        Lang = YAHOO.lang,
74751        Widget = YAHOO.widget;
74752
74753
74754
74755/**
74756 * The treeview widget is a generic tree building tool.
74757 * @module treeview
74758 * @title TreeView Widget
74759 * @requires yahoo, dom, event
74760 * @optional animation, json, calendar
74761 * @namespace YAHOO.widget
74762 */
74763
74764/**
74765 * Contains the tree view state data and the root node.
74766 *
74767 * @class TreeView
74768 * @uses YAHOO.util.EventProvider
74769 * @constructor
74770 * @param {string|HTMLElement} id The id of the element, or the element itself that the tree will be inserted into.
74771 *        Existing markup in this element, if valid, will be used to build the tree
74772 * @param {Array|Object|String}  oConfig (optional)  If present, it will be used to build the tree via method <a href="#method_buildTreeFromObject">buildTreeFromObject</a>
74773 *
74774 */
74775YAHOO.widget.TreeView = function(id, oConfig) {
74776    if (id) { this.init(id); }
74777    if (oConfig) {
74778        this.buildTreeFromObject(oConfig);
74779    } else if (Lang.trim(this._el.innerHTML)) {
74780        this.buildTreeFromMarkup(id);
74781    }
74782};
74783
74784var TV = Widget.TreeView;
74785
74786TV.prototype = {
74787
74788    /**
74789     * The id of tree container element
74790     * @property id
74791     * @type String
74792     */
74793    id: null,
74794
74795    /**
74796     * The host element for this tree
74797     * @property _el
74798     * @private
74799     * @type HTMLelement
74800     */
74801    _el: null,
74802
74803     /**
74804     * Flat collection of all nodes in this tree.  This is a sparse
74805     * array, so the length property can't be relied upon for a
74806     * node count for the tree.
74807     * @property _nodes
74808     * @type Node[]
74809     * @private
74810     */
74811    _nodes: null,
74812
74813    /**
74814     * We lock the tree control while waiting for the dynamic loader to return
74815     * @property locked
74816     * @type boolean
74817     */
74818    locked: false,
74819
74820    /**
74821     * The animation to use for expanding children, if any
74822     * @property _expandAnim
74823     * @type string
74824     * @private
74825     */
74826    _expandAnim: null,
74827
74828    /**
74829     * The animation to use for collapsing children, if any
74830     * @property _collapseAnim
74831     * @type string
74832     * @private
74833     */
74834    _collapseAnim: null,
74835
74836    /**
74837     * The current number of animations that are executing
74838     * @property _animCount
74839     * @type int
74840     * @private
74841     */
74842    _animCount: 0,
74843
74844    /**
74845     * The maximum number of animations to run at one time.
74846     * @property maxAnim
74847     * @type int
74848     */
74849    maxAnim: 2,
74850
74851    /**
74852     * Whether there is any subscriber to dblClickEvent
74853     * @property _hasDblClickSubscriber
74854     * @type boolean
74855     * @private
74856     */
74857    _hasDblClickSubscriber: false,
74858
74859    /**
74860     * Stores the timer used to check for double clicks
74861     * @property _dblClickTimer
74862     * @type window.timer object
74863     * @private
74864     */
74865    _dblClickTimer: null,
74866
74867  /**
74868     * A reference to the Node currently having the focus or null if none.
74869     * @property currentFocus
74870     * @type YAHOO.widget.Node
74871     */
74872    currentFocus: null,
74873
74874    /**
74875    * If true, only one Node can be highlighted at a time
74876    * @property singleNodeHighlight
74877    * @type boolean
74878    * @default false
74879    */
74880
74881    singleNodeHighlight: false,
74882
74883    /**
74884    * A reference to the Node that is currently highlighted.
74885    * It is only meaningful if singleNodeHighlight is enabled
74886    * @property _currentlyHighlighted
74887    * @type YAHOO.widget.Node
74888    * @default null
74889    * @private
74890    */
74891
74892    _currentlyHighlighted: null,
74893
74894    /**
74895     * Sets up the animation for expanding children
74896     * @method setExpandAnim
74897     * @param {string} type the type of animation (acceptable values defined
74898     * in YAHOO.widget.TVAnim)
74899     */
74900    setExpandAnim: function(type) {
74901        this._expandAnim = (Widget.TVAnim.isValid(type)) ? type : null;
74902    },
74903
74904    /**
74905     * Sets up the animation for collapsing children
74906     * @method setCollapseAnim
74907     * @param {string} type of animation (acceptable values defined in
74908     * YAHOO.widget.TVAnim)
74909     */
74910    setCollapseAnim: function(type) {
74911        this._collapseAnim = (Widget.TVAnim.isValid(type)) ? type : null;
74912    },
74913
74914    /**
74915     * Perform the expand animation if configured, or just show the
74916     * element if not configured or too many animations are in progress
74917     * @method animateExpand
74918     * @param el {HTMLElement} the element to animate
74919     * @param node {YAHOO.util.Node} the node that was expanded
74920     * @return {boolean} true if animation could be invoked, false otherwise
74921     */
74922    animateExpand: function(el, node) {
74923        this.logger.log("animating expand");
74924
74925        if (this._expandAnim && this._animCount < this.maxAnim) {
74926            // this.locked = true;
74927            var tree = this;
74928            var a = Widget.TVAnim.getAnim(this._expandAnim, el,
74929                            function() { tree.expandComplete(node); });
74930            if (a) {
74931                ++this._animCount;
74932                this.fireEvent("animStart", {
74933                        "node": node,
74934                        "type": "expand"
74935                    });
74936                a.animate();
74937            }
74938
74939            return true;
74940        }
74941
74942        return false;
74943    },
74944
74945    /**
74946     * Perform the collapse animation if configured, or just show the
74947     * element if not configured or too many animations are in progress
74948     * @method animateCollapse
74949     * @param el {HTMLElement} the element to animate
74950     * @param node {YAHOO.util.Node} the node that was expanded
74951     * @return {boolean} true if animation could be invoked, false otherwise
74952     */
74953    animateCollapse: function(el, node) {
74954        this.logger.log("animating collapse");
74955
74956        if (this._collapseAnim && this._animCount < this.maxAnim) {
74957            // this.locked = true;
74958            var tree = this;
74959            var a = Widget.TVAnim.getAnim(this._collapseAnim, el,
74960                            function() { tree.collapseComplete(node); });
74961            if (a) {
74962                ++this._animCount;
74963                this.fireEvent("animStart", {
74964                        "node": node,
74965                        "type": "collapse"
74966                    });
74967                a.animate();
74968            }
74969
74970            return true;
74971        }
74972
74973        return false;
74974    },
74975
74976    /**
74977     * Function executed when the expand animation completes
74978     * @method expandComplete
74979     */
74980    expandComplete: function(node) {
74981        this.logger.log("expand complete: " + this.id);
74982        --this._animCount;
74983        this.fireEvent("animComplete", {
74984                "node": node,
74985                "type": "expand"
74986            });
74987        // this.locked = false;
74988    },
74989
74990    /**
74991     * Function executed when the collapse animation completes
74992     * @method collapseComplete
74993     */
74994    collapseComplete: function(node) {
74995        this.logger.log("collapse complete: " + this.id);
74996        --this._animCount;
74997        this.fireEvent("animComplete", {
74998                "node": node,
74999                "type": "collapse"
75000            });
75001        // this.locked = false;
75002    },
75003
75004    /**
75005     * Initializes the tree
75006     * @method init
75007     * @parm {string|HTMLElement} id the id of the element that will hold the tree
75008     * @private
75009     */
75010    init: function(id) {
75011        this._el = Dom.get(id);
75012        this.id = Dom.generateId(this._el,"yui-tv-auto-id-");
75013
75014    /**
75015         * When animation is enabled, this event fires when the animation
75016         * starts
75017         * @event animStart
75018         * @type CustomEvent
75019         * @param {YAHOO.widget.Node} oArgs.node the node that is expanding/collapsing
75020         * @param {String} oArgs.type the type of animation ("expand" or "collapse")
75021         */
75022        this.createEvent("animStart", this);
75023
75024        /**
75025         * When animation is enabled, this event fires when the animation
75026         * completes
75027         * @event animComplete
75028         * @type CustomEvent
75029         * @param {YAHOO.widget.Node} oArgs.node the node that is expanding/collapsing
75030         * @param {String} oArgs.type the type of animation ("expand" or "collapse")
75031         */
75032        this.createEvent("animComplete", this);
75033
75034        /**
75035         * Fires when a node is going to be collapsed.  Return false to stop
75036         * the collapse.
75037         * @event collapse
75038         * @type CustomEvent
75039         * @param {YAHOO.widget.Node} node the node that is collapsing
75040         */
75041        this.createEvent("collapse", this);
75042
75043        /**
75044         * Fires after a node is successfully collapsed.  This event will not fire
75045         * if the "collapse" event was cancelled.
75046         * @event collapseComplete
75047         * @type CustomEvent
75048         * @param {YAHOO.widget.Node} node the node that was collapsed
75049         */
75050        this.createEvent("collapseComplete", this);
75051
75052        /**
75053         * Fires when a node is going to be expanded.  Return false to stop
75054         * the collapse.
75055         * @event expand
75056         * @type CustomEvent
75057         * @param {YAHOO.widget.Node} node the node that is expanding
75058         */
75059        this.createEvent("expand", this);
75060
75061        /**
75062         * Fires after a node is successfully expanded.  This event will not fire
75063         * if the "expand" event was cancelled.
75064         * @event expandComplete
75065         * @type CustomEvent
75066         * @param {YAHOO.widget.Node} node the node that was expanded
75067         */
75068        this.createEvent("expandComplete", this);
75069
75070    /**
75071         * Fires when the Enter key is pressed on a node that has the focus
75072         * @event enterKeyPressed
75073         * @type CustomEvent
75074         * @param {YAHOO.widget.Node} node the node that has the focus
75075         */
75076        this.createEvent("enterKeyPressed", this);
75077
75078    /**
75079         * Fires when the label in a TextNode or MenuNode or content in an HTMLNode receives a Click.
75080    * The listener may return false to cancel toggling and focusing on the node.
75081         * @event clickEvent
75082         * @type CustomEvent
75083         * @param oArgs.event  {HTMLEvent} The event object
75084         * @param oArgs.node {YAHOO.widget.Node} node the node that was clicked
75085         */
75086        this.createEvent("clickEvent", this);
75087
75088    /**
75089         * Fires when the focus receives the focus, when it changes from a Node
75090    * to another Node or when it is completely lost (blurred)
75091         * @event focusChanged
75092         * @type CustomEvent
75093         * @param oArgs.oldNode  {YAHOO.widget.Node} Node that had the focus or null if none
75094         * @param oArgs.newNode {YAHOO.widget.Node} Node that receives the focus or null if none
75095         */
75096
75097        this.createEvent('focusChanged',this);
75098
75099    /**
75100         * Fires when the label in a TextNode or MenuNode or content in an HTMLNode receives a double Click
75101         * @event dblClickEvent
75102         * @type CustomEvent
75103         * @param oArgs.event  {HTMLEvent} The event object
75104         * @param oArgs.node {YAHOO.widget.Node} node the node that was clicked
75105         */
75106        var self = this;
75107        this.createEvent("dblClickEvent", {
75108            scope:this,
75109            onSubscribeCallback: function() {
75110                self._hasDblClickSubscriber = true;
75111            }
75112        });
75113
75114    /**
75115         * Custom event that is fired when the text node label is clicked.
75116         *  The node clicked is  provided as an argument
75117         *
75118         * @event labelClick
75119         * @type CustomEvent
75120         * @param {YAHOO.widget.Node} node the node clicked
75121    * @deprecated use clickEvent or dblClickEvent
75122         */
75123        this.createEvent("labelClick", this);
75124
75125    /**
75126     * Custom event fired when the highlight of a node changes.
75127     * The node that triggered the change is provided as an argument:
75128     * The status of the highlight can be checked in
75129     * <a href="YAHOO.widget.Node.html#property_highlightState">nodeRef.highlightState</a>.
75130     * Depending on <a href="YAHOO.widget.Node.html#property_propagateHighlight">nodeRef.propagateHighlight</a>, other nodes might have changed
75131     * @event highlightEvent
75132     * @type CustomEvent
75133     * @param node {YAHOO.widget.Node} the node that started the change in highlighting state
75134    */
75135        this.createEvent("highlightEvent",this);
75136
75137
75138        this._nodes = [];
75139
75140        // store a global reference
75141        TV.trees[this.id] = this;
75142
75143        // Set up the root node
75144        this.root = new Widget.RootNode(this);
75145
75146        var LW = Widget.LogWriter;
75147
75148        this.logger = (LW) ? new LW(this.toString()) : YAHOO;
75149
75150        this.logger.log("tree init: " + this.id);
75151
75152        if (this._initEditor) {
75153            this._initEditor();
75154        }
75155
75156        // YAHOO.util.Event.onContentReady(this.id, this.handleAvailable, this, true);
75157        // YAHOO.util.Event.on(this.id, "click", this.handleClick, this, true);
75158    },
75159
75160    //handleAvailable: function() {
75161        //var Event = YAHOO.util.Event;
75162        //Event.on(this.id,
75163    //},
75164 /**
75165     * Builds the TreeView from an object.
75166     * This is the method called by the constructor to build the tree when it has a second argument.
75167     *  A tree can be described by an array of objects, each object corresponding to a node.
75168     *  Node descriptions may contain values for any property of a node plus the following extra properties: <ul>
75169     * <li>type:  can be one of the following:<ul>
75170     *    <li> A shortname for a node type (<code>'text','menu','html'</code>) </li>
75171     *    <li>The name of a Node class under YAHOO.widget (<code>'TextNode', 'MenuNode', 'DateNode'</code>, etc) </li>
75172     *    <li>a reference to an actual class: <code>YAHOO.widget.DateNode</code></li>
75173     * </ul></li>
75174     * <li>children: an array containing further node definitions</li></ul>
75175     * A string instead of an object will produce a node of type 'text' with the given string as its label.
75176     * @method buildTreeFromObject
75177     * @param  oConfig {Array|Object|String}  array containing a full description of the tree.
75178     *        An object or a string will be turned into an array with the given object or string as its only element.
75179     *
75180     */
75181    buildTreeFromObject: function (oConfig) {
75182        var logger = this.logger;
75183        logger.log('Building tree from object');
75184        var build = function (parent, oConfig) {
75185            var i, item, node, children, type, NodeType, ThisType;
75186            for (i = 0; i < oConfig.length; i++) {
75187                item = oConfig[i];
75188                if (Lang.isString(item)) {
75189                    node = new Widget.TextNode(item, parent);
75190                } else if (Lang.isObject(item)) {
75191                    children = item.children;
75192                    delete item.children;
75193                    type = item.type || 'text';
75194                    delete item.type;
75195                    switch (Lang.isString(type) && type.toLowerCase()) {
75196                        case 'text':
75197                            node = new Widget.TextNode(item, parent);
75198                            break;
75199                        case 'menu':
75200                            node = new Widget.MenuNode(item, parent);
75201                            break;
75202                        case 'html':
75203                            node = new Widget.HTMLNode(item, parent);
75204                            break;
75205                        default:
75206                            if (Lang.isString(type)) {
75207                                NodeType = Widget[type];
75208                            } else {
75209                                NodeType = type;
75210                            }
75211                            if (Lang.isObject(NodeType)) {
75212                                for (ThisType = NodeType; ThisType && ThisType !== Widget.Node; ThisType = ThisType.superclass.constructor) {}
75213                                if (ThisType) {
75214                                    node = new NodeType(item, parent);
75215                                } else {
75216                                    logger.log('Invalid type in node definition: ' + type,'error');
75217                                }
75218                            } else {
75219                                logger.log('Invalid type in node definition: ' + type,'error');
75220                            }
75221                    }
75222                    if (children) {
75223                        build(node,children);
75224                    }
75225                } else {
75226                    logger.log('Invalid node definition','error');
75227                }
75228            }
75229        };
75230        if (!Lang.isArray(oConfig)) {
75231            oConfig = [oConfig];
75232        }
75233
75234
75235        build(this.root,oConfig);
75236    },
75237/**
75238     * Builds the TreeView from existing markup.   Markup should consist of &lt;UL&gt; or &lt;OL&gt; elements containing &lt;LI&gt; elements.
75239     * Each &lt;LI&gt; can have one element used as label and a second optional element which is to be a &lt;UL&gt; or &lt;OL&gt;
75240     * containing nested nodes.
75241     * Depending on what the first element of the &lt;LI&gt; element is, the following Nodes will be created: <ul>
75242     *           <li>plain text:  a regular TextNode</li>
75243     *           <li>anchor &lt;A&gt;: a TextNode with its <code>href</code> and <code>target</code> taken from the anchor</li>
75244     *           <li>anything else: an HTMLNode</li></ul>
75245     * Only the first  outermost (un-)ordered list in the markup and its children will be parsed.
75246     * Nodes will be collapsed unless  an  &lt;LI&gt;  tag has a className called 'expanded'.
75247     * All other className attributes will be copied over to the Node className property.
75248     * If the &lt;LI&gt; element contains an attribute called <code>yuiConfig</code>, its contents should be a JSON-encoded object
75249     * as the one used in method <a href="#method_buildTreeFromObject">buildTreeFromObject</a>.
75250     * @method buildTreeFromMarkup
75251     * @param  id {string|HTMLElement} The id of the element that contains the markup or a reference to it.
75252     */
75253    buildTreeFromMarkup: function (id) {
75254        this.logger.log('Building tree from existing markup');
75255        var build = function (markup) {
75256            var el, child, branch = [], config = {}, label, yuiConfig;
75257            // Dom's getFirstChild and getNextSibling skip over text elements
75258            for (el = Dom.getFirstChild(markup); el; el = Dom.getNextSibling(el)) {
75259                switch (el.tagName.toUpperCase()) {
75260                    case 'LI':
75261                        label = '';
75262                        config = {
75263                            expanded: Dom.hasClass(el,'expanded'),
75264                            title: el.title || el.alt || null,
75265                            className: Lang.trim(el.className.replace(/\bexpanded\b/,'')) || null
75266                        };
75267                        // I cannot skip over text elements here because I want them for labels
75268                        child = el.firstChild;
75269                        if (child.nodeType == 3) {
75270                            // nodes with only whitespace, tabs and new lines don't count, they are probably just formatting.
75271                            label = Lang.trim(child.nodeValue.replace(/[\n\t\r]*/g,''));
75272                            if (label) {
75273                                config.type = 'text';
75274                                config.label = label;
75275                            } else {
75276                                child = Dom.getNextSibling(child);
75277                            }
75278                        }
75279                        if (!label) {
75280                            if (child.tagName.toUpperCase() == 'A') {
75281                                config.type = 'text';
75282                                config.label = child.innerHTML;
75283                                config.href = child.href;
75284                                config.target = child.target;
75285                                config.title = child.title || child.alt || config.title;
75286                            } else {
75287                                config.type = 'html';
75288                                var d = document.createElement('div');
75289                                d.appendChild(child.cloneNode(true));
75290                                config.html = d.innerHTML;
75291                                config.hasIcon = true;
75292                            }
75293                        }
75294                        // see if after the label it has a further list which will become children of this node.
75295                        child = Dom.getNextSibling(child);
75296                        switch (child && child.tagName.toUpperCase()) {
75297                            case 'UL':
75298                            case 'OL':
75299                                config.children = build(child);
75300                                break;
75301                        }
75302                        // if there are further elements or text, it will be ignored.
75303
75304                        if (YAHOO.lang.JSON) {
75305                            yuiConfig = el.getAttribute('yuiConfig');
75306                            if (yuiConfig) {
75307                                yuiConfig = YAHOO.lang.JSON.parse(yuiConfig);
75308                                config = YAHOO.lang.merge(config,yuiConfig);
75309                            }
75310                        }
75311
75312                        branch.push(config);
75313                        break;
75314                    case 'UL':
75315                    case 'OL':
75316                        this.logger.log('ULs or OLs can only contain LI elements, not other UL or OL.  This will not work in some browsers','error');
75317                        config = {
75318                            type: 'text',
75319                            label: '',
75320                            children: build(child)
75321                        };
75322                        branch.push(config);
75323                        break;
75324                }
75325            }
75326            return branch;
75327        };
75328
75329        var markup = Dom.getChildrenBy(Dom.get(id),function (el) {
75330            var tag = el.tagName.toUpperCase();
75331            return  tag == 'UL' || tag == 'OL';
75332        });
75333        if (markup.length) {
75334            this.buildTreeFromObject(build(markup[0]));
75335        } else {
75336            this.logger.log('Markup contains no UL or OL elements','warn');
75337        }
75338    },
75339  /**
75340     * Returns the TD element where the event has occurred
75341     * @method _getEventTargetTdEl
75342     * @private
75343     */
75344    _getEventTargetTdEl: function (ev) {
75345        var target = Event.getTarget(ev);
75346        // go up looking for a TD with a className with a ygtv prefix
75347        while (target && !(target.tagName.toUpperCase() == 'TD' && Dom.hasClass(target.parentNode,'ygtvrow'))) {
75348            target = Dom.getAncestorByTagName(target,'td');
75349        }
75350        if (Lang.isNull(target)) { return null; }
75351        // If it is a spacer cell, do nothing
75352        if (/\bygtv(blank)?depthcell/.test(target.className)) { return null;}
75353        // If it has an id, search for the node number and see if it belongs to a node in this tree.
75354        if (target.id) {
75355            var m = target.id.match(/\bygtv([^\d]*)(.*)/);
75356            if (m && m[2] && this._nodes[m[2]]) {
75357                return target;
75358            }
75359        }
75360        return null;
75361    },
75362  /**
75363     * Event listener for click events
75364     * @method _onClickEvent
75365     * @private
75366     */
75367    _onClickEvent: function (ev) {
75368        var self = this,
75369            td = this._getEventTargetTdEl(ev),
75370            node,
75371            target,
75372            toggle = function (force) {
75373                node.focus();
75374                if (force || !node.href) {
75375                    node.toggle();
75376                    try {
75377                        Event.preventDefault(ev);
75378                    } catch (e) {
75379                        // @TODO
75380                        // For some reason IE8 is providing an event object with
75381                        // most of the fields missing, but only when clicking on
75382                        // the node's label, and only when working with inline
75383                        // editing.  This generates a "Member not found" error
75384                        // in that browser.  Determine if this is a browser
75385                        // bug, or a problem with this code.  Already checked to
75386                        // see if the problem has to do with access the event
75387                        // in the outer scope, and that isn't the problem.
75388                        // Maybe the markup for inline editing is broken.
75389                    }
75390                }
75391            };
75392
75393        if (!td) {
75394            return;
75395        }
75396
75397        node = this.getNodeByElement(td);
75398        if (!node) {
75399            return;
75400        }
75401
75402        // exception to handle deprecated event labelClick
75403        // @TODO take another look at this deprecation.  It is common for people to
75404        // only be interested in the label click, so why make them have to test
75405        // the node type to figure out whether the click was on the label?
75406        target = Event.getTarget(ev);
75407        if (Dom.hasClass(target, node.labelStyle) || Dom.getAncestorByClassName(target,node.labelStyle)) {
75408            this.logger.log("onLabelClick " + node.label);
75409            this.fireEvent('labelClick',node);
75410        }
75411        // http://yuilibrary.com/projects/yui2/ticket/2528946
75412        // Ensures that any open editor is closed.
75413        // Since the editor is in a separate source which might not be included,
75414        // we first need to ensure we have the _closeEditor method available
75415        if (this._closeEditor) { this._closeEditor(false); }
75416
75417        //  If it is a toggle cell, toggle
75418        if (/\bygtv[tl][mp]h?h?/.test(td.className)) {
75419            toggle(true);
75420        }
75421        //Start Changes
75422         else if (node._customRegEx(node.customCls) && node._customRegEx(node.customCls).test(td.className))
75423         {
75424            toggle(true);
75425         }
75426         //End Changes
75427        else {
75428            if (this._dblClickTimer) {
75429                window.clearTimeout(this._dblClickTimer);
75430                this._dblClickTimer = null;
75431            } else {
75432                if (this._hasDblClickSubscriber) {
75433                    this._dblClickTimer = window.setTimeout(function () {
75434                        self._dblClickTimer = null;
75435                        if (self.fireEvent('clickEvent', {event:ev,node:node}) !== false) {
75436                            toggle();
75437                        }
75438                    }, 200);
75439                } else {
75440                    if (self.fireEvent('clickEvent', {event:ev,node:node}) !== false) {
75441                        toggle();
75442                    }
75443                }
75444            }
75445        }
75446    },
75447
75448  /**
75449     * Event listener for double-click events
75450     * @method _onDblClickEvent
75451     * @private
75452     */
75453    _onDblClickEvent: function (ev) {
75454        if (!this._hasDblClickSubscriber) { return; }
75455        var td = this._getEventTargetTdEl(ev);
75456        if (!td) {return;}
75457
75458        if (!(/\bygtv[tl][mp]h?h?/.test(td.className))) {
75459            this.fireEvent('dblClickEvent', {event:ev, node:this.getNodeByElement(td)});
75460            if (this._dblClickTimer) {
75461                window.clearTimeout(this._dblClickTimer);
75462                this._dblClickTimer = null;
75463            }
75464        }
75465    },
75466  /**
75467     * Event listener for mouse over events
75468     * @method _onMouseOverEvent
75469     * @private
75470     */
75471    _onMouseOverEvent:function (ev) {
75472        var target;
75473        if ((target = this._getEventTargetTdEl(ev)) && (target = this.getNodeByElement(target)) && (target = target.getToggleEl())) {
75474            target.className = target.className.replace(/\bygtv([lt])([mp])\b/gi,'ygtv$1$2h');
75475        }
75476    },
75477  /**
75478     * Event listener for mouse out events
75479     * @method _onMouseOutEvent
75480     * @private
75481     */
75482    _onMouseOutEvent: function (ev) {
75483        var target;
75484        if ((target = this._getEventTargetTdEl(ev)) && (target = this.getNodeByElement(target)) && (target = target.getToggleEl())) {
75485            target.className = target.className.replace(/\bygtv([lt])([mp])h\b/gi,'ygtv$1$2');
75486        }
75487    },
75488  /**
75489     * Event listener for key down events
75490     * @method _onKeyDownEvent
75491     * @private
75492     */
75493    _onKeyDownEvent: function (ev) {
75494        var target = Event.getTarget(ev),
75495            node = this.getNodeByElement(target),
75496            newNode = node,
75497            KEY = YAHOO.util.KeyListener.KEY;
75498
75499        switch(ev.keyCode) {
75500            case KEY.UP:
75501                this.logger.log('UP');
75502                do {
75503                    if (newNode.previousSibling) {
75504                        newNode = newNode.previousSibling;
75505                    } else {
75506                        newNode = newNode.parent;
75507                    }
75508                } while (newNode && !newNode._canHaveFocus());
75509                if (newNode) { newNode.focus(); }
75510                Event.preventDefault(ev);
75511                break;
75512            case KEY.DOWN:
75513                this.logger.log('DOWN');
75514                do {
75515                    if (newNode.nextSibling) {
75516                        newNode = newNode.nextSibling;
75517                    } else {
75518                        newNode.expand();
75519                        newNode = (newNode.children.length || null) && newNode.children[0];
75520                    }
75521                } while (newNode && !newNode._canHaveFocus);
75522                if (newNode) { newNode.focus();}
75523                Event.preventDefault(ev);
75524                break;
75525            case KEY.LEFT:
75526                this.logger.log('LEFT');
75527                do {
75528                    if (newNode.parent) {
75529                        newNode = newNode.parent;
75530                    } else {
75531                        newNode = newNode.previousSibling;
75532                    }
75533                } while (newNode && !newNode._canHaveFocus());
75534                if (newNode) { newNode.focus();}
75535                Event.preventDefault(ev);
75536                break;
75537            case KEY.RIGHT:
75538                this.logger.log('RIGHT');
75539                var self = this,
75540                    moveFocusRight,
75541                    focusOnExpand = function (newNode) {
75542                        self.unsubscribe('expandComplete',focusOnExpand);
75543                        moveFocusRight(newNode);
75544                    };
75545                moveFocusRight = function (newNode) {
75546                    do {
75547                        if (newNode.isDynamic() && !newNode.childrenRendered) {
75548                            self.subscribe('expandComplete',focusOnExpand);
75549                            newNode.expand();
75550                            newNode = null;
75551                            break;
75552                        } else {
75553                            newNode.expand();
75554                            if (newNode.children.length) {
75555                                newNode = newNode.children[0];
75556                            } else {
75557                                newNode = newNode.nextSibling;
75558                            }
75559                        }
75560                    } while (newNode && !newNode._canHaveFocus());
75561                    if (newNode) { newNode.focus();}
75562                };
75563
75564                moveFocusRight(newNode);
75565                Event.preventDefault(ev);
75566                break;
75567            case KEY.ENTER:
75568                this.logger.log('ENTER: ' + newNode.href);
75569                if (node.href) {
75570                    if (node.target) {
75571                        window.open(node.href,node.target);
75572                    } else {
75573                        window.location(node.href);
75574                    }
75575                } else {
75576                    node.toggle();
75577                }
75578                this.fireEvent('enterKeyPressed',node);
75579                Event.preventDefault(ev);
75580                break;
75581            case KEY.HOME:
75582                this.logger.log('HOME');
75583                newNode = this.getRoot();
75584                if (newNode.children.length) {newNode = newNode.children[0];}
75585                if (newNode._canHaveFocus()) { newNode.focus(); }
75586                Event.preventDefault(ev);
75587                break;
75588            case KEY.END:
75589                this.logger.log('END');
75590                newNode = newNode.parent.children;
75591                newNode = newNode[newNode.length -1];
75592                if (newNode._canHaveFocus()) { newNode.focus(); }
75593                Event.preventDefault(ev);
75594                break;
75595            // case KEY.PAGE_UP:
75596                // this.logger.log('PAGE_UP');
75597                // break;
75598            // case KEY.PAGE_DOWN:
75599                // this.logger.log('PAGE_DOWN');
75600                // break;
75601            case 107:  // plus key
75602            case 187:  // plus key
75603                if (ev.shiftKey) {
75604                    this.logger.log('Shift-PLUS');
75605                    node.parent.expandAll();
75606                } else {
75607                    this.logger.log('PLUS');
75608                    node.expand();
75609                }
75610                break;
75611            case 109: // minus key
75612            case 189: // minus key
75613                if (ev.shiftKey) {
75614                    this.logger.log('Shift-MINUS');
75615                    node.parent.collapseAll();
75616                } else {
75617                    this.logger.log('MINUS');
75618                    node.collapse();
75619                }
75620                break;
75621            default:
75622                break;
75623        }
75624    },
75625    /**
75626     * Renders the tree boilerplate and visible nodes
75627     * @method render
75628     */
75629    render: function() {
75630        var html = this.root.getHtml(),
75631            el = this.getEl();
75632        el.innerHTML = html;
75633        if (!this._hasEvents) {
75634            Event.on(el, 'click', this._onClickEvent, this, true);
75635            Event.on(el, 'dblclick', this._onDblClickEvent, this, true);
75636            Event.on(el, 'mouseover', this._onMouseOverEvent, this, true);
75637            Event.on(el, 'mouseout', this._onMouseOutEvent, this, true);
75638            Event.on(el, 'keydown', this._onKeyDownEvent, this, true);
75639        }
75640        this._hasEvents = true;
75641    },
75642
75643  /**
75644     * Returns the tree's host element
75645     * @method getEl
75646     * @return {HTMLElement} the host element
75647     */
75648    getEl: function() {
75649        if (! this._el) {
75650            this._el = Dom.get(this.id);
75651        }
75652        return this._el;
75653    },
75654
75655    /**
75656     * Nodes register themselves with the tree instance when they are created.
75657     * @method regNode
75658     * @param node {Node} the node to register
75659     * @private
75660     */
75661    regNode: function(node) {
75662        this._nodes[node.index] = node;
75663    },
75664
75665    /**
75666     * Returns the root node of this tree
75667     * @method getRoot
75668     * @return {Node} the root node
75669     */
75670    getRoot: function() {
75671        return this.root;
75672    },
75673
75674    /**
75675     * Configures this tree to dynamically load all child data
75676     * @method setDynamicLoad
75677     * @param {function} fnDataLoader the function that will be called to get the data
75678     * @param iconMode {int} configures the icon that is displayed when a dynamic
75679     * load node is expanded the first time without children.  By default, the
75680     * "collapse" icon will be used.  If set to 1, the leaf node icon will be
75681     * displayed.
75682     */
75683    setDynamicLoad: function(fnDataLoader, iconMode) {
75684        this.root.setDynamicLoad(fnDataLoader, iconMode);
75685    },
75686
75687    /**
75688     * Expands all child nodes.  Note: this conflicts with the "multiExpand"
75689     * node property.  If expand all is called in a tree with nodes that
75690     * do not allow multiple siblings to be displayed, only the last sibling
75691     * will be expanded.
75692     * @method expandAll
75693     */
75694    expandAll: function() {
75695        if (!this.locked) {
75696            this.root.expandAll();
75697        }
75698    },
75699
75700    /**
75701     * Collapses all expanded child nodes in the entire tree.
75702     * @method collapseAll
75703     */
75704    collapseAll: function() {
75705        if (!this.locked) {
75706            this.root.collapseAll();
75707        }
75708    },
75709
75710    /**
75711     * Returns a node in the tree that has the specified index (this index
75712     * is created internally, so this function probably will only be used
75713     * in html generated for a given node.)
75714     * @method getNodeByIndex
75715     * @param {int} nodeIndex the index of the node wanted
75716     * @return {Node} the node with index=nodeIndex, null if no match
75717     */
75718    getNodeByIndex: function(nodeIndex) {
75719        var n = this._nodes[nodeIndex];
75720        return (n) ? n : null;
75721    },
75722
75723    /**
75724     * Returns a node that has a matching property and value in the data
75725     * object that was passed into its constructor.
75726     * @method getNodeByProperty
75727     * @param {object} property the property to search (usually a string)
75728     * @param {object} value the value we want to find (usuall an int or string)
75729     * @return {Node} the matching node, null if no match
75730     */
75731    getNodeByProperty: function(property, value) {
75732        for (var i in this._nodes) {
75733            if (this._nodes.hasOwnProperty(i)) {
75734                var n = this._nodes[i];
75735                if ((property in n && n[property] == value) || (n.data && value == n.data[property])) {
75736                    return n;
75737                }
75738            }
75739        }
75740
75741        return null;
75742    },
75743
75744    /**
75745     * Returns a collection of nodes that have a matching property
75746     * and value in the data object that was passed into its constructor.
75747     * @method getNodesByProperty
75748     * @param {object} property the property to search (usually a string)
75749     * @param {object} value the value we want to find (usuall an int or string)
75750     * @return {Array} the matching collection of nodes, null if no match
75751     */
75752    getNodesByProperty: function(property, value) {
75753        var values = [];
75754        for (var i in this._nodes) {
75755            if (this._nodes.hasOwnProperty(i)) {
75756                var n = this._nodes[i];
75757                if ((property in n && n[property] == value) || (n.data && value == n.data[property])) {
75758                    values.push(n);
75759                }
75760            }
75761        }
75762
75763        return (values.length) ? values : null;
75764    },
75765
75766
75767    /**
75768     * Returns a collection of nodes that have passed the test function
75769     * passed as its only argument.
75770     * The function will receive a reference to each node to be tested.
75771     * @method getNodesBy
75772     * @param {function} a boolean function that receives a Node instance and returns true to add the node to the results list
75773     * @return {Array} the matching collection of nodes, null if no match
75774     */
75775    getNodesBy: function(fn) {
75776        var values = [];
75777        for (var i in this._nodes) {
75778            if (this._nodes.hasOwnProperty(i)) {
75779                var n = this._nodes[i];
75780                if (fn(n)) {
75781                    values.push(n);
75782                }
75783            }
75784        }
75785        return (values.length) ? values : null;
75786    },
75787    /**
75788     * Returns the treeview node reference for an ancestor element
75789     * of the node, or null if it is not contained within any no
75789de
75790     * in this tree.
75791     * @method getNodeByElement
75792     * @param el {HTMLElement} the element to test
75793     * @return {YAHOO.widget.Node} a node reference or null
75794     */
75795    getNodeByElement: function(el) {
75796
75797        var p=el, m, re=/ygtv([^\d]*)(.*)/;
75798
75799        do {
75800
75801            if (p && p.id) {
75802                m = p.id.match(re);
75803                if (m && m[2]) {
75804                    return this.getNodeByIndex(m[2]);
75805                }
75806            }
75807
75808            p = p.parentNode;
75809
75810            if (!p || !p.tagName) {
75811                break;
75812            }
75813
75814        }
75815        while (p.id !== this.id && p.tagName.toLowerCase() !== "body");
75816
75817        return null;
75818    },
75819
75820    /**
75821     * When in singleNodeHighlight it returns the node highlighted
75822     * or null if none.  Returns null if singleNodeHighlight is false.
75823     * @method getHighlightedNode
75824     * @return {YAHOO.widget.Node} a node reference or null
75825     */
75826    getHighlightedNode: function() {
75827        return this._currentlyHighlighted;
75828    },
75829
75830
75831    /**
75832     * Removes the node and its children, and optionally refreshes the
75833     * branch of the tree that was affected.
75834     * @method removeNode
75835     * @param {Node} node to remove
75836     * @param {boolean} autoRefresh automatically refreshes branch if true
75837     * @return {boolean} False is there was a problem, true otherwise.
75838     */
75839    removeNode: function(node, autoRefresh) {
75840
75841        // Don't delete the root node
75842        if (node.isRoot()) {
75843            return false;
75844        }
75845
75846        // Get the branch that we may need to refresh
75847        var p = node.parent;
75848        if (p.parent) {
75849            p = p.parent;
75850        }
75851
75852        // Delete the node and its children
75853        this._deleteNode(node);
75854
75855        // Refresh the parent of the parent
75856        if (autoRefresh && p && p.childrenRendered) {
75857            p.refresh();
75858        }
75859
75860        return true;
75861    },
75862
75863    /**
75864     * wait until the animation is complete before deleting
75865     * to avoid javascript errors
75866     * @method _removeChildren_animComplete
75867     * @param o the custom event payload
75868     * @private
75869     */
75870    _removeChildren_animComplete: function(o) {
75871        this.unsubscribe(this._removeChildren_animComplete);
75872        this.removeChildren(o.node);
75873    },
75874
75875    /**
75876     * Deletes this nodes child collection, recursively.  Also collapses
75877     * the node, and resets the dynamic load flag.  The primary use for
75878     * this method is to purge a node and allow it to fetch its data
75879     * dynamically again.
75880     * @method removeChildren
75881     * @param {Node} node the node to purge
75882     */
75883    removeChildren: function(node) {
75884
75885        if (node.expanded) {
75886            // wait until the animation is complete before deleting to
75887            // avoid javascript errors
75888            if (this._collapseAnim) {
75889                this.subscribe("animComplete",
75890                        this._removeChildren_animComplete, this, true);
75891                Widget.Node.prototype.collapse.call(node);
75892                return;
75893            }
75894
75895            node.collapse();
75896        }
75897
75898        this.logger.log("Removing children for " + node);
75899        while (node.children.length) {
75900            this._deleteNode(node.children[0]);
75901        }
75902
75903        if (node.isRoot()) {
75904            Widget.Node.prototype.expand.call(node);
75905        }
75906
75907        node.childrenRendered = false;
75908        node.dynamicLoadComplete = false;
75909
75910        node.updateIcon();
75911    },
75912
75913    /**
75914     * Deletes the node and recurses children
75915     * @method _deleteNode
75916     * @private
75917     */
75918    _deleteNode: function(node) {
75919        // Remove all the child nodes first
75920        this.removeChildren(node);
75921
75922        // Remove the node from the tree
75923        this.popNode(node);
75924    },
75925
75926    /**
75927     * Removes the node from the tree, preserving the child collection
75928     * to make it possible to insert the branch into another part of the
75929     * tree, or another tree.
75930     * @method popNode
75931     * @param {Node} node to remove
75932     */
75933    popNode: function(node) {
75934        var p = node.parent;
75935
75936        // Update the parent's collection of children
75937        var a = [];
75938
75939        for (var i=0, len=p.children.length;i<len;++i) {
75940            if (p.children[i] != node) {
75941                a[a.length] = p.children[i];
75942            }
75943        }
75944
75945        p.children = a;
75946
75947        // reset the childrenRendered flag for the parent
75948        p.childrenRendered = false;
75949
75950         // Update the sibling relationship
75951        if (node.previousSibling) {
75952            node.previousSibling.nextSibling = node.nextSibling;
75953        }
75954
75955        if (node.nextSibling) {
75956            node.nextSibling.previousSibling = node.previousSibling;
75957        }
75958
75959        if (this.currentFocus == node) {
75960            this.currentFocus = null;
75961        }
75962        if (this._currentlyHighlighted == node) {
75963            this._currentlyHighlighted = null;
75964        }
75965
75966        node.parent = null;
75967        node.previousSibling = null;
75968        node.nextSibling = null;
75969        node.tree = null;
75970
75971        // Update the tree's node collection
75972        delete this._nodes[node.index];
75973    },
75974
75975    /**
75976    * Nulls out the entire TreeView instance and related objects, removes attached
75977    * event listeners, and clears out DOM elements inside the container. After
75978    * calling this method, the instance reference should be expliclitly nulled by
75979    * implementer, as in myDataTable = null. Use with caution!
75980    *
75981    * @method destroy
75982    */
75983    destroy : function() {
75984        // Since the label editor can be separated from the main TreeView control
75985        // the destroy method for it might not be there.
75986        if (this._destroyEditor) { this._destroyEditor(); }
75987        var el = this.getEl();
75988        Event.removeListener(el,'click');
75989        Event.removeListener(el,'dblclick');
75990        Event.removeListener(el,'mouseover');
75991        Event.removeListener(el,'mouseout');
75992        Event.removeListener(el,'keydown');
75993        for (var i = 0 ; i < this._nodes.length; i++) {
75994            var node = this._nodes[i];
75995            if (node && node.destroy) {node.destroy(); }
75996        }
75997        el.innerHTML = '';
75998        this._hasEvents = false;
75999    },
76000
76001
76002
76003
76004    /**
76005     * TreeView instance toString
76006     * @method toString
76007     * @return {string} string representation of the tree
76008     */
76009    toString: function() {
76010        return "TreeView " + this.id;
76011    },
76012
76013    /**
76014     * Count of nodes in tree
76015     * @method getNodeCount
76016     * @return {int} number of nodes in the tree
76017     */
76018    getNodeCount: function() {
76019        return this.getRoot().getNodeCount();
76020    },
76021
76022    /**
76023     * Returns an object which could be used to rebuild the tree.
76024     * It can be passed to the tree constructor to reproduce the same tree.
76025     * It will return false if any node loads dynamically, regardless of whether it is loaded or not.
76026     * @method getTreeDefinition
76027     * @return {Object | false}  definition of the tree or false if any node is defined as dynamic
76028     */
76029    getTreeDefinition: function() {
76030        return this.getRoot().getNodeDefinition();
76031    },
76032
76033    /**
76034     * Abstract method that is executed when a node is expanded
76035     * @method onExpand
76036     * @param node {Node} the node that was expanded
76037     * @deprecated use treeobj.subscribe("expand") instead
76038     */
76039    onExpand: function(node) { },
76040
76041    /**
76042     * Abstract method that is executed when a node is collapsed.
76043     * @method onCollapse
76044     * @param node {Node} the node that was collapsed.
76045     * @deprecated use treeobj.subscribe("collapse") instead
76046     */
76047    onCollapse: function(node) { },
76048
76049    /**
76050    * Sets the value of a property for all loaded nodes in the tree.
76051    * @method setNodesProperty
76052    * @param name {string} Name of the property to be set
76053    * @param value {any} value to be set
76054    * @param refresh {boolean} if present and true, it does a refresh
76055    */
76056    setNodesProperty: function(name, value, refresh) {
76057        this.root.setNodesProperty(name,value);
76058        if (refresh) {
76059            this.root.refresh();
76060        }
76061    },
76062    /**
76063    * Event listener to toggle node highlight.
76064    * Can be assigned as listener to clickEvent, dblClickEvent and enterKeyPressed.
76065    * It returns false to prevent the default action.
76066    * @method onEventToggleHighlight
76067    * @param oArgs {any} it takes the arguments of any of the events mentioned above
76068    * @return {false} Always cancels the default action for the event
76069    */
76070    onEventToggleHighlight: function (oArgs) {
76071        var node;
76072        if ('node' in oArgs && oArgs.node instanceof Widget.Node) {
76073            node = oArgs.node;
76074        } else if (oArgs instanceof Widget.Node) {
76075            node = oArgs;
76076        } else {
76077            return false;
76078        }
76079        node.toggleHighlight();
76080        return false;
76081    }
76082
76083
76084};
76085
76086/* Backwards compatibility aliases */
76087var PROT = TV.prototype;
76088 /**
76089     * Renders the tree boilerplate and visible nodes.
76090     *  Alias for render
76091     * @method draw
76092     * @deprecated Use render instead
76093     */
76094PROT.draw = PROT.render;
76095
76096/* end backwards compatibility aliases */
76097
76098YAHOO.augment(TV, YAHOO.util.EventProvider);
76099
76100/**
76101 * Running count of all nodes created in all trees.  This is
76102 * used to provide unique identifies for all nodes.  Deleting
76103 * nodes does not change the nodeCount.
76104 * @property YAHOO.widget.TreeView.nodeCount
76105 * @type int
76106 * @static
76107 */
76108TV.nodeCount = 0;
76109
76110/**
76111 * Global cache of tree instances
76112 * @property YAHOO.widget.TreeView.trees
76113 * @type Array
76114 * @static
76115 * @private
76116 */
76117TV.trees = [];
76118
76119/**
76120 * Global method for getting a tree by its id.  Used in the generated
76121 * tree html.
76122 * @method YAHOO.widget.TreeView.getTree
76123 * @param treeId {String} the id of the tree instance
76124 * @return {TreeView} the tree instance requested, null if not found.
76125 * @static
76126 */
76127TV.getTree = function(treeId) {
76128    var t = TV.trees[treeId];
76129    return (t) ? t : null;
76130};
76131
76132
76133/**
76134 * Global method for getting a node by its id.  Used in the generated
76135 * tree html.
76136 * @method YAHOO.widget.TreeView.getNode
76137 * @param treeId {String} the id of the tree instance
76138 * @param nodeIndex {String} the index of the node to return
76139 * @return {Node} the node instance requested, null if not found
76140 * @static
76141 */
76142TV.getNode = function(treeId, nodeIndex) {
76143    var t = TV.getTree(treeId);
76144    return (t) ? t.getNodeByIndex(nodeIndex) : null;
76145};
76146
76147
76148/**
76149     * Class name assigned to elements that have the focus
76150     *
76151     * @property TreeView.FOCUS_CLASS_NAME
76152     * @type String
76153     * @static
76154     * @final
76155     * @default "ygtvfocus"
76156
76157    */
76158TV.FOCUS_CLASS_NAME = 'ygtvfocus';
76159
76160
76161
76162})();
76163(function () {
76164    var Dom = YAHOO.util.Dom,
76165        Lang = YAHOO.lang,
76166        Event = YAHOO.util.Event;
76167/**
76168 * The base class for all tree nodes.  The node's presentation and behavior in
76169 * response to mouse events is handled in Node subclasses.
76170 * @namespace YAHOO.widget
76171 * @class Node
76172 * @uses YAHOO.util.EventProvider
76173 * @param oData {object} a string or object containing the data that will
76174 * be used to render this node, and any custom attributes that should be
76175 * stored with the node (which is available in noderef.data).
76176 * All values in oData will be used to set equally named properties in the node
76177 * as long as the node does have such properties, they are not undefined, private or functions,
76178 * the rest of the values will be stored in noderef.data
76179 * @param oParent {Node} this node's parent node
76180 * @param expanded {boolean} the initial expanded/collapsed state (deprecated, use oData.expanded)
76181 * @constructor
76182 */
76183YAHOO.widget.Node = function(oData, oParent, expanded) {
76184    if (oData) { this.init(oData, oParent, expanded); }
76185};
76186
76187YAHOO.widget.Node.prototype = {
76188
76189    /**
76190     * The index for this instance obtained from global counter in YAHOO.widget.TreeView.
76191     * @property index
76192     * @type int
76193     */
76194    index: 0,
76195
76196    /**
76197     * This node's child node collection.
76198     * @property children
76199     * @type Node[]
76200     */
76201    children: null,
76202
76203    /**
76204     * Tree instance this node is part of
76205     * @property tree
76206     * @type TreeView
76207     */
76208    tree: null,
76209
76210    /**
76211     * The data linked to this node.  This can be any object or primitive
76212     * value, and the data can be used in getNodeHtml().
76213     * @property data
76214     * @type object
76215     */
76216    data: null,
76217
76218    /**
76219     * Parent node
76220     * @property parent
76221     * @type Node
76222     */
76223    parent: null,
76224
76225    /**
76226     * The depth of this node.  We start at -1 for the root node.
76227     * @property depth
76228     * @type int
76229     */
76230    depth: -1,
76231
76232    /**
76233     * The node's expanded/collapsed state
76234     * @property expanded
76235     * @type boolean
76236     */
76237    expanded: false,
76238
76239    /**
76240     * Can multiple children be expanded at once?
76241     * @property multiExpand
76242     * @type boolean
76243     */
76244    multiExpand: true,
76245
76246    /**
76247     * Should we render children for a collapsed node?  It is possible that the
76248     * implementer will want to render the hidden data...  @todo verify that we
76249     * need this, and implement it if we do.
76250     * @property renderHidden
76251     * @type boolean
76252     */
76253    renderHidden: false,
76254
76255    /**
76256     * This flag is set to true when the html is generated for this node's
76257     * children, and set to false when new children are added.
76258     * @property childrenRendered
76259     * @type boolean
76260     */
76261    childrenRendered: false,
76262
76263    /**
76264     * Dynamically loaded nodes only fetch the data the first time they are
76265     * expanded.  This flag is set to true once the data has been fetched.
76266     * @property dynamicLoadComplete
76267     * @type boolean
76268     */
76269    dynamicLoadComplete: false,
76270
76271    /**
76272     * This node's previous sibling
76273     * @property previousSibling
76274     * @type Node
76275     */
76276    previousSibling: null,
76277
76278    /**
76279     * This node's next sibling
76280     * @property nextSibling
76281     * @type Node
76282     */
76283    nextSibling: null,
76284
76285    /**
76286     * We can set the node up to call an external method to get the child
76287     * data dynamically.
76288     * @property _dynLoad
76289     * @type boolean
76290     * @private
76291     */
76292    _dynLoad: false,
76293
76294    /**
76295     * Function to execute when we need to get this node's child data.
76296     * @property dataLoader
76297     * @type function
76298     */
76299    dataLoader: null,
76300
76301    /**
76302     * This is true for dynamically loading nodes while waiting for the
76303     * callback to return.
76304     * @property isLoading
76305     * @type boolean
76306     */
76307    isLoading: false,
76308
76309    /**
76310     * The toggle/branch icon will not show if this is set to false.  This
76311     * could be useful if the implementer wants to have the child contain
76312     * extra info about the parent, rather than an actual node.
76313     * @property hasIcon
76314     * @type boolean
76315     */
76316    hasIcon: true,
76317    /**
76318     * Attribute used for setting custom style class for tree node.
76319     */
76320    customCls: null,
76321    /**
76322     * Used to configure what happens when a dynamic load node is expanded
76323     * and we discover that it does not have children.  By default, it is
76324     * treated as if it still could have children (plus/minus icon).  Set
76325     * iconMode to have it display like a leaf node instead.
76326     * @property iconMode
76327     * @type int
76328     */
76329    iconMode: 0,
76330
76331    /**
76332     * Specifies whether or not the content area of the node should be allowed
76333     * to wrap.
76334     * @property nowrap
76335     * @type boolean
76336     * @default false
76337     */
76338    nowrap: false,
76339
76340 /**
76341     * If true, the node will alway be rendered as a leaf node.  This can be
76342     * used to override the presentation when dynamically loading the entire
76343     * tree.  Setting this to true also disables the dynamic load call for the
76344     * node.
76345     * @property isLeaf
76346     * @type boolean
76347     * @default false
76348     */
76349    isLeaf: false,
76350
76351/**
76352     * The CSS class for the html content container.  Defaults to ygtvhtml, but
76353     * can be overridden to provide a custom presentation for a specific node.
76354     * @property contentStyle
76355     * @type string
76356     */
76357    contentStyle: "",
76358
76359
76360    /**
76361     * The generated id that will contain the data passed in by the implementer.
76362     * @property contentElId
76363     * @type string
76364     */
76365    contentElId: null,
76366
76367/**
76368 * Enables node highlighting.  If true, the node can be highlighted and/
76368or propagate highlighting
76369 * @property enableHighlight
76370 * @type boolean
76371 * @default true
76372 */
76373    enableHighlight: true,
76374
76375/**
76376 * Stores the highlight state.  Can be any of:
76377 * <ul>
76378 * <li>0 - not highlighted</li>
76379 * <li>1 - highlighted</li>
76380 * <li>2 - some children highlighted</li>
76381 * </ul>
76382 * @property highlightState
76383 * @type integer
76384 * @default 0
76385 */
76386
76387 highlightState: 0,
76388
76389 /**
76390 * Tells whether highlighting will be propagated up to the parents of the clicked node
76391 * @property propagateHighlightUp
76392 * @type boolean
76393 * @default false
76394 */
76395
76396 propagateHighlightUp: false,
76397
76398 /**
76399 * Tells whether highlighting will be propagated down to the children of the clicked node
76400 * @property propagateHighlightDown
76401 * @type boolean
76402 * @default false
76403 */
76404
76405 propagateHighlightDown: false,
76406
76407 /**
76408  * User-defined className to be added to the Node
76409  * @property className
76410  * @type string
76411  * @default null
76412  */
76413
76414 className: null,
76415
76416 /**
76417     * The node type
76418     * @property _type
76419     * @private
76420     * @type string
76421     * @default "Node"
76422*/
76423    _type: "Node",
76424
76425    /*
76426    spacerPath: "http://l.yimg.com/a/i/space.gif",
76427    expandedText: "Expanded",
76428    collapsedText: "Collapsed",
76429    loadingText: "Loading",
76430    */
76431
76432    /**
76433     * Initializes this node, gets some of the properties from the parent
76434     * @method init
76435     * @param oData {object} a string or object containing the data that will
76436     * be used to render this node
76437     * @param oParent {Node} this node's parent node
76438     * @param expanded {boolean} the initial expanded/collapsed state
76439     */
76440    init: function(oData, oParent, expanded) {
76441
76442        this.data = {};
76443        this.children   = [];
76444        this.index      = YAHOO.widget.TreeView.nodeCount;
76445        ++YAHOO.widget.TreeView.nodeCount;
76446        this.contentElId = "ygtvcontentel" + this.index;
76447
76448        if (Lang.isObject(oData)) {
76449            for (var property in oData) {
76450                if (oData.hasOwnProperty(property)) {
76451                    if (property.charAt(0) != '_'  && !Lang.isUndefined(this[property]) && !Lang.isFunction(this[property]) ) {
76452                        this[property] = oData[property];
76453                    } else {
76454                        this.data[property] = oData[property];
76455                    }
76456                }
76457            }
76458        }
76459        if (!Lang.isUndefined(expanded) ) { this.expanded  = expanded;  }
76460
76461        this.logger     = new YAHOO.widget.LogWriter(this.toString());
76462
76463        /**
76464         * The parentChange event is fired when a parent element is applied
76465         * to the node.  This is useful if you need to apply tree-level
76466         * properties to a tree that need to happen if a node is moved from
76467         * one tree to another.
76468         *
76469         * @event parentChange
76470         * @type CustomEvent
76471         */
76472        this.createEvent("parentChange", this);
76473
76474        // oParent should never be null except when we create the root node.
76475        if (oParent) {
76476            oParent.appendChild(this);
76477        }
76478    },
76479
76480    /**
76481     * Certain properties for the node cannot be set until the parent
76482     * is known. This is called after the node is inserted into a tree.
76483     * the parent is also applied to this node's children in order to
76484     * make it possible to move a branch from one tree to another.
76485     * @method applyParent
76486     * @param {Node} parentNode this node's parent node
76487     * @return {boolean} true if the application was successful
76488     */
76489    applyParent: function(parentNode) {
76490        if (!parentNode) {
76491            return false;
76492        }
76493
76494        this.tree   = parentNode.tree;
76495        this.parent = parentNode;
76496        this.depth  = parentNode.depth + 1;
76497
76498        // @todo why was this put here.  This causes new nodes added at the
76499        // root level to lose the menu behavior.
76500        // if (! this.multiExpand) {
76501            // this.multiExpand = parentNode.multiExpand;
76502        // }
76503
76504        this.tree.regNode(this);
76505        parentNode.childrenRendered = false;
76506
76507        // cascade update existing children
76508        for (var i=0, len=this.children.length;i<len;++i) {
76509            this.children[i].applyParent(this);
76510        }
76511
76512        this.fireEvent("parentChange");
76513
76514        return true;
76515    },
76516
76517    /**
76518     * Appends a node to the child collection.
76519     * @method appendChild
76520     * @param childNode {Node} the new node
76521     * @return {Node} the child node
76522     * @private
76523     */
76524    appendChild: function(childNode) {
76525        if (this.hasChildren()) {
76526            var sib = this.children[this.children.length - 1];
76527            sib.nextSibling = childNode;
76528            childNode.previousSibling = sib;
76529        }
76530        this.children[this.children.length] = childNode;
76531        childNode.applyParent(this);
76532
76533        // part of the IE display issue workaround. If child nodes
76534        // are added after the initial render, and the node was
76535        // instantiated with expanded = true, we need to show the
76536        // children div now that the node has a child.
76537        if (this.childrenRendered && this.expanded) {
76538            this.getChildrenEl().style.display = "";
76539        }
76540
76541        return childNode;
76542    },
76543
76544    /**
76545     * Appends this node to the supplied node's child collection
76546     * @method appendTo
76547     * @param parentNode {Node} the node to append to.
76548     * @return {Node} The appended node
76549     */
76550    appendTo: function(parentNode) {
76551        return parentNode.appendChild(this);
76552    },
76553
76554    /**
76555    * Inserts this node before this supplied node
76556    * @method insertBefore
76557    * @param node {Node} the node to insert this node before
76558    * @return {Node} the inserted node
76559    */
76560    insertBefore: function(node) {
76561        this.logger.log("insertBefore: " + node);
76562        var p = node.parent;
76563        if (p) {
76564
76565            if (this.tree) {
76566                this.tree.popNode(this);
76567            }
76568
76569            var refIndex = node.isChildOf(p);
76570            //this.logger.log(refIndex);
76571            p.children.splice(refIndex, 0, this);
76572            if (node.previousSibling) {
76573                node.previousSibling.nextSibling = this;
76574            }
76575            this.previousSibling = node.previousSibling;
76576            this.nextSibling = node;
76577            node.previousSibling = this;
76578
76579            this.applyParent(p);
76580        }
76581
76582        return this;
76583    },
76584
76585    /**
76586    * Inserts this node after the supplied node
76587    * @method insertAfter
76588    * @param node {Node} the node to insert after
76589    * @return {Node} the inserted node
76590    */
76591    insertAfter: function(node) {
76592        this.logger.log("insertAfter: " + node);
76593        var p = node.parent;
76594        if (p) {
76595
76596            if (this.tree) {
76597                this.tree.popNode(this);
76598            }
76599
76600            var refIndex = node.isChildOf(p);
76601            this.logger.log(refIndex);
76602
76603            if (!node.nextSibling) {
76604                this.nextSibling = null;
76605                return this.appendTo(p);
76606            }
76607
76608            p.children.splice(refIndex + 1, 0, this);
76609
76610            node.nextSibling.previousSibling = this;
76611            this.previousSibling = node;
76612            this.nextSibling = node.nextSibling;
76613            node.nextSibling = this;
76614
76615            this.applyParent(p);
76616        }
76617
76618        return this;
76619    },
76620
76621    /**
76622    * Returns true if the Node is a child of supplied Node
76623    * @method isChildOf
76624    * @param parentNode {Node} the Node to check
76625    * @return {boolean} The node index if this Node is a child of
76626    *                   supplied Node, else -1.
76627    * @private
76628    */
76629    isChildOf: function(parentNode) {
76630        if (parentNode && parentNode.children) {
76631            for (var i=0, len=parentNode.children.length; i<len ; ++i) {
76632                if (parentNode.children[i] === this) {
76633                    return i;
76634                }
76635            }
76636        }
76637
76638        return -1;
76639    },
76640
76641    /**
76642     * Returns a node array of this node's siblings, null if none.
76643     * @method getSiblings
76644     * @return Node[]
76645     */
76646    getSiblings: function() {
76647        var sib =  this.parent.children.slice(0);
76648        for (var i=0;i < sib.length && sib[i] != this;i++) {}
76649        sib.splice(i,1);
76650        if (sib.length) { return sib; }
76651        return null;
76652    },
76653
76654    /**
76655     * Shows this node's children
76656     * @method showChildren
76657     */
76658    showChildren: function() {
76659        if (!this.tree.animateExpand(this.getChildrenEl(), this)) {
76660            if (this.hasChildren()) {
76661                this.getChildrenEl().style.display = "";
76662            }
76663        }
76664    },
76665
76666    /**
76667     * Hides this node's children
76668     * @method hideChildren
76669     */
76670    hideChildren: function() {
76671        this.logger.log("hiding " + this.index);
76672
76673        if (!this.tree.animateCollapse(this.getChildrenEl(), this)) {
76674            this.getChildrenEl().style.display = "none";
76675        }
76676    },
76677
76678    /**
76679     * Returns the id for this node's container div
76680     * @method getElId
76681     * @return {string} the element id
76682     */
76683    getElId: function() {
76684        return "ygtv" + this.index;
76685    },
76686
76687    /**
76688     * Returns the id for this node's children div
76689     * @method getChildrenElId
76690     * @return {string} the element id for this node's children div
76691     */
76692    getChildrenElId: function() {
76693        return "ygtvc" + this.index;
76694    },
76695
76696    /**
76697     * Returns the id for this node's toggle element
76698     * @method getToggleElId
76699     * @return {string} the toggel element id
76700     */
76701    getToggleElId: function() {
76702        return "ygtvt" + this.index;
76703    },
76704
76705
76706    /*
76707     * Returns the id for this node's spacer image.  The spacer is positioned
76708     * over the toggle and provides feedback for screen readers.
76709     * @method getSpacerId
76710     * @return {string} the id for the spacer image
76711     */
76712    /*
76713    getSpacerId: function() {
76714        return "ygtvspacer" + this.index;
76715    },
76716    */
76717
76718    /**
76719     * Returns this node's container html element
76720     * @method getEl
76721     * @return {HTMLElement} the container html element
76722     */
76723    getEl: function() {
76724        return Dom.get(this.getElId());
76725    },
76726
76727    /**
76728     * Returns the div that was generated for this node's children
76729     * @method getChildrenEl
76730     * @return {HTMLElement} this node's children div
76731     */
76732    getChildrenEl: function() {
76733        return Dom.get(this.getChildrenElId());
76734    },
76735
76736    /**
76737     * Returns the element that is being used for this node's toggle.
76738     * @method getToggleEl
76739     * @return {HTMLElement} this node's toggle html element
76740     */
76741    getToggleEl: function() {
76742        return Dom.get(this.getToggleElId());
76743    },
76744    /**
76745    * Returns the outer html element for this node's content
76746    * @method getContentEl
76747    * @return {HTMLElement} the element
76748    */
76749    getContentEl: function() {
76750        return Dom.get(this.contentElId);
76751    },
76752
76753
76754    /*
76755     * Returns the element that is being used for this node's spacer.
76756     * @method getSpacer
76757     * @return {HTMLElement} this node's spacer html element
76758     */
76759    /*
76760    getSpacer: function() {
76761        return document.getElementById( this.getSpacerId() ) || {};
76762    },
76763    */
76764
76765    /*
76766    getStateText: function() {
76767        if (this.isLoading) {
76768            return this.loadingText;
76769        } else if (this.hasChildren(true)) {
76770            if (this.expanded) {
76771                return this.expandedText;
76772            } else {
76773                return this.collapsedText;
76774            }
76775        } else {
76776            return "";
76777        }
76778    },
76779    */
76780
76781  /**
76782     * Hides this nodes children (creating them if necessary), changes the toggle style.
76783     * @method collapse
76784     */
76785    collapse: function() {
76786        // Only collapse if currently expanded
76787        if (!this.expanded) { return; }
76788
76789        // fire the collapse event handler
76790        var ret = this.tree.onCollapse(this);
76791
76792        if (false === ret) {
76793            this.logger.log("Collapse was stopped by the abstract onCollapse");
76794            return;
76795        }
76796
76797        ret = this.tree.fireEvent("collapse", this);
76798
76799        if (false === ret) {
76800            this.logger.log("Collapse was stopped by a custom event handler");
76801            return;
76802        }
76803
76804
76805        if (!this.getEl()) {
76806            this.expanded = false;
76807        } else {
76808            // hide the child div
76809            this.hideChildren();
76810            this.expanded = false;
76811
76812            this.updateIcon();
76813        }
76814
76815        // this.getSpacer().title = this.getStateText();
76816
76817        ret = this.tree.fireEvent("collapseComplete", this);
76818
76819    },
76820
76821    /**
76822     * Shows this nodes children (creating them if necessary), changes the
76823     * toggle style, and collapses its siblings if multiExpand is not set.
76824     * @method expand
76825     */
76826    expand: function(lazySource) {
76827        // Only expand if currently collapsed.
76828        if (this.isLoading || (this.expanded && !lazySource)) {
76829            return;
76830        }
76831
76832        var ret = true;
76833
76834        // When returning from the lazy load handler, expand is called again
76835        // in order to render the new children.  The "expand" event already
76836        // fired before fething the new data, so we need to skip it now.
76837        if (!lazySource) {
76838            // fire the expand event handler
76839            ret = this.tree.onExpand(this);
76840
76841            if (false === ret) {
76842                this.logger.log("Expand was stopped by the abstract onExpand");
76843                return;
76844            }
76845
76846            ret = this.tree.fireEvent("expand", this);
76847        }
76848
76849        if (false === ret) {
76850            this.logger.log("Expand was stopped by the custom event handler");
76851            return;
76852        }
76853
76854        if (!this.getEl()) {
76855            this.expanded = true;
76856            return;
76857        }
76858
76859        if (!this.childrenRendered) {
76860            this.logger.log("children not rendered yet");
76861            this.getChildrenEl().innerHTML = this.renderChildren();
76862        } else {
76863            this.logger.log("children already rendered");
76864        }
76865
76866        this.expanded = true;
76867
76868        this.updateIcon();
76869
76870        // this.getSpacer().title = this.getStateText();
76871
76872        // We do an extra check for children here because the lazy
76873        // load feature can expose nodes that have no children.
76874
76875        // if (!this.hasChildren()) {
76876        if (this.isLoading) {
76877            this.expanded = false;
76878            return;
76879        }
76880
76881        if (! this.multiExpand) {
76882            var sibs = this.getSiblings();
76883            for (var i=0; sibs && i<sibs.length; ++i) {
76884                if (sibs[i] != this && sibs[i].expanded) {
76885                    sibs[i].collapse();
76886                }
76887            }
76888        }
76889
76890        this.showChildren();
76891
76892        ret = this.tree.fireEvent("expandComplete", this);
76893    },
76894
76895    updateIcon: function() {
76896        if (this.hasIcon) {
76897            var el = this.getToggleEl();
76898            if (el) {
76899               // Alfresco Changes Start
76900               if (this._customRegEx(this.customCls) && this._customRegEx(this.customCls).test(el.className))
76901               {
76902                  el.className = el.className.replace(this._customRegEx(this.customCls), this.getStyle());
76903               }
76904               else
76905               {
76906                  el.className = el.className.replace(/\bygtv(([tl][pmn]h?)|(loading))\b/gi, this.get
76906Style());
76907               }
76908               // Alfresco Changes End
76909            }
76910        }
76911        el = Dom.get('ygtvtableel' + this.index);
76912        if (el) {
76913            if (this.expanded) {
76914                Dom.replaceClass(el,'ygtv-collapsed','ygtv-expanded');
76915            } else {
76916                Dom.replaceClass(el,'ygtv-expanded','ygtv-collapsed');
76917            }
76918        }
76919    },
76920
76921    /**
76922     * Returns the css style name for the toggle
76923     * @method getStyle
76924     * @return {string} the css class for this node's toggle
76925     */
76926      getStyle : function()
76927      {
76928         // this.logger.log("No children, " + " isDyanmic: " + this.isDynamic() + " expanded: " + this.expanded);
76929         if (this.isLoading)
76930         {
76931            this.logger.log("returning the loading icon");
76932            return "ygtvloading";
76933         }
76934         else
76935         {
76936            // type p=plus(expand), m=minus(collapase), n=none(no children)
76937            var type = "n";
76938            if (this.hasChildren(true) || (this.isDynamic() && !this.getIconMode()))
76939            {
76940               // if (this.hasChildren(true)) {
76941               type = (this.expanded) ? "m" : "p";
76942            }
76943            // Alfresco Changes Start
76944            if (this.customCls)
76945            {
76946               return this.customCls+type;
76947            }
76948            else
76949            {
76950               // location top or bottom, middle nodes also get the top style
76951               var loc = (this.nextSibling) ? "t" : "l";
76952               return "ygtv" + loc + type;
76953            }
76954            // Alfresco Changes End
76955         }
76956      },
76957
76958    /**
76959     * Returns the hover style for the icon
76960     * @return {string} the css class hover state
76961     * @method getHoverStyle
76962     */
76963    getHoverStyle: function() {
76964        var s = this.getStyle();
76965        if (this.hasChildren(true) && !this.isLoading) {
76966            s += "h";
76967        }
76968        return s;
76969    },
76970
76971    /**
76972     * Recursively expands all of this node's children.
76973     * @method expandAll
76974     */
76975    expandAll: function() {
76976        var l = this.children.length;
76977        for (var i=0;i<l;++i) {
76978            var c = this.children[i];
76979            if (c.isDynamic()) {
76980                this.logger.log("Not supported (lazy load + expand all)");
76981                break;
76982            } else if (! c.multiExpand) {
76983                this.logger.log("Not supported (no multi-expand + expand all)");
76984                break;
76985            } else {
76986                c.expand();
76987                c.expandAll();
76988            }
76989        }
76990    },
76991
76992    /**
76993     * Recursively collapses all of this node's children.
76994     * @method collapseAll
76995     */
76996    collapseAll: function() {
76997        for (var i=0;i<this.children.length;++i) {
76998            this.children[i].collapse();
76999            this.children[i].collapseAll();
77000        }
77001    },
77002
77003    /**
77004     * Configures this node for dynamically obtaining the child data
77005     * when the node is first expanded.  Calling it without the callback
77006     * will turn off dynamic load for the node.
77007     * @method setDynamicLoad
77008     * @param fmDataLoader {function} the function that will be used to get the data.
77009     * @param iconMode {int} configures the icon that is displayed when a dynamic
77010     * load node is expanded the first time without children.  By default, the
77011     * "collapse" icon will be used.  If set to 1, the leaf node icon will be
77012     * displayed.
77013     */
77014    setDynamicLoad: function(fnDataLoader, iconMode) {
77015        if (fnDataLoader) {
77016            this.dataLoader = fnDataLoader;
77017            this._dynLoad = true;
77018        } else {
77019            this.dataLoader = null;
77020            this._dynLoad = false;
77021        }
77022
77023        if (iconMode) {
77024            this.iconMode = iconMode;
77025        }
77026    },
77027
77028    /**
77029     * Evaluates if this node is the root node of the tree
77030     * @method isRoot
77031     * @return {boolean} true if this is the root node
77032     */
77033    isRoot: function() {
77034        return (this == this.tree.root);
77035    },
77036
77037    /**
77038     * Evaluates if this node's children should be loaded dynamically.  Looks for
77039     * the property both in this instance and the root node.  If the tree is
77040     * defined to load all children dynamically, the data callback function is
77041     * defined in the root node
77042     * @method isDynamic
77043     * @return {boolean} true if this node's children are to be loaded dynamically
77044     */
77045    isDynamic: function() {
77046        if (this.isLeaf) {
77047            return false;
77048        } else {
77049            return (!this.isRoot() && (this._dynLoad || this.tree.root._dynLoad));
77050            // this.logger.log("isDynamic: " + lazy);
77051            // return lazy;
77052        }
77053    },
77054
77055    /**
77056     * Returns the current icon mode.  This refers to the way childless dynamic
77057     * load nodes appear (this comes into play only after the initial dynamic
77058     * load request produced no children).
77059     * @method getIconMode
77060     * @return {int} 0 for collapse style, 1 for leaf node style
77061     */
77062    getIconMode: function() {
77063        return (this.iconMode || this.tree.root.iconMode);
77064    },
77065
77066    /**
77067     * Checks if this node has children.  If this node is lazy-loading and the
77068     * children have not been rendered, we do not know whether or not there
77069     * are actual children.  In most cases, we need to assume that there are
77070     * children (for instance, the toggle needs to show the expandable
77071     * presentation state).  In other times we want to know if there are rendered
77072     * children.  For the latter, "checkForLazyLoad" should be false.
77073     * @method hasChildren
77074     * @param checkForLazyLoad {boolean} should we check for unloaded children?
77075     * @return {boolean} true if this has children or if it might and we are
77076     * checking for this condition.
77077     */
77078    hasChildren: function(checkForLazyLoad) {
77079        if (this.isLeaf) {
77080            return false;
77081        } else {
77082            return ( this.children.length > 0 ||
77083                (checkForLazyLoad && this.isDynamic() && !this.dynamicLoadComplete)
77084            );
77085        }
77086    },
77087
77088    /**
77089     * Expands if node is collapsed, collapses otherwise.
77090     * @method toggle
77091     */
77092    toggle: function() {
77093        if (!this.tree.locked && ( this.hasChildren(true) || this.isDynamic()) ) {
77094            if (this.expanded) { this.collapse(); } else { this.expand(); }
77095        }
77096    },
77097
77098    /**
77099     * Returns the markup for this node and its children.
77100     * @method getHtml
77101     * @return {string} the markup for this node and its expanded children.
77102     */
77103    getHtml: function() {
77104
77105        this.childrenRendered = false;
77106
77107        return ['<div class="ygtvitem" id="' , this.getElId() , '">' ,this.getNodeHtml() , this.getChildrenHtml() ,'</div>'].join("");
77108    },
77109
77110    /**
77111     * Called when first rendering the tree.  We always build the div that will
77112     * contain this nodes children, but we don't render the children themselves
77113     * unless this node is expanded.
77114     * @method getChildrenHtml
77115     * @return {string} the children container div html and any expanded children
77116     * @private
77117     */
77118    getChildrenHtml: function() {
77119
77120
77121        var sb = [];
77122        sb[sb.length] = '<div class="ygtvchildren" id="' + this.getChildrenElId() + '"';
77123
77124        // This is a workaround for an IE rendering issue, the child div has layout
77125        // in IE, creating extra space if a leaf node is created with the exp
77125anded
77126        // property set to true.
77127        if (!this.expanded || !this.hasChildren()) {
77128            sb[sb.length] = ' style="display:none;"';
77129        }
77130        sb[sb.length] = '>';
77131
77132        // this.logger.log(["index", this.index,
77133                         // "hasChildren", this.hasChildren(true),
77134                         // "expanded", this.expanded,
77135                         // "renderHidden", this.renderHidden,
77136                         // "isDynamic", this.isDynamic()]);
77137
77138        // Don't render the actual child node HTML unless this node is expanded.
77139        if ( (this.hasChildren(true) && this.expanded) ||
77140                (this.renderHidden && !this.isDynamic()) ) {
77141            sb[sb.length] = this.renderChildren();
77142        }
77143
77144        sb[sb.length] = '</div>';
77145
77146        return sb.join("");
77147    },
77148
77149    /**
77150     * Generates the markup for the child nodes.  This is not done until the node
77151     * is expanded.
77152     * @method renderChildren
77153     * @return {string} the html for this node's children
77154     * @private
77155     */
77156    renderChildren: function() {
77157
77158        this.logger.log("rendering children for " + this.index);
77159
77160        var node = this;
77161
77162        if (this.isDynamic() && !this.dynamicLoadComplete) {
77163            this.isLoading = true;
77164            this.tree.locked = true;
77165
77166            if (this.dataLoader) {
77167                this.logger.log("Using dynamic loader defined for this node");
77168
77169                setTimeout(
77170                    function() {
77171                        node.dataLoader(node,
77172                            function() {
77173                                node.loadComplete();
77174                            });
77175                    }, 10);
77176
77177            } else if (this.tree.root.dataLoader) {
77178                this.logger.log("Using the tree-level dynamic loader");
77179
77180                setTimeout(
77181                    function() {
77182                        node.tree.root.dataLoader(node,
77183                            function() {
77184                                node.loadComplete();
77185                            });
77186                    }, 10);
77187
77188            } else {
77189                this.logger.log("no loader found");
77190                return "Error: data loader not found or not specified.";
77191            }
77192
77193            return "";
77194
77195        } else {
77196            return this.completeRender();
77197        }
77198    },
77199
77200    /**
77201     * Called when we know we have all the child data.
77202     * @method completeRender
77203     * @return {string} children html
77204     */
77205    completeRender: function() {
77206        this.logger.log("completeRender: " + this.index + ", # of children: " + this.children.length);
77207        var sb = [];
77208
77209        for (var i=0; i < this.children.length; ++i) {
77210            // this.children[i].childrenRendered = false;
77211            sb[sb.length] = this.children[i].getHtml();
77212        }
77213
77214        this.childrenRendered = true;
77215
77216        return sb.join("");
77217    },
77218
77219    /**
77220     * Load complete is the callback function we pass to the data provider
77221     * in dynamic load situations.
77222     * @method loadComplete
77223     */
77224    loadComplete: function() {
77225        this.logger.log(this.index + " loadComplete, children: " + this.children.length);
77226        this.getChildrenEl().innerHTML = this.completeRender();
77227        if (this.propagateHighlightDown) {
77228            if (this.highlightState === 1 && !this.tree.singleNodeHighlight) {
77229                for (var i = 0; i < this.children.length; i++) {
77230                this.children[i].highlight(true);
77231            }
77232            } else if (this.highlightState === 0 || this.tree.singleNodeHighlight) {
77233                for (i = 0; i < this.children.length; i++) {
77234                    this.children[i].unhighlight(true);
77235                }
77236            } // if (highlighState == 2) leave child nodes with whichever highlight state they are set
77237        }
77238
77239        this.dynamicLoadComplete = true;
77240        this.isLoading = false;
77241        this.expand(true);
77242        this.tree.locked = false;
77243    },
77244
77245    /**
77246     * Returns this node's ancestor at the specified depth.
77247     * @method getAncestor
77248     * @param {int} depth the depth of the ancestor.
77249     * @return {Node} the ancestor
77250     */
77251    getAncestor: function(depth) {
77252        if (depth >= this.depth || depth < 0)  {
77253            this.logger.log("illegal getAncestor depth: " + depth);
77254            return null;
77255        }
77256
77257        var p = this.parent;
77258
77259        while (p.depth > depth) {
77260            p = p.parent;
77261        }
77262
77263        return p;
77264    },
77265
77266    /**
77267     * Returns the css class for the spacer at the specified depth for
77268     * this node.  If this node's ancestor at the specified depth
77269     * has a next sibling the presentation is different than if it
77270     * does not have a next sibling
77271     * @method getDepthStyle
77272     * @param {int} depth the depth of the ancestor.
77273     * @return {string} the css class for the spacer
77274     */
77275    getDepthStyle: function(depth) {
77276        return (this.getAncestor(depth).nextSibling) ?
77277            "ygtvdepthcell" : "ygtvblankdepthcell";
77278    },
77279
77280    /**
77281     * Get the markup for the node.  This may be overrided so that we can
77282     * support different types of nodes.
77283     * @method getNodeHtml
77284     * @return {string} The HTML that will render this node.
77285     */
77286    getNodeHtml: function() {
77287        this.logger.log("Generating html");
77288        var sb = [];
77289
77290        sb[sb.length] = '<table id="ygtvtableel' + this.index + '" border="0" cellpadding="0" cellspacing="0" class="ygtvtable ygtvdepth' + this.depth;
77291        sb[sb.length] = ' ygtv-' + (this.expanded?'expanded':'collapsed');
77292        if (this.enableHighlight) {
77293            sb[sb.length] = ' ygtv-highlight' + this.highlightState;
77294        }
77295        if (this.className) {
77296            sb[sb.length] = ' ' + this.className;
77297        }
77298        sb[sb.length] = '"><tr class="ygtvrow">';
77299
77300        for (var i=0;i<this.depth;++i) {
77301            sb[sb.length] = '<td class="ygtvcell ' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
77302        }
77303
77304        if (this.hasIcon) {
77305            sb[sb.length] = '<td id="' + this.getToggleElId();
77306            sb[sb.length] = '" class="ygtvcell ';
77307            sb[sb.length] = this.getStyle() ;
77308            sb[sb.length] = '"><a href="#" class="ygtvspacer">&#160;</a></td>';
77309        }
77310
77311        sb[sb.length] = '<td id="' + this.contentElId;
77312        sb[sb.length] = '" class="ygtvcell ';
77313        sb[sb.length] = this.contentStyle  + ' ygtvcontent" ';
77314        sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
77315        sb[sb.length] = ' >';
77316        sb[sb.length] = this.getContentHtml();
77317        sb[sb.length] = '</td></tr></table>';
77318
77319        return sb.join("");
77320
77321    },
77322    /**
77323     * Get the markup for the contents of the node.  This is designed to be overrided so that we can
77324     * support different types of nodes.
77325     * @method getContentHtml
77326     * @return {string} The HTML that will render the content of this node.
77327     */
77328    getContentHtml: function () {
77329        return "";
77330    },
77331
77332    /**
77333     * Regenerates the html for this node and its children.  To be used when the
77334     * node is expanded and new children have been added.
77335     * @method refresh
77336     */
77337    refresh: function() {
77338        // this.loadComplete();
77339        this.getChildrenEl().innerHTML = this.completeRender();
77340
77341        if (this.hasIcon) {
77342            var el = this.getToggleEl();
77343            if (el) {
77344                el.className = el.className.replace(/\bygtv[lt][nmp]h*\b/gi,this.getStyle());
77345            }
77346        }
77347    },
77348
77349    /**
77350     * Node toString
77351     * @method toString
77352     * @return {string} string representation of the node
77353     */
77354    toString: function() {
77355        return this._type + " (" + this.index + ")";
77356    },
77357    /**
77358    * array of items that had the focus set on them
77359    * so that they can be cleaned when focus is lost
77360    * @property _focusHighlightedItems
77361    * @type Array of DOM elements
77362    * @private
77363    */
77364    _focusHighlightedItems: [],
77365    /**
77366    * DOM element that actually got the browser focus
77367    * @property _focusedItem
77368    * @type DOM element
77369    * @private
77370    */
77371    _focusedItem: null,
77372
77373    /**
77374    * Returns true if there are any elements in the node that can
77375    * accept the real actual browser focus
77376    * @method _canHaveFocus
77377    * @return {boolean} success
77378    * @private
77379    */
77380    _canHaveFocus: function() {
77381        return this.getEl().getElementsByTagName('a').length > 0;
77382    },
77383    /**
77384    * Removes the focus of previously selected Node
77385    * @method _removeFocus
77386    * @private
77387    */
77388    _removeFocus:function () {
77389        if (this._focusedItem) {
77390            Event.removeListener(this._focusedItem,'blur');
77391            this._focusedItem = null;
77392        }
77393        var el;
77394        while ((el = this._focusHighlightedItems.shift())) {  // yes, it is meant as an assignment, really
77395            Dom.removeClass(el,YAHOO.widget.TreeView.FOCUS_CLASS_NAME );
77396        }
77397    },
77398    /**
77399    * Sets the focus on the node element.
77400    * It will only be able to set the focus on nodes that have anchor elements in it.
77401    * Toggle or branch icons have anchors and can be focused on.
77402    * If will fail in nodes that have no anchor
77403    * @method focus
77404    * @return {boolean} success
77405    */
77406    focus: function () {
77407        var focused = false, self = this;
77408
77409        if (this.tree.currentFocus) {
77410            this.tree.currentFocus._removeFocus();
77411        }
77412
77413        var  expandParent = function (node) {
77414            if (node.parent) {
77415                expandParent(node.parent);
77416                node.parent.expand();
77417            }
77418        };
77419        expandParent(this);
77420
77421        Dom.getElementsBy  (
77422            function (el) {
77423                return (/ygtv(([tl][pmn]h?)|(content))/).test(el.className);
77424            } ,
77425            'td' ,
77426            self.getEl().firstChild ,
77427            function (el) {
77428                Dom.addClass(el, YAHOO.widget.TreeView.FOCUS_CLASS_NAME );
77429                if (!focused) {
77430                    var aEl = el.getElementsByTagName('a');
77431                    if (aEl.length) {
77432                        aEl = aEl[0];
77433                        aEl.focus();
77434                        self._focusedItem = aEl;
77435                        Event.on(aEl,'blur',function () {
77436                            self.tree.fireEvent('focusChanged',{oldNode:self.tree.currentFocus,newNode:null});
77437                            self.tree.currentFocus = null;
77438                            self._removeFocus();
77439                        });
77440                        focused = true;
77441                    }
77442                }
77443                self._focusHighlightedItems.push(el);
77444            }
77445        );
77446        if (focused) {
77447            this.tree.fireEvent('focusChanged',{oldNode:this.tree.currentFocus,newNode:this});
77448            this.tree.currentFocus = this;
77449        } else {
77450            this.tree.fireEvent('focusChanged',{oldNode:self.tree.currentFocus,newNode:null});
77451            this.tree.currentFocus = null;
77452            this._removeFocus();
77453        }
77454        return focused;
77455    },
77456
77457  /**
77458     * Count of nodes in a branch
77459     * @method getNodeCount
77460     * @return {int} number of nodes in the branch
77461     */
77462    getNodeCount: function() {
77463        for (var i = 0, count = 0;i< this.children.length;i++) {
77464            count += this.children[i].getNodeCount();
77465        }
77466        return count + 1;
77467    },
77468
77469      /**
77470     * Returns an object which could be used to build a tree out of this node and its children.
77471     * It can be passed to the tree constructor to reproduce this node as a tree.
77472     * It will return false if the node or any children loads dynamically, regardless of whether it is loaded or not.
77473     * @method getNodeDefinition
77474     * @return {Object | false}  definition of the tree or false if the node or any children is defined as dynamic
77475     */
77476    getNodeDefinition: function() {
77477
77478        if (this.isDynamic()) { return false; }
77479
77480        var def, defs = Lang.merge(this.data), children = [];
77481
77482
77483
77484        if (this.expanded) {defs.expanded = this.expanded; }
77485        if (!this.multiExpand) { defs.multiExpand = this.multiExpand; }
77486        if (this.renderHidden) { defs.renderHidden = this.renderHidden; }
77487        if (!this.hasIcon) { defs.hasIcon = this.hasIcon; }
77488        if (this.nowrap) { defs.nowrap = this.nowrap; }
77489        if (this.className) { defs.className = this.className; }
77490        if (this.editable) { defs.editable = this.editable; }
77491        if (!this.enableHighlight) { defs.enableHighlight = this.enableHighlight; }
77492        if (this.highlightState) { defs.highlightState = this.highlightState; }
77493        if (this.propagateHighlightUp) { defs.propagateHighlightUp = this.propagateHighlightUp; }
77494        if (this.propagateHighlightDown) { defs.propagateHighlightDown = this.propagateHighlightDown; }
77495        defs.type = this._type;
77496
77497
77498
77499        for (var i = 0; i < this.children.length;i++) {
77500            def = this.children[i].getNodeDefinition();
77501            if (def === false) { return false;}
77502            children.push(def);
77503        }
77504        if (children.length) { defs.children = children; }
77505        return defs;
77506    },
77507
77508
77509    /**
77510     * Generates the link that will invoke this node's toggle method
77511     * @method getToggleLink
77512     * @return {string} the javascript url for toggling this node
77513     */
77514    getToggleLink: function() {
77515        return 'return false;';
77516    },
77517
77518    /**
77519    * Sets the value of property for this node and all loaded descendants.
77520    * Only public and defined properties can be set, not methods.
77521    * Values for unknown properties will be assigned to the refNode.data object
77522    * @method setNodesProperty
77523    * @param name {string} Name of the property to be set
77524    * @param value {any} value to be set
77525    * @param refresh {boolean} if present and true, it does a refresh
77526    */
77527    setNodesProperty: function(name, value, refresh) {
77528        if (name.charAt(0) != '_'  && !Lang.isUndefined(this[name]) && !Lang.isFunction(this[name]) ) {
77529            this[name] = value;
77530        } else {
77531            this.data[name] = value;
77532        }
77533        for (var i = 0; i < this.children.length;i++) {
77534            this.children[i].setNodesProperty(name,value);
77535        }
77536        if (refresh) {
77537            this.refresh();
77538        }
77539    },
77540    /**
77541    * Toggles the highlighted state of a Node
77542    * @method toggleHighlight
77543    */
77544    toggleHighlight: function() {
77545        if (this.enableHighlight) {
77546            // unhighlights only if fully highligthed.  For not or partially highlighted it will highlight
77547            if (this.highlightState == 1) {
77548                this.unhighlight();
77549            } else {
77550                this.highlight();
77551            }
77552        }
77553    },
77554
77555    /**
77556    * Turns highlighting on node.
77557    * @method highlight
77558    * @param _silent {boolean} optional, don't fire the highlightEvent
77559    */
77560    highlight: function(_silent) {
77561        if (this.enableHighlight) {
77562            if (this.tree.singleNodeHighlight) {
77563                if (this.tree._currentlyHighlighted) {
77564                    this.tree._currentlyHighlighted.unhighlight(_silent);
77565                }
77566                this.tree._currentlyHighlighted = this;
77567            }
77568            this.highlightState = 1;
77569            this._setHighlightClassName();
77570            if (!this.tree.singleNodeHighlight) {
77571                if (this.propagateHighlightDown) {
77572                    for (var i = 0;i < this.children.length;i++) {
77573                        this.children[i].highlight(true);
77574                    }
77575                }
77576                if (this.propagateHighlightUp) {
77577                    if (this.parent) {
77578                        this.parent._childrenHighlighted();
77579                    }
77580                }
77581            }
77582            if (!_silent) {
77583                this.tree.fireEvent('highlightEvent',this);
77584            }
77585        }
77586    },
77587    /**
77588    * Turns highlighting off a node.
77589    * @method unhighlight
77590    * @param _silent {boolean} optional, don't fire the highlightEvent
77591    */
77592    unhighlight: function(_silent) {
77593        if (this.enableHighlight) {
77594            // might have checked singleNodeHighlight but it wouldn't really matter either way
77595            this.tree._currentlyHighlighted = null;
77596            this.highlightState = 0;
77597            this._setHighlightClassName();
77598            if (!this.tree.singleNodeHighlight) {
77599                if (this.propagateHighlightDown) {
77600                    for (var i = 0;i < this.children.length;i++) {
77601                        this.children[i].unhighlight(true);
77602                    }
77603                }
77604                if (this.propagateHighlightUp) {
77605                    if (this.parent) {
77606                        this.parent._childrenHighlighted();
77607                    }
77608                }
77609            }
77610            if (!_silent) {
77611                this.tree.fireEvent('highlightEvent',this);
77612            }
77613        }
77614    },
77615    /**
77616    * Checks whether all or part of the children of a node are highlighted and
77617    * sets the node highlight to full, none or partial highlight.
77618    * If set to propagate it will further call the parent
77619    * @method _childrenHighlighted
77620    * @private
77621    */
77622    _childrenHighlighted: function() {
77623        var yes = false, no = false;
77624        if (this.enableHighlight) {
77625            for (var i = 0;i < this.children.length;i++) {
77626                switch(this.children[i].highlightState) {
77627                    case 0:
77628                        no = true;
77629                        break;
77630                    case 1:
77631                        yes = true;
77632                        break;
77633                    case 2:
77634                        yes = no = true;
77635                        break;
77636                }
77637            }
77638            if (yes && no) {
77639                this.highlightState = 2;
77640            } else if (yes) {
77641                this.highlightState = 1;
77642            } else {
77643                this.highlightState = 0;
77644            }
77645            this._setHighlightClassName();
77646            if (this.propagateHighlightUp) {
77647                if (this.parent) {
77648                    this.parent._childrenHighlighted();
77649                }
77650            }
77651        }
77652    },
77653
77654    /**
77655    * Changes the classNames on the toggle and content containers to reflect the current highlighting
77656    * @method _setHighlightClassName
77657    * @private
77658    */
77659    _setHighlightClassName: function() {
77660        var el = Dom.get('ygtvtableel' + this.index);
77661        if (el) {
77662            el.className = el.className.replace(/\bygtv-highlight\d\b/gi,'ygtv-highlight' + this.highlightState);
77663        }
77664    },
77665    
77666    //Start Changes
77667    /**
77668     * Gets a RegExp based on custom style. This is used for expanding collapsing nodes that have custom style. 
77669     * @param customCls
77670     * @returns {RegExp}
77671     */
77672          _customRegEx : function(customCls)
77673      {
77674         var customRegEx = null;
77675         if (customCls != null)
77676         {
77677            customRegEx = new RegExp(customCls+"[mpn]", "g");
77678         }
77679         return customRegEx;
77680      }
77681      //End Changes
77682
77683};
77684
77685YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider);
77686})();
77687/**
77688 * A custom YAHOO.widget.Node that handles the unique nature of
77689 * the virtual, presentationless root node.
77690 * @namespace YAHOO.widget
77691 * @class RootNode
77692 * @extends YAHOO.widget.Node
77693 * @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to
77694 * @constructor
77695 */
77696YAHOO.widget.RootNode = function(oTree) {
77697    // Initialize the node with null params.  The root node is a
77698    // special case where the node has no presentation.  So we have
77699    // to alter the standard properties a bit.
77700    this.init(null, null, true);
77701
77702    /*
77703     * For the root node, we get the tree reference from as a param
77704     * to the constructor instead of from the parent element.
77705     */
77706    this.tree = oTree;
77707};
77708
77709YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, {
77710
77711   /**
77712     * The node type
77713     * @property _type
77714      * @type string
77715     * @private
77716     * @default "RootNode"
77717     */
77718    _type: "RootNode",
77719
77720    // overrides YAHOO.widget.Node
77721    getNodeHtml: function() {
77722        return "";
77723    },
77724
77725    toString: function() {
77726        return this._type;
77727    },
77728
77729    loadComplete: function() {
77730        this.tree.draw();
77731    },
77732
77733   /**
77734     * Count of nodes in tree.
77735    * It overrides Nodes.getNodeCount because the root node should not be counted.
77736     * @method getNodeCount
77737     * @return {int} number of nodes in the tree
77738     */
77739    getNodeCount: function() {
77740        for (var i = 0, count = 0;i< this.children.length;i++) {
77741            count += this.children[i].getNodeCount();
77742        }
77743        return count;
77744    },
77745
77746  /**
77747     * Returns an object which could be used to build a tree out of this node and its children.
77748     * It can be passed to the tree constructor to reproduce this node as a tree.
77749     * Since the RootNode is automatically created by treeView,
77750     * its own definition is excluded from the returned node definition
77751     * which only contains its children.
77752     * @method getNodeDefinition
77753     * @return {Object | false}  definition of the tree or false if any child node is defined as dynamic
77754     */
77755    getNodeDefinition: function() {
77756
77757        for (var def, defs = [], i = 0; i < this.children.length;i++) {
77758            def = this.children[i].getNodeDefinition();
77759            if (def === false) { return false;}
77760            defs.push(def);
77761        }
77762        return defs;
77763    },
77764
77765    collapse: function() {},
77766    expand: function() {},
77767    getSiblings: function() { return null; },
77768    focus: function () {}
77769
77770});
77771(function () {
77772    var Dom = YAHOO.util.Dom,
77773        Lang = YAHOO.lang,
77774        Event = YAHOO.util.Event;
77775/**
77776 * The default node presentation.  The first parameter should be
77777 * either a string that will be used as the node's label, or an object
77778 * that has at least a string property called label.  By default,  clicking the
77779 * label will toggle the expanded/collapsed state of the node.  By
77780 * setting the href property of the instance, this behavior can be
77781 * changed so that the label will go to the specified href.
77782 * @namespace YAHOO.widget
77783 * @class TextNode
77784 * @extends YAHOO.widget.Node
77785 * @constructor
77786 * @param oData {object} a string or object containing the data that will
77787 * be used to render this node.
77788 * Providing a string is the same as providing an object with a single property named label.
77789 * All values in the oData will be used to set equally named properties in the node
77790 * as long as the node does have such properties, they are not undefined, private or functions.
77791 * All attributes are made available in noderef.data, which
77792 * can be used to store custom attributes.  TreeView.getNode(s)ByProperty
77793 * can be used to retrieve a node by one of the attributes.
77794 * @param oParent {YAHOO.widget.Node} this node's parent node
77795 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
77796 */
77797YAHOO.widget.TextNode = function(o
77797Data, oParent, expanded) {
77798
77799    if (oData) {
77800        if (Lang.isString(oData)) {
77801            oData = { label: oData };
77802        }
77803        this.init(oData, oParent, expanded);
77804        this.setUpLabel(oData);
77805    }
77806
77807    this.logger     = new YAHOO.widget.LogWriter(this.toString());
77808};
77809
77810YAHOO.extend(YAHOO.widget.TextNode, YAHOO.widget.Node, {
77811
77812    /**
77813     * The CSS class for the label href.  Defaults to ygtvlabel, but can be
77814     * overridden to provide a custom presentation for a specific node.
77815     * @property labelStyle
77816     * @type string
77817     */
77818    labelStyle: "ygtvlabel",
77819
77820    /**
77821     * The derived element id of the label for this node
77822     * @property labelElId
77823     * @type string
77824     */
77825    labelElId: null,
77826
77827    /**
77828     * The text for the label.  It is assumed that the oData parameter will
77829     * either be a string that will be used as the label, or an object that
77830     * has a property called "label" that we will use.
77831     * @property label
77832     * @type string
77833     */
77834    label: null,
77835
77836    /**
77837     * The text for the title (tooltip) for the label element
77838     * @property title
77839     * @type string
77840     */
77841    title: null,
77842
77843    /**
77844     * The href for the node's label.  If one is not specified, the href will
77845     * be set so that it toggles the node.
77846     * @property href
77847     * @type string
77848     */
77849    href: null,
77850
77851    /**
77852     * The label href target, defaults to current window
77853     * @property target
77854     * @type string
77855     */
77856    target: "_self",
77857
77858    /**
77859     * The node type
77860     * @property _type
77861     * @private
77862     * @type string
77863     * @default "TextNode"
77864     */
77865    _type: "TextNode",
77866
77867
77868    /**
77869     * Sets up the node label
77870     * @method setUpLabel
77871     * @param oData string containing the label, or an object with a label property
77872     */
77873    setUpLabel: function(oData) {
77874
77875        if (Lang.isString(oData)) {
77876            oData = {
77877                label: oData
77878            };
77879        } else {
77880            if (oData.style) {
77881                this.labelStyle = oData.style;
77882            }
77883        }
77884
77885        this.label = oData.label;
77886
77887        this.labelElId = "ygtvlabelel" + this.index;
77888
77889    },
77890
77891    /**
77892     * Returns the label element
77893     * @for YAHOO.widget.TextNode
77894     * @method getLabelEl
77895     * @return {object} the element
77896     */
77897    getLabelEl: function() {
77898        return Dom.get(this.labelElId);
77899    },
77900
77901    // overrides YAHOO.widget.Node
77902    getContentHtml: function() {
77903        var sb = [];
77904        sb[sb.length] = this.href ? '<a' : '<span';
77905        sb[sb.length] = ' id="' + Lang.escapeHTML(this.labelElId) + '"';
77906        sb[sb.length] = ' class="' + Lang.escapeHTML(this.labelStyle)  + '"';
77907        if (this.href) {
77908            sb[sb.length] = ' href="' + Lang.escapeHTML(this.href) + '"';
77909            sb[sb.length] = ' target="' + Lang.escapeHTML(this.target) + '"';
77910        }
77911        if (this.title) {
77912            sb[sb.length] = ' title="' + Lang.escapeHTML(this.title) + '"';
77913        }
77914        sb[sb.length] = ' >';
77915        sb[sb.length] = Lang.escapeHTML(this.label);
77916        sb[sb.length] = this.href?'</a>':'</span>';
77917        return sb.join("");
77918    },
77919
77920
77921
77922  /**
77923     * Returns an object which could be used to build a tree out of this node and its children.
77924     * It can be passed to the tree constructor to reproduce this node as a tree.
77925     * It will return false if the node or any descendant loads dynamically, regardless of whether it is loaded or not.
77926     * @method getNodeDefinition
77927     * @return {Object | false}  definition of the tree or false if this node or any descendant is defined as dynamic
77928     */
77929    getNodeDefinition: function() {
77930        var def = YAHOO.widget.TextNode.superclass.getNodeDefinition.call(this);
77931        if (def === false) { return false; }
77932
77933        // Node specific properties
77934        def.label = this.label;
77935        if (this.labelStyle != 'ygtvlabel') { def.style = this.labelStyle; }
77936        if (this.title) { def.title = this.title; }
77937        if (this.href) { def.href = this.href; }
77938        if (this.target != '_self') { def.target = this.target; }
77939
77940        return def;
77941
77942    },
77943
77944    toString: function() {
77945        return YAHOO.widget.TextNode.superclass.toString.call(this) + ": " + this.label;
77946    },
77947
77948    // deprecated
77949    onLabelClick: function() {
77950        return false;
77951    },
77952    refresh: function() {
77953        YAHOO.widget.TextNode.superclass.refresh.call(this);
77954        var label = this.getLabelEl();
77955        label.innerHTML = this.label;
77956        if (label.tagName.toUpperCase() == 'A') {
77957            label.href = this.href;
77958            label.target = this.target;
77959        }
77960    }
77961
77962
77963
77964
77965});
77966})();
77967/**
77968 * A menu-specific implementation that differs from TextNode in that only
77969 * one sibling can be expanded at a time.
77970 * @namespace YAHOO.widget
77971 * @class MenuNode
77972 * @extends YAHOO.widget.TextNode
77973 * @param oData {object} a string or object containing the data that will
77974 * be used to render this node.
77975 * Providing a string is the same as providing an object with a single property named label.
77976 * All values in the oData will be used to set equally named properties in the node
77977 * as long as the node does have such properties, they are not undefined, private or functions.
77978 * All attributes are made available in noderef.data, which
77979 * can be used to store custom attributes.  TreeView.getNode(s)ByProperty
77980 * can be used to retrieve a node by one of the attributes.
77981 * @param oParent {YAHOO.widget.Node} this node's parent node
77982 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
77983 * @constructor
77984 */
77985YAHOO.widget.MenuNode = function(oData, oParent, expanded) {
77986    YAHOO.widget.MenuNode.superclass.constructor.call(this,oData,oParent,expanded);
77987
77988   /*
77989     * Menus usually allow only one branch to be open at a time.
77990     */
77991    this.multiExpand = false;
77992
77993};
77994
77995YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, {
77996
77997    /**
77998     * The node type
77999     * @property _type
78000     * @private
78001    * @default "MenuNode"
78002     */
78003    _type: "MenuNode"
78004
78005});
78006(function () {
78007    var Dom = YAHOO.util.Dom,
78008        Lang = YAHOO.lang,
78009        Event = YAHOO.util.Event;
78010
78011/**
78012 * This implementation takes either a string or object for the
78013 * oData argument.  If is it a string, it will use it for the display
78014 * of this node (and it can contain any html code).  If the parameter
78015 * is an object,it looks for a parameter called "html" that will be
78016 * used for this node's display.
78017 * @namespace YAHOO.widget
78018 * @class HTMLNode
78019 * @extends YAHOO.widget.Node
78020 * @constructor
78021 * @param oData {object} a string or object containing the data that will
78022 * be used to render this node.
78023 * Providing a string is the same as providing an object with a single property named html.
78024 * All values in the oData will be used to set equally named properties in the node
78025 * as long as the node does have such properties, they are not undefined, private or functions.
78026 * All other attributes are made available in noderef.data, which
78027 * can be used to store custom attributes.  TreeView.getNode(s)ByProperty
78028 * can be used to retrieve a node by one of the attributes.
78029 * @param oParent {YAHOO.widget.Node} this node's parent node
78030 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
78031 * @param hasIcon {boolean} specifies whether or not leaf nodes should
78032 * be rendered with or without a horizontal line and/or toggle icon. If the icon
78033 * is not displayed, the content fills the space it would have occupied.
78034 * This option operates independently of the leaf node presentation logic
78035 * for dynamic nodes.
78036 * (deprecated; use oData.hasIcon)
78037 */
78038var HN =  function(oData, oParent, expanded, hasIcon) {
78039    if (oData) {
78040        this.init(oData, oParent, expanded);
78041        this.initContent(oData, hasIcon);
78042    }
78043};
78044
78045
78046YAHOO.widget.HTMLNode = HN;
78047YAHOO.extend(HN, YAHOO.widget.Node, {
78048
78049    /**
78050     * The CSS class for the html content container.  Defaults to ygtvhtml, but
78051     * can be overridden to provide a custom presentation for a specific node.
78052     * @property contentStyle
78053     * @type string
78054     */
78055    contentStyle: "ygtvhtml",
78056
78057
78058    /**
78059     * The HTML content to use for this node's display
78060     * @property html
78061     * @type string
78062     */
78063    html: null,
78064
78065/**
78066     * The node type
78067     * @property _type
78068     * @private
78069     * @type string
78070     * @default "HTMLNode"
78071     */
78072    _type: "HTMLNode",
78073
78074    /**
78075     * Sets up the node label
78076     * @method initContent
78077     * @param oData {object} An html string or object containing an html property
78078     * @param hasIcon {boolean} determines if the node will be rendered with an
78079     * icon or not
78080     */
78081    initContent: function(oData, hasIcon) {
78082        this.setHtml(oData);
78083        this.contentElId = "ygtvcontentel" + this.index;
78084        if (!Lang.isUndefined(hasIcon)) { this.hasIcon  = hasIcon; }
78085
78086        this.logger = new YAHOO.widget.LogWriter(this.toString());
78087    },
78088
78089    /**
78090     * Synchronizes the node.html, and the node's content
78091     * @method setHtml
78092     * @param o {object |string | HTMLElement } An html string, an object containing an html property or an HTML element
78093     */
78094    setHtml: function(o) {
78095        this.html = (Lang.isObject(o) && 'html' in o) ? o.html : o;
78096
78097        var el = this.getContentEl();
78098        if (el) {
78099            if (o.nodeType && o.nodeType == 1 && o.tagName) {
78100                el.innerHTML = "";
78101            } else {
78102                el.innerHTML = this.html;
78103            }
78104        }
78105
78106    },
78107
78108    // overrides YAHOO.widget.Node
78109    // If property html is a string, it sets the innerHTML for the node
78110    // If it is an HTMLElement, it defers appending it to the tree until the HTML basic structure is built
78111    getContentHtml: function() {
78112        if (typeof this.html === "string") {
78113            return this.html;
78114        } else {
78115
78116            HN._deferredNodes.push(this);
78117            if (!HN._timer) {
78118                HN._timer = window.setTimeout(function () {
78119                    var n;
78120                    while((n = HN._deferredNodes.pop())) {
78121                        n.getContentEl().appendChild(n.html);
78122                    }
78123                    HN._timer = null;
78124                },0);
78125            }
78126            return "";
78127        }
78128    },
78129
78130      /**
78131     * Returns an object which could be used to build a tree out of this node and its children.
78132     * It can be passed to the tree constructor to reproduce this node as a tree.
78133     * It will return false if any node loads dynamically, regardless of whether it is loaded or not.
78134     * @method getNodeDefinition
78135     * @return {Object | false}  definition of the tree or false if any node is defined as dynamic
78136     */
78137    getNodeDefinition: function() {
78138        var def = HN.superclass.getNodeDefinition.call(this);
78139        if (def === false) { return false; }
78140        def.html = this.html;
78141        return def;
78142
78143    }
78144});
78145
78146    /**
78147    * An array of HTMLNodes created with HTML Elements that had their rendering
78148    * deferred until the basic tree structure is rendered.
78149    * @property _deferredNodes
78150    * @type YAHOO.widget.HTMLNode[]
78151    * @default []
78152    * @private
78153    * @static
78154    */
78155HN._deferredNodes = [];
78156    /**
78157    * A system timer value used to mark whether a deferred operation is pending.
78158    * @property _timer
78159    * @type System Timer
78160    * @default null
78161    * @private
78162    * @static
78163    */
78164HN._timer = null;
78165})();
78166(function () {
78167    var Dom = YAHOO.util.Dom,
78168        Lang = YAHOO.lang,
78169        Event = YAHOO.util.Event,
78170        Calendar = YAHOO.widget.Calendar;
78171
78172/**
78173 * A Date-specific implementation that differs from TextNode in that it uses
78174 * YAHOO.widget.Calendar as an in-line editor, if available
78175 * If Calendar is not available, it behaves as a plain TextNode.
78176 * @namespace YAHOO.widget
78177 * @class DateNode
78178 * @extends YAHOO.widget.TextNode
78179 * @param oData {object} a string or object containing the data that will
78180 * be used to render this node.
78181 * Providing a string is the same as providing an object with a single property named label.
78182 * All values in the oData will be used to set equally named properties in the node
78183 * as long as the node does have such properties, they are not undefined, private nor functions.
78184 * All attributes are made available in noderef.data, which
78185 * can be used to store custom attributes.  TreeView.getNode(s)ByProperty
78186 * can be used to retrieve a node by one of the attributes.
78187 * @param oParent {YAHOO.widget.Node} this node's parent node
78188 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
78189 * @constructor
78190 */
78191YAHOO.widget.DateNode = function(oData, oParent, expanded) {
78192    YAHOO.widget.DateNode.superclass.constructor.call(this,oData, oParent, expanded);
78193};
78194
78195YAHOO.extend(YAHOO.widget.DateNode, YAHOO.widget.TextNode, {
78196
78197    /**
78198     * The node type
78199     * @property _type
78200     * @type string
78201     * @private
78202     * @default  "DateNode"
78203     */
78204    _type: "DateNode",
78205
78206    /**
78207    * Configuration object for the Calendar editor, if used.
78208    * See <a href="http://developer.yahoo.com/yui/calendar/#internationalization">http://developer.yahoo.com/yui/calendar/#internationalization</a>
78209    * @property calendarConfig
78210    */
78211    calendarConfig: null,
78212
78213
78214
78215    /**
78216     *  If YAHOO.widget.Calendar is available, it will pop up a Calendar to enter a new date.  Otherwise, it falls back to a plain &lt;input&gt;  textbox
78217     * @method fillEditorContainer
78218     * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
78219     * @return void
78220     */
78221    fillEditorContainer: function (editorData) {
78222
78223        var cal, container = editorData.inputContainer;
78224
78225        if (Lang.isUndefined(Calendar)) {
78226            Dom.replaceClass(editorData.editorPanel,'ygtv-edit-DateNode','ygtv-edit-TextNode');
78227            YAHOO.widget.DateNode.superclass.fillEditorContainer.call(this, editorData);
78228            return;
78229        }
78230
78231        if (editorData.nodeType != this._type) {
78232            editorData.nodeType = this._type;
78233            editorData.saveOnEnter = false;
78234
78235            editorData.node.destroyEditorContents(editorData);
78236
78237            editorData.inputObject = cal = new Calendar(container.appendChild(document.createElement('div')));
78238            if (this.calendarConfig) {
78239                cal.cfg.applyConfig(this.calendarConfig,true);
78240                cal.cfg.fireQueue();
78241            }
78242            cal.selectEvent.subscribe(function () {
78243                this.tree._closeEditor(true);
78244            },this,true);
78245        } else {
78246            cal = editorData.inputObject;
78247        }
78248
78249        editorData.oldValue = this.label;
78250        cal.cfg.setProperty("selected",this.label, false);
78251
78252        var delim = cal.cfg.getProperty('DATE_FIELD_DELIMITER');
78253        var pageDate = this.label.split(delim);
78254        cal.cfg.setProperty('pagedate',pageDate[cal.cfg.getProperty('MDY_MONTH_POSITION') -1] + delim + pageDate[cal.cfg.getProperty('MDY_YEAR_POSITION') -1]);
78255        cal.cfg.fireQueue();
78256
78257        cal.render();
78258        cal.oDomContainer.focus();
78259    },
78260     /**
78261    * Returns the value from the input element.
78262    * Overrides Node.getEditorValue.
78263    * @method getEditorValue
78264     * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
78265     * @return {string} date entered
78266     */
78267
78268    getEditorValue: function (editorData) {
78269        if (Lang.isUndefined(Calendar)) {
78270            return editorData.inputElement.value;
78271        } else {
78272            var cal = editorData.inputObject,
78273                date = cal.getSelectedDates()[0],
78274                dd = [];
78275
78276            dd[cal.cfg.getProperty('MDY_DAY_POSITION') -1] = date.getDate();
78277            dd[cal.cfg.getProperty('MDY_MONTH_POSITION') -1] = date.getMonth() + 1;
78278            dd[cal.cfg.getProperty('MDY_YEAR_POSITION') -1] = date.getFullYear();
78279            return dd.join(cal.cfg.getProperty('DATE_FIELD_DELIMITER'));
78280        }
78281    },
78282
78283    /**
78284     * Finally displays the newly entered date in the tree.
78285     * Overrides Node.displayEditedValue.
78286     * @method displayEditedValue
78287     * @param value {HTML} date to be displayed and stored in the node.
78288     * This data is added to the node unescaped via the innerHTML property.
78289     * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
78290     */
78291    displayEditedValue: function (value,editorData) {
78292        var node = editorData.node;
78293        node.label = value;
78294        node.getLabelEl().innerHTML = value;
78295    },
78296
78297   /**
78298     * Returns an object which could be used to build a tree out of this node and its children.
78299     * It can be passed to the tree constructor to reproduce this node as a tree.
78300     * It will return false if the node or any descendant loads dynamically, regardless of whether it is loaded or not.
78301     * @method getNodeDefinition
78302     * @return {Object | false}  definition of the node or false if this node or any descendant is defined as dynamic
78303     */
78304    getNodeDefinition: function() {
78305        var def = YAHOO.widget.DateNode.superclass.getNodeDefinition.call(this);
78306        if (def === false) { return false; }
78307        if (this.calendarConfig) { def.calendarConfig = this.calendarConfig; }
78308        return def;
78309    }
78310
78311
78312});
78313})();
78314(function () {
78315    var Dom = YAHOO.util.Dom,
78316        Lang = YAHOO.lang,
78317        Event = YAHOO.util.Event,
78318        TV = YAHOO.widget.TreeView,
78319        TVproto = TV.prototype;
78320
78321    /**
78322     * An object to store information used for in-line editing
78323     * for all Nodes of all TreeViews. It contains:
78324     * <ul>
78325    * <li>active {boolean}, whether there is an active cell editor </li>
78326    * <li>
78326whoHasIt {YAHOO.widget.TreeView} TreeView instance that is currently using the editor</li>
78327    * <li>nodeType {string} value of static Node._type property, allows reuse of input element if node is of the same type.</li>
78328    * <li>editorPanel {HTMLelement (&lt;div&gt;)} element holding the in-line editor</li>
78329    * <li>inputContainer {HTMLelement (&lt;div&gt;)} element which will hold the type-specific input element(s) to be filled by the fillEditorContainer method</li>
78330    * <li>buttonsContainer {HTMLelement (&lt;div&gt;)} element which holds the &lt;button&gt; elements for Ok/Cancel.  If you don't want any of the buttons, hide it via CSS styles, don't destroy it</li>
78331    * <li>node {YAHOO.widget.Node} reference to the Node being edited</li>
78332    * <li>saveOnEnter {boolean}, whether the Enter key should be accepted as a Save command (Esc. is always taken as Cancel), disable for multi-line input elements </li>
78333    * <li>oldValue {any}  value before editing</li>
78334    * </ul>
78335    *  Editors are free to use this object to store additional data.
78336     * @property editorData
78337     * @static
78338     * @for YAHOO.widget.TreeView
78339     */
78340    TV.editorData = {
78341        active:false,
78342        whoHasIt:null, // which TreeView has it
78343        nodeType:null,
78344        editorPanel:null,
78345        inputContainer:null,
78346        buttonsContainer:null,
78347        node:null, // which Node is being edited
78348        saveOnEnter:true,
78349        oldValue:undefined
78350        // Each node type is free to add its own properties to this as it sees fit.
78351    };
78352
78353    /**
78354    * Validator function for edited data, called from the TreeView instance scope,
78355    * receives the arguments (newValue, oldValue, nodeInstance)
78356    * and returns either the validated (or type-converted) value or undefined.
78357    * An undefined return will prevent the editor from closing
78358    * @property validator
78359    * @type function
78360    * @default null
78361     * @for YAHOO.widget.TreeView
78362     */
78363    TVproto.validator = null;
78364
78365    /**
78366    * Entry point for initializing the editing plug-in.
78367    * TreeView will call this method on initializing if it exists
78368    * @method _initEditor
78369     * @for YAHOO.widget.TreeView
78370     * @private
78371    */
78372
78373    TVproto._initEditor = function () {
78374        /**
78375        * Fires when the user clicks on the ok button of a node editor
78376        * @event editorSaveEvent
78377        * @type CustomEvent
78378        * @param oArgs.newValue {mixed} the new value just entered
78379        * @param oArgs.oldValue {mixed} the value originally in the tree
78380        * @param oArgs.node {YAHOO.widget.Node} the node that has the focus
78381            * @for YAHOO.widget.TreeView
78382        */
78383        this.createEvent("editorSaveEvent", this);
78384
78385        /**
78386        * Fires when the user clicks on the cancel button of a node editor
78387        * @event editorCancelEvent
78388        * @type CustomEvent
78389        * @param {YAHOO.widget.Node} node the node that has the focus
78390            * @for YAHOO.widget.TreeView
78391        */
78392        this.createEvent("editorCancelEvent", this);
78393
78394    };
78395
78396    /**
78397    * Entry point of the editing plug-in.
78398    * TreeView will call this method if it exists when a node label is clicked
78399    * @method _nodeEditing
78400    * @param node {YAHOO.widget.Node} the node to be edited
78401    * @return {Boolean} true to indicate that the node is editable and prevent any further bubbling of the click.
78402     * @for YAHOO.widget.TreeView
78403     * @private
78404    */
78405
78406
78407
78408    TVproto._nodeEditing = function (node) {
78409        if (node.fillEditorContainer && node.editable) {
78410            var ed, topLeft, buttons, button, editorData = TV.editorData;
78411            editorData.active = true;
78412            editorData.whoHasIt = this;
78413            if (!editorData.nodeType) {
78414                // Fixes: http://yuilibrary.com/projects/yui2/ticket/2528945
78415                editorData.editorPanel = ed = this.getEl().appendChild(document.createElement('div'));
78416                Dom.addClass(ed,'ygtv-label-editor');
78417                ed.tabIndex = 0;
78418
78419                buttons = editorData.buttonsContainer = ed.appendChild(document.createElement('div'));
78420                Dom.addClass(buttons,'ygtv-button-container');
78421                button = buttons.appendChild(document.createElement('button'));
78422                Dom.addClass(button,'ygtvok');
78423                button.innerHTML = ' ';
78424                button = buttons.appendChild(document.createElement('button'));
78425                Dom.addClass(button,'ygtvcancel');
78426                button.innerHTML = ' ';
78427                Event.on(buttons, 'click', function (ev) {
78428                    var target = Event.getTarget(ev),
78429                        editorData = TV.editorData,
78430                        node = editorData.node,
78431                        self = editorData.whoHasIt;
78432                    self.logger.log('click on editor');
78433                    if (Dom.hasClass(target,'ygtvok')) {
78434                        node.logger.log('ygtvok');
78435                        Event.stopEvent(ev);
78436                        self._closeEditor(true);
78437                    }
78438                    if (Dom.hasClass(target,'ygtvcancel')) {
78439                        node.logger.log('ygtvcancel');
78440                        Event.stopEvent(ev);
78441                        self._closeEditor(false);
78442                    }
78443                });
78444
78445                editorData.inputContainer = ed.appendChild(document.createElement('div'));
78446                Dom.addClass(editorData.inputContainer,'ygtv-input');
78447
78448                Event.on(ed,'keydown',function (ev) {
78449                    var editorData = TV.editorData,
78450                        KEY = YAHOO.util.KeyListener.KEY,
78451                        self = editorData.whoHasIt;
78452                    switch (ev.keyCode) {
78453                        case KEY.ENTER:
78454                            self.logger.log('ENTER');
78455                            Event.stopEvent(ev);
78456                            if (editorData.saveOnEnter) {
78457                                self._closeEditor(true);
78458                            }
78459                            break;
78460                        case KEY.ESCAPE:
78461                            self.logger.log('ESC');
78462                            Event.stopEvent(ev);
78463                            self._closeEditor(false);
78464                            break;
78465                    }
78466                });
78467
78468
78469
78470            } else {
78471                ed = editorData.editorPanel;
78472            }
78473            editorData.node = node;
78474            if (editorData.nodeType) {
78475                Dom.removeClass(ed,'ygtv-edit-' + editorData.nodeType);
78476            }
78477            Dom.addClass(ed,' ygtv-edit-' + node._type);
78478            // Fixes: http://yuilibrary.com/projects/yui2/ticket/2528945
78479            Dom.setStyle(ed,'display','block');
78480            Dom.setXY(ed,Dom.getXY(node.getContentEl()));
78481            // up to here
78482            ed.focus();
78483            node.fillEditorContainer(editorData);
78484
78485            return true;  // If inline editor available, don't do anything else.
78486        }
78487    };
78488
78489    /**
78490    * Method to be associated with an event (clickEvent, dblClickEvent or enterKeyPressed) to pop up the contents editor
78491    *  It calls the corresponding node editNode method.
78492    * @method onEventEditNode
78493    * @param oArgs {object} Object passed as arguments to TreeView event listeners
78494    * @for YAHOO.widget.TreeView
78495    */
78496
78497    TVproto.onEventEditNode = function (oArgs) {
78498        if (oArgs instanceof YAHOO.widget.Node) {
78499            oArgs.editNode();
78500        } else if (oArgs.node instanceof YAHOO.widget.Node) {
78501            oArgs.node.editNode();
78502        }
78503        return false;
78504    };
78505
78506    /**
78507    * Method to be called when the inline editing is finished and the editor is to be closed
78508    * @method _closeEditor
78509    * @param save {Boolean} true if the edited value is to be saved, false if discarded
78510    * @private
78511     * @for YAHOO.widget.TreeView
78512    */
78513
78514    TVproto._closeEditor = function (save) {
78515        var ed = TV.editorData,
78516            node = ed.node,
78517            close = true;
78518        // http://yuilibrary.com/projects/yui2/ticket/2528946
78519        // _closeEditor might now be called at any time, even when there is no label editor open
78520        // so we need to ensure there is one.
78521        if (!node || !ed.active) { return; }
78522        if (save) {
78523            close = ed.node.saveEditorValue(ed) !== false;
78524        } else {
78525            this.fireEvent( 'editorCancelEvent', node);
78526        }
78527
78528        if (close) {
78529            Dom.setStyle(ed.editorPanel,'display','none');
78530            ed.active = false;
78531            node.focus();
78532        }
78533    };
78534
78535    /**
78536    *  Entry point for TreeView's destroy method to destroy whatever the editing plug-in has created
78537    * @method _destroyEditor
78538    * @private
78539     * @for YAHOO.widget.TreeView
78540    */
78541    TVproto._destroyEditor = function() {
78542        var ed = TV.editorData;
78543        if (ed && ed.nodeType && (!ed.active || ed.whoHasIt === this)) {
78544            Event.removeListener(ed.editorPanel,'keydown');
78545            Event.removeListener(ed.buttonContainer,'click');
78546            ed.node.destroyEditorContents(ed);
78547            document.body.removeChild(ed.editorPanel);
78548            ed.nodeType = ed.editorPanel = ed.inputContainer = ed.buttonsContainer = ed.whoHasIt = ed.node = null;
78549            ed.active = false;
78550        }
78551    };
78552
78553    var Nproto = YAHOO.widget.Node.prototype;
78554
78555    /**
78556    * Signals if the label is editable.  (Ignored on TextNodes with href set.)
78557    * @property editable
78558    * @type boolean
78559         * @for YAHOO.widget.Node
78560    */
78561    Nproto.editable = false;
78562
78563    /**
78564    * pops up the contents editor, if there is one and the node is declared editable
78565    * @method editNode
78566     * @for YAHOO.widget.Node
78567    */
78568
78569    Nproto.editNode = function () {
78570        this.tree._nodeEditing(this);
78571    };
78572
78573
78574    /** Placeholder for a function that should provide the inline node label editor.
78575     *   Leaving it set to null will indicate that this node type is not editable.
78576     * It should be overridden by nodes that provide inline editing.
78577     *  The Node-specific editing element (input box, textarea or whatever) should be inserted into editorData.inputContainer.
78578     * @method fillEditorContainer
78579     * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
78580     * @return void
78581     * @for YAHOO.widget.Node
78582     */
78583    Nproto.fillEditorContainer = null;
78584
78585
78586    /**
78587    * Node-specific destroy function to empty the contents of the inline editor panel.
78588    * This function is the worst case alternative that will purge all possible events and remove the editor contents.
78589    * Method Event.purgeElement is somewhat costly so if it can be replaced by specifc Event.removeListeners, it is better to do so.
78590    * @method destroyEditorContents
78591     * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
78592     * @for YAHOO.widget.Node
78593     */
78594    Nproto.destroyEditorContents = function (editorData) {
78595        // In the worst case, if the input editor (such as the Calendar) has no destroy method
78596        // we can only try to remove all possible events on it.
78597        Event.purgeElement(editorData.inputContainer,true);
78598        editorData.inputContainer.innerHTML = '';
78599    };
78600
78601    /**
78602    * Saves the value entered into the editor.
78603    * @method saveEditorValue
78604     * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
78605     * @return {false or none} a return of exactly false will prevent the editor from closing
78606     * @for YAHOO.widget.Node
78607     */
78608    Nproto.saveEditorValue = function (editorData) {
78609        var node = editorData.node,
78610            value,
78611            validator = node.tree.validator;
78612
78613        value = this.getEditorValue(editorData);
78614
78615        if (Lang.isFunction(validator)) {
78616            value = validator(value,editorData.oldValue,node);
78617            if (Lang.isUndefined(value)) {
78618                return false;
78619            }
78620        }
78621
78622        if (this.tree.fireEvent( 'editorSaveEvent', {
78623            newValue:value,
78624            oldValue:editorData.oldValue,
78625            node:node
78626        }) !== false) {
78627            this.displayEditedValue(value,editorData);
78628        }
78629    };
78630
78631
78632    /**
78633     * Returns the value(s) from the input element(s) .
78634     * Should be overridden by each node type.
78635     * @method getEditorValue
78636     * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
78637     * @return {any} value entered
78638     * @for YAHOO.widget.Node
78639     */
78640
78641     Nproto.getEditorValue = function (editorData) {
78642    };
78643
78644    /**
78645     * Finally displays the newly edited value(s) in the tree.
78646     * Should be overridden by each node type.
78647     * @method displayEditedValue
78648     * @param value {HTML} value to be displayed and stored in the node
78649     * This data is added to the node unescaped via the innerHTML property.
78650     * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
78651     * @for YAHOO.widget.Node
78652     */
78653    Nproto.displayEditedValue = function (value,editorData) {
78654    };
78655
78656    var TNproto = YAHOO.widget.TextNode.prototype;
78657
78658
78659
78660    /**
78661     *  Places an &lt;input&gt;  textbox in the input container and loads the label text into it.
78662     * @method fillEditorContainer
78663     * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
78664     * @return void
78665     * @for YAHOO.widget.TextNode
78666     */
78667    TNproto.fillEditorContainer = function (editorData) {
78668
78669        var input;
78670        // If last node edited is not of the same type as this one, delete it and fill it with our editor
78671        if (editorData.nodeType != this._type) {
78672            editorData.nodeType = this._type;
78673            editorData.saveOnEnter = true;
78674            editorData.node.destroyEditorContents(editorData);
78675
78676            editorData.inputElement = input = editorData.inputContainer.appendChild(document.createElement('input'));
78677
78678        } else {
78679            // if the last node edited was of the same time, reuse the input element.
78680            input = editorData.inputElement;
78681        }
78682        editorData.oldValue = this.label;
78683        input.value = this.label;
78684        input.focus();
78685        input.select();
78686    };
78687
78688    /**
78689     * Returns the value from the input element.
78690     * Overrides Node.getEditorValue.
78691     * @method getEditorValue
78692     * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
78693     * @return {string} value entered
78694     * @for YAHOO.widget.TextNode
78695     */
78696
78697    TNproto.getEditorValue = function (editorData) {
78698        return editorData.inputElement.value;
78699    };
78700
78701    /**
78702     * Finally displays the newly edited value in the tree.
78703     * Overrides Node.displayEditedValue.
78704     * @method displayEditedValue
78705     * @param value {string} value to be displayed and stored in the node
78706     * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
78707     * @for YAHOO.widget.TextNode
78708     */
78709    TNproto.displayEditedValue = function (value,editorData) {
78710        var node = editorData.node;
78711        node.label = value;
78712        node.getLabelEl().innerHTML = value;
78713    };
78714
78715    /**
78716    * Destroys the contents of the inline editor panel.
78717    * Overrides Node.destroyEditorContent.
78718    * Since we didn't set any event listeners on this inline editor, it is more efficient to avoid the generic method in Node.
78719    * @method destroyEditorContents
78720     * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
78721     * @for YAHOO.widget.TextNode
78722     */
78723    TNproto.destroyEditorContents = function (editorData) {
78724        editorData.inputContainer.innerHTML = '';
78725    };
78726})();
78727/**
78728 * A static factory class for tree view expand/collapse animations
78729 * @class TVAnim
78730 * @static
78731 */
78732YAHOO.widget.TVAnim = function() {
78733    return {
78734        /**
78735         * Constant for the fade in animation
78736         * @property FADE_IN
78737         * @type string
78738         * @static
78739         */
78740        FADE_IN: "TVFadeIn",
78741
78742        /**
78743         * Constant for the fade out animation
78744         * @property FADE_OUT
78745         * @type string
78746         * @static
78747         */
78748        FADE_OUT: "TVFadeOut",
78749
78750        /**
78751         * Returns a ygAnim instance of the given type
78752         * @method getAnim
78753         * @param type {string} the type of animation
78754         * @param el {HTMLElement} the element to element (probably the children div)
78755         * @param callback {function} function to invoke when the animation is done.
78756         * @return {YAHOO.util.Animation} the animation instance
78757         * @static
78758         */
78759        getAnim: function(type, el, callback) {
78760            if (YAHOO.widget[type]) {
78761                return new YAHOO.widget[type](el, callback);
78762            } else {
78763                return null;
78764            }
78765        },
78766
78767        /**
78768         * Returns true if the specified animation class is available
78769         * @method isValid
78770         * @param type {string} the type of animation
78771         * @return {boolean} true if valid, false if not
78772         * @static
78773         */
78774        isValid: function(type) {
78775            return (YAHOO.widget[type]);
78776        }
78777    };
78778} ();
78779/**
78780 * A 1/2 second fade-in animation.
78781 * @class TVFadeIn
78782 * @constructor
78783 * @param el {HTMLElement} the element to animate
78784 * @param callback {function} function to invoke when the animation is finished
78785 */
78786YAHOO.widget.TVFadeIn = function(el, callback) {
78787    /**
78788     * The element to animate
78789     * @property el
78790     * @type HTMLElement
78791     */
78792    this.el = el;
78793
78794    /**
78795     * the callback to invoke when the animation is complete
78796     * @property callback
78797     * @type function
78798     */
78799    this.callback = callback;
78800
78801    this.logger = new YAHOO.widget.LogWriter(this.toString());
78802};
78803
78804YAHOO.widget.TVFadeIn.prototype = {
78805    /**
78806     * Performs the animation
78807     * @method animate
78808     */
78809    animate: function() {
78810        var tvanim = this;
78811
78812        var s = this.el.style;
78813        s.opacity = 0.1;
78814        s.filter = "alpha(opacity=10)";
78815        s.display = "";
78816
78817        var dur = 0.4; 
78818        var a = new YAHOO.util.Anim(this.el, {opacity: {from: 0.1, to: 1, unit:""}}
78818, dur);
78819        a.onComplete.subscribe( function() { tvanim.onComplete(); } );
78820        a.animate();
78821    },
78822
78823    /**
78824     * Clean up and invoke callback
78825     * @method onComplete
78826     */
78827    onComplete: function() {
78828        this.callback();
78829    },
78830
78831    /**
78832     * toString
78833     * @method toString
78834     * @return {string} the string representation of the instance
78835     */
78836    toString: function() {
78837        return "TVFadeIn";
78838    }
78839};
78840/**
78841 * A 1/2 second fade out animation.
78842 * @class TVFadeOut
78843 * @constructor
78844 * @param el {HTMLElement} the element to animate
78845 * @param callback {Function} function to invoke when the animation is finished
78846 */
78847YAHOO.widget.TVFadeOut = function(el, callback) {
78848    /**
78849     * The element to animate
78850     * @property el
78851     * @type HTMLElement
78852     */
78853    this.el = el;
78854
78855    /**
78856     * the callback to invoke when the animation is complete
78857     * @property callback
78858     * @type function
78859     */
78860    this.callback = callback;
78861
78862    this.logger = new YAHOO.widget.LogWriter(this.toString());
78863};
78864
78865YAHOO.widget.TVFadeOut.prototype = {
78866    /**
78867     * Performs the animation
78868     * @method animate
78869     */
78870    animate: function() {
78871        var tvanim = this;
78872        var dur = 0.4;
78873        var a = new YAHOO.util.Anim(this.el, {opacity: {from: 1, to: 0.1, unit:""}}, dur);
78874        a.onComplete.subscribe( function() { tvanim.onComplete(); } );
78875        a.animate();
78876    },
78877
78878    /**
78879     * Clean up and invoke callback
78880     * @method onComplete
78881     */
78882    onComplete: function() {
78883        var s = this.el.style;
78884        s.display = "none";
78885        s.opacity = 1;
78886        s.filter = "alpha(opacity=100)";
78887        this.callback();
78888    },
78889
78890    /**
78891     * toString
78892     * @method toString
78893     * @return {string} the string representation of the instance
78894     */
78895    toString: function() {
78896        return "TVFadeOut";
78897    }
78898};
78899YAHOO.register("treeview", YAHOO.widget.TreeView, {version: "2.9.0", build: "2800"});
78900/*
78901Copyright (c) 2011, Yahoo! Inc. All rights reserved.
78902Code licensed under the BSD License:
78903http://developer.yahoo.com/yui/license.html
78904version: 2.9.0
78905*/
78906/**
78907 * Utilities for cookie management
78908 * @namespace YAHOO.util
78909 * @module cookie
78910 */
78911YAHOO.namespace("util");
78912
78913/**
78914 * Cookie utility.
78915 * @class Cookie
78916 * @static
78917 */
78918YAHOO.util.Cookie = {
78919    
78920    //-------------------------------------------------------------------------
78921    // Private Methods
78922    //-------------------------------------------------------------------------
78923    
78924    /**
78925     * Creates a cookie string that can be assigned into document.cookie.
78926     * @param {String} name The name of the cookie.
78927     * @param {String} value The value of the cookie.
78928     * @param {Boolean} encodeValue True to encode the value, false to leave as-is.
78929     * @param {Object} options (Optional) Options for the cookie.
78930     * @return {String} The formatted cookie string.
78931     * @method _createCookieString
78932     * @private
78933     * @static
78934     */
78935    _createCookieString : function (name /*:String*/, value /*:Variant*/, encodeValue /*:Boolean*/, options /*:Object*/) /*:String*/ {
78936        
78937        //shortcut
78938        var lang = YAHOO.lang,
78939            text = encodeURIComponent(name) + "=" + (encodeValue ? encodeURIComponent(value) : value);
78940        
78941        
78942        if (lang.isObject(options)){
78943            //expiration date
78944            if (options.expires instanceof Date){
78945                text += "; expires=" + options.expires.toUTCString();
78946            }
78947            
78948            //path
78949            if (lang.isString(options.path) && options.path !== ""){
78950                text += "; path=" + options.path;
78951            }
78952            
78953            //domain
78954            if (lang.isString(options.domain) && options.domain !== ""){
78955                text += "; domain=" + options.domain;
78956            }
78957            
78958            //secure
78959            if (options.secure === true){
78960                text += "; secure";
78961            }
78962        }
78963        
78964        return text;
78965    },
78966    
78967    /**
78968     * Formats a cookie value for an object containing multiple values.
78969     * @param {Object} hash An object of key-value pairs to create a string for.
78970     * @return {String} A string suitable for use as a cookie value.
78971     * @method _createCookieHashString
78972     * @private
78973     * @static
78974     */
78975    _createCookieHashString : function (hash /*:Object*/) /*:String*/ {
78976        
78977        //shortcuts
78978        var lang = YAHOO.lang;
78979        
78980        if (!lang.isObject(hash)){
78981            throw new TypeError("Cookie._createCookieHashString(): Argument must be an object.");
78982        }
78983        
78984        var text /*:Array*/ = [];
78985        
78986        for (var key in hash){
78987            if (lang.hasOwnProperty(hash, key) && !lang.isFunction(hash[key]) && !lang.isUndefined(hash[key])){
78988                text.push(encodeURIComponent(key) + "=" + encodeURIComponent(String(hash[key])));
78989            }
78990        }
78991        
78992        return text.join("&");
78993    },
78994    
78995    /**
78996     * Parses a cookie hash string into an object.
78997     * @param {String} text The cookie hash string to parse. The string should already be URL-decoded.
78998     * @return {Object} An object containing entries for each cookie value.
78999     * @method _parseCookieHash
79000     * @private
79001     * @static
79002     */
79003    _parseCookieHash : function (text /*:String*/) /*:Object*/ {
79004        
79005        var hashParts /*:Array*/ = text.split("&"),
79006            hashPart /*:Array*/ = null,
79007            hash /*:Object*/ = {};
79008        
79009        if (text.length > 0){
79010            for (var i=0, len=hashParts.length; i < len; i++){
79011                hashPart = hashParts[i].split("=");
79012                hash[decodeURIComponent(hashPart[0])] = decodeURIComponent(hashPart[1]);
79013            }
79014        }
79015        
79016        return hash;
79017    },
79018    
79019    /**
79020     * Parses a cookie string into an object representing all accessible cookies.
79021     * @param {String} text The cookie string to parse.
79022     * @param {Boolean} decode (Optional) Indicates if the cookie values should be decoded or not. Default is true.
79023     * @return {Object} An object containing entries for each accessible cookie.
79024     * @method _parseCookieString
79025     * @private
79026     * @static
79027     */
79028    _parseCookieString : function (text /*:String*/, decode /*:Boolean*/) /*:Object*/ {
79029        
79030        var cookies /*:Object*/ = {};
79031        
79032        if (YAHOO.lang.isString(text) && text.length > 0) {
79033            
79034            var decodeValue = (decode === false ? function(s){return s;} : decodeURIComponent);
79035            
79036            //if (/[^=]+=[^=;]?(?:; [^=]+=[^=]?)?/.test(text)){
79037                var cookieParts /*:Array*/ = text.split(/;\s/g),
79038                    cookieName /*:String*/ = null,
79039                    cookieValue /*:String*/ = null,
79040                    cookieNameValue /*:Array*/ = null;
79041                
79042                for (var i=0, len=cookieParts.length; i < len; i++){
79043                    
79044                    //check for normally-formatted cookie (name-value)
79045                    cookieNameValue = cookieParts[i].match(/([^=]+)=/i);
79046                    if (cookieNameValue instanceof Array){
79047                        try {
79048                            cookieName = decodeURIComponent(cookieNameValue[1]);
79049                            cookieValue = decodeValue(cookieParts[i].substring(cookieNameValue[1].length+1));
79050                        } catch (ex){
79051                            //ignore the entire cookie - encoding is likely invalid
79052                        }
79053                    } else {
79054                        //means the cookie does not have an "=", so treat it as a boolean flag
79055                        cookieName = decodeURIComponent(cookieParts[i]);
79056                        cookieValue = "";
79057                    }
79058                    cookies[cookieName] = cookieValue;
79059                }
79060            //}
79061        }
79062        
79063        return cookies;
79064    },
79065    
79066    //-------------------------------------------------------------------------
79067    // Public Methods
79068    //-------------------------------------------------------------------------
79069    
79070    /**
79071     * Determines if the cookie with the given name exists. This is useful for
79072     * Boolean cookies (those that do not follow the name=value convention).
79073     * @param {String} name The name of the cookie to check.
79074     * @return {Boolean} True if the cookie exists, false if not.
79075     * @method exists
79076     * @static
79077     */
79078    exists: function(name) {
79079
79080        if (!YAHOO.lang.isString(name) || name === ""){
79081            throw new TypeError("Cookie.exists(): Cookie name must be a non-empty string.");
79082        }
79083
79084        var cookies /*:Object*/ = this._parseCookieString(document.cookie, true);
79085        
79086        return cookies.hasOwnProperty(name);
79087    },
79088    
79089    /**
79090     * Returns the cookie value for the given name.
79091     * @param {String} name The name of the cookie to retrieve.
79092     * @param {Object|Function}
79092 options (Optional) An object containing one or more
79093     *      cookie options: raw (true/false) and converter (a function).
79094     *      The converter function is run on the value before returning it. The
79095     *      function is not used if the cookie doesn't exist. The function can be
79096     *      passed instead of the options object for backwards compatibility.
79097     * @return {Variant} If no converter is specified, returns a string or null if
79098     *      the cookie doesn't exist. If the converter is specified, returns the value
79099     *      returned from the converter or null if the cookie doesn't exist.
79100     * @method get
79101     * @static
79102     */
79103    get : function (name /*:String*/, options /*:Variant*/) /*:Variant*/{
79104        
79105        var lang = YAHOO.lang,
79106            converter;
79107        
79108        if (lang.isFunction(options)) {
79109            converter = options;
79110            options = {};
79111        } else if (lang.isObject(options)) {
79112            converter = options.converter;
79113        } else {
79114            options = {};
79115        }
79116        
79117        var cookies /*:Object*/ = this._parseCookieString(document.cookie, !options.raw);
79118        
79119        if (!lang.isString(name) || name === ""){
79120            throw new TypeError("Cookie.get(): Cookie name must be a non-empty string.");
79121        }
79122        
79123        if (lang.isUndefined(cookies[name])) {
79124            return null;
79125        }
79126        
79127        if (!lang.isFunction(converter)){
79128            return cookies[name];
79129        } else {
79130            return converter(cookies[name]);
79131        }
79132    },
79133    
79134    /**
79135     * Returns the value of a subcookie.
79136     * @param {String} name The name of the cookie to retrieve.
79137     * @param {String} subName The name of the subcookie to retrieve.
79138     * @param {Function} converter (Optional) A function to run on the value before returning
79139     *      it. The function is not used if the cookie doesn't exist.
79140     * @return {Variant} If the cookie doesn't exist, null is returned. If the subcookie
79141     *      doesn't exist, null if also returned. If no converter is specified and the
79142     *      subcookie exists, a string is returned. If a converter is specified and the
79143     *      subcookie exists, the value returned from the converter is returned.
79144     * @method getSub
79145     * @static
79146     */
79147    getSub : function (name, subName, converter) {
79148        
79149        var lang = YAHOO.lang,
79150            hash = this.getSubs(name);
79151        
79152        if (hash !== null) {
79153            
79154            if (!lang.isString(subName) || subName === ""){
79155                throw new TypeError("Cookie.getSub(): Subcookie name must be a non-empty string.");
79156            }
79157            
79158            if (lang.isUndefined(hash[subName])){
79159                return null;
79160            }
79161            
79162            if (!lang.isFunction(converter)){
79163                return hash[subName];
79164            } else {
79165                return converter(hash[subName]);
79166            }
79167        } else {
79168            return null;
79169        }
79170    
79171    },
79172    
79173    /**
79174     * Returns an object containing name-value pairs stored in the cookie with the given name.
79175     * @param {String} name The name of the cookie to retrieve.
79176     * @return {Object} An object of name-value pairs if the cookie with the given name
79177     *      exists, null if it does not.
79178     * @method getSubs
79179     * @static
79180     */
79181    getSubs : function (name /*:String*/) /*:Object*/ {
79182    
79183        var isString = YAHOO.lang.isString;
79184        
79185        //check cookie name
79186        if (!isString(name) || name === ""){
79187            throw new TypeError("Cookie.getSubs(): Cookie name must be a non-empty string.");
79188        }
79189        
79190        var cookies = this._parseCookieString(document.cookie, false);
79191        if (isString(cookies[name])){
79192            return this._parseCookieHash(cookies[name]);
79193        }
79194        return null;
79195    },
79196    
79197    /**
79198     * Removes a cookie from the machine by setting its expiration date to
79199     * sometime in the past.
79200     * @param {String} name The name of the cookie to remove.
79201     * @param {Object} options (Optional) An object containing one or more
79202     *      cookie options: path (a string), domain (a string),
79203     *      and secure (true/false). The expires option will be overwritten
79204     *      by the method.
79205     * @return {String} The created cookie string.
79206     * @method remove
79207     * @static
79208     */
79209    remove : function (name /*:String*/, options /*:Object*/) /*:String*/ {
79210        
79211        //check cookie name
79212        if (!YAHOO.lang.isString(name) || name === ""){
79213            throw new TypeError("Cookie.remove(): Cookie name must be a non-empty string.");
79214        }
79215        
79216        //set options - clone options so the original isn't affected
79217        options = YAHOO.lang.merge(options || {}, {
79218            expires: new Date(0)
79219        });
79220        
79221        //set cookie
79222        return this.set(name, "", options);
79223    },
79224    
79225    /**
79226     * Removes a subcookie with a given name. Removing the last subcookie
79227     *      won't remove the entire cookie unless options.removeIfEmpty is true.
79228     * @param {String} name The name of the cookie in which the subcookie exists.
79229     * @param {String} subName The name of the subcookie to remove.
79230     * @param {Object} options (Optional) An object containing one or more
79231     *      cookie options: path (a string), domain (a string), expires (a Date object),
79232     *      removeIfEmpty (true/false), and secure (true/false). This must be the same
79233     *      settings as the original subcookie.
79234     * @return {String} The created cookie string.
79235     * @method removeSub
79236     * @static
79237     */
79238    removeSub : function(name /*:String*/, subName /*:String*/, options /*:Object*/) /*:String*/ {
79239        
79240        var lang = YAHOO.lang;
79241        
79242        options = options || {};
79243        
79244        //check cookie name
79245        if (!lang.isString(name) || name === ""){
79246            throw new TypeError("Cookie.removeSub(): Cookie name must be a non-empty string.");
79247        }
79248        
79249        //check subcookie name
79250        if (!lang.isString(subName) || subName === ""){
79251            throw new TypeError("Cookie.removeSub(): Subcookie name must be a non-empty string.");
79252        }
79253        
79254        //get all subcookies for this cookie
79255        var subs = this.getSubs(name);
79256        
79257        //delete the indicated subcookie
79258        if (lang.isObject(subs) && lang.hasOwnProperty(subs, subName)){
79259            delete subs[subName];
79260
79261            if (!options.removeIfEmpty) {
79262                //reset the cookie
79263
79264                return this.setSubs(name, subs, options);
79265            } else {
79266                //reset the cookie if there are subcookies left, else remove
79267                for (var key in subs){
79268                    if (lang.hasOwnProperty(subs, key) && !lang.isFunction(subs[key]) && !lang.isUndefined(subs[key])){
79269                        return this.setSubs(name, subs, options);
79270                    }
79271                }
79272                
79273                return this.remove(name, options);
79274            }
79275        } else {
79276            return "";
79277        }
79278        
79279    },
79280    
79281    /**
79282     * Sets a cookie with a given name and value.
79283     * @param {String} name The name of the cookie to set.
79284     * @param {Variant} value The value to set for the cookie.
79285     * @param {Object}
79285 options (Optional) An object containing one or more
79286     *      cookie options: path (a string), domain (a string), expires (a Date object),
79287     *      raw (true/false), and secure (true/false).
79288     * @return {String} The created cookie string.
79289     * @method set
79290     * @static
79291     */
79292    set : function (name /*:String*/, value /*:Variant*/, options /*:Object*/) /*:String*/ {
79293        
79294        var lang = YAHOO.lang;
79295        
79296        options = options || {};
79297        
79298        if (!lang.isString(name)){
79299            throw new TypeError("Cookie.set(): Cookie name must be a string.");
79300        }
79301        
79302        if (lang.isUndefined(value)){
79303            throw new TypeError("Cookie.set(): Value cannot be undefined.");
79304        }
79305        
79306        var text /*:String*/ = this._createCookieString(name, value, !options.raw, options);
79307        document.cookie = text;
79308        return text;
79309    },
79310    
79311    /**
79312     * Sets a sub cookie with a given name to a particular value.
79313     * @param {String} name The name of the cookie to set.
79314     * @param {String} subName The name of the subcookie to set.
79315     * @param {Variant} value The value to set.
79316     * @param {Object} options (Optional) An object containing one or more
79317     *      cookie options: path (a string), domain (a string), expires (a Date object),
79318     *      and secure (true/false).
79319     * @return {String} The created cookie string.
79320     * @method setSub
79321     * @static
79322     */
79323    setSub : function (name /*:String*/, subName /*:String*/, value /*:Variant*/, options /*:Object*/) /*:String*/ {
79324        
79325        var lang = YAHOO.lang;
79326        
79327        if (!lang.isString(name) || name === ""){
79328            throw new TypeError("Cookie.setSub(): Cookie name must be a non-empty string.");
79329        }
79330        
79331        if (!lang.isString(subName) || subName === ""){
79332            throw new TypeError("Cookie.setSub(): Subcookie name must be a non-empty string.");
79333        }
79334        
79335        if (lang.isUndefined(value)){
79336            throw new TypeError("Cookie.setSub(): Subcookie value cannot be undefined.");
79337        }
79338        
79339        var hash /*:Object*/ = this.getSubs(name);
79340        
79341        if (!lang.isObject(hash)){
79342            hash = {};
79343        }
79344        
79345        hash[subName] = value;
79346        
79347        return this.setSubs(name, hash, options);
79348        
79349    },
79350    
79351    /**
79352     * Sets a cookie with a given name to contain a hash of name-value pairs.
79353     * @param {String} name The name of the cookie to set.
79354     * @param {Object} value An object containing name-value pairs.
79355     * @param {Object} options (Optional) An object containing one or more
79356     *      cookie options: path (a string), domain (a string), expires (a Date object),
79357     *      and secure (true/false).
79358     * @return {String} The created cookie string.
79359     * @method setSubs
79360     * @static
79361     */
79362    setSubs : function (name /*:String*/, value /*:Object*/, options /*:Object*/) /*:String*/ {
79363        
79364        var lang = YAHOO.lang;
79365        
79366        if (!lang.isString(name)){
79367            throw new TypeError("Cookie.setSubs(): Cookie name must be a string.");
79368        }
79369        
79370        if (!lang.isObject(value)){
79371            throw new TypeError("Cookie.setSubs(): Cookie value must be an object.");
79372        }
79373        
79374        var text /*:String*/ = this._createCookieString(name, this._createCookieHashString(value), false, options);
79375        document.cookie = text;
79376        return text;
79377    }
79378
79379};
79380
79381YAHOO.register("cookie", YAHOO.util.Cookie, {version: "2.9.0", build: "2800"});
79382/*
79383Copyright (c) 2011, Yahoo! Inc. All rights reserved.
79384Code licensed under the BSD License:
79385http://developer.yahoo.com/yui/license.html
79386version: 2.9.0
79387*/
79388/*extern ActiveXObject, __flash_unloadHandler, __flash_savedUnloadHandler */
79389/*!
79390 * SWFObject v1.5: Flash Player detection and embed - http://blog.deconcept.com/swfobject/
79391 *
79392 * SWFObject is (c) 2007 Geoff Stearns and is released under the MIT License:
79393 * http://www.opensource.org/licenses/mit-license.php
79394 * @namespace YAHOO
79395 */
79396
79397YAHOO.namespace("deconcept"); 
79398	
79399YAHOO.deconcept = YAHOO.deconcept || {};
79400
79401if(typeof YAHOO.deconcept.util == "undefined" || !YAHOO.deconcept.util)
79402{
79403	YAHOO.deconcept.util = {};
79404}
79405
79406if(typeof YAHOO.deconcept.SWFObjectUtil == "undefined" || !YAHOO.deconcept.SWFObjectUtil)
79407{
79408	YAHOO.deconcept.SWFObjectUtil = {};
79409}
79410
79411YAHOO.deconcept.SWFObject = function(swf, id, w, h, ver, c, quality, xiRedirectUrl, redirectUrl, detectKey)
79412{
79413	if(!document.getElementById) { return; }
79414	this.DETECT_KEY = detectKey ? detectKey : 'detectflash';
79415	this.skipDetect = YAHOO.deconcept.util.getRequestParameter(this.DETECT_KEY);
79416	this.params = {};
79417	this.variables = {};
79418	this.attributes = [];
79419	if(swf) { this.setAttribute('swf', swf); }
79420	if(id) { this.setAttribute('id', id); }
79421	if(w) { this.setAttribute('width', w); }
79422	if(h) { this.setAttribute('height', h); }
79423	if(ver) { this.setAttribute('version', new YAHOO.deconcept.PlayerVersion(ver.toString().split("."))); }
79424	this.installedVer = YAHOO.deconcept.SWFObjectUtil.getPlayerVersion();
79425	if (!window.opera && document.all && this.installedVer.major > 7)
79426	{
79427		// only add the onunload cleanup if the Flash Player version supports External Interface and we are in IE
79428		YAHOO.deconcept.SWFObject.doPrepUnload = true;
79429	}
79430	if(c)
79431	{
79432		this.addParam('bgcolor', c);
79433	}
79434	var q = quality ? quality : 'high';
79435	this.addParam('quality', q);
79436	this.setAttribute('useExpressInstall', false);
79437	this.setAttribute('doExpressInstall', false);
79438	var xir = (xiRedirectUrl) ? xiRedirectUrl : window.location;
79439	this.setAttribute('xiRedirectUrl', xir);
79440	this.setAttribute('redirectUrl', '');
79441	if(redirectUrl)
79442	{
79443		this.setAttribute('redirectUrl', redirectUrl);
79444	}
79445};
79446
79447YAHOO.deconcept.SWFObject.prototype =
79448{
79449	useExpressInstall: function(path)
79450	{
79451		this.xiSWFPath = !path ? "expressinstall.swf" : path;
79452		this.setAttribute('useExpressInstall', true);
79453	},
79454	setAttribute: function(name, value){
79455		this.attributes[name] = value;
79456	},
79457	getAttribute: function(name){
79458		return this.attributes[name];
79459	},
79460	addParam: function(name, value){
79461		this.params[name] = value;
79462	},
79463	getParams: function(){
79464		return this.params;
79465	},
79466	addVariable: function(name, value){
79467		this.variables[name] = value;
79468	},
79469	getVariable: function(name){
79470		return this.variables[name];
79471	},
79472	getVariables: function(){
79473		return this.variables;
79474	},
79475	getVariablePairs: function(){
79476		var variablePairs = [];
79477		var key;
79478		var variables = this.getVariables();
79479		for(key in variables)
79480		{
79481			if(variables.hasOwnProperty(key))
79482			{
79483				variablePairs[variablePairs.length] = YAHOO.lang.escapeHTML(key || '') +"="+ YAHOO.lang.escapeHTML(encodeURIComponent(variables[key]  || ''));
79484			}
79485		}
79486		return variablePairs;
79487	},
79488	getSWFHTML: function() {
79489		var swfNode = "";
79490		var params = {};
79491		var key = "";
79492		var pairs = "";
79493		if (navigator.plugins && navigator.mimeTypes && navigator.mimeTypes.length) { // netscape plugin architecture
79494			if (this.getAttribute("doExpressInstall")) {
79495				this.addVariable("MMplayerType", "PlugIn");
79496				this.setAttribute('swf', this.xiSWFPath);
79497			}
79498			swfNode = '<embed type="application/x-shockwave-flash" src="'+ YAHOO.lang.escapeHTML(this.getAttribute('swf') || '') +'" width="'+ YAHOO.lang.escapeHTML(this.getAttribute('width') || '') +'" height="'+ YAHOO.lang.escapeHTML(this.getAttribute('height') || '') +'" style="'+ YAHOO.lang.escapeHTML(this.getAttribute('style') || '') +'"';
79499			swfNode += ' id="'+ YAHOO.lang.escapeHTML(this.getAttribute('id') || '') +'" name="'+ YAHOO.lang.escapeHTML(this.getAttribute('id') || '') +'" ';
79500			params = this.getParams();
79501			for(key in params)
79502			{
79503				if(params.hasOwnProperty(key))
79504				{
79505					swfNode += YAHOO.lang.escapeHTML(key || '') +'="'+ YAHOO.lang.escapeHTML(params[key] || '') +'" ';
79506				}
79507			}
79508			pairs = this.getVariablePairs().join("&");
79509			if (pairs.length > 0){ swfNode += 'flashvars="'+ pairs +'"'; }
79510			swfNode += '/>';
79511		} else { // PC IE
79512			if (this.getAttribute("doExpressInstall")) {
79513				this.addVariable("MMplayerType", "ActiveX");
79514				this.setAttribute('swf', this.xiSWFPath);
79515			}
79516			swfNode = '<object id="'+ YAHOO.lang.escapeHTML(this.getAttribute('id') || '') +'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+ YAHOO.lang.escapeHTML(this.getAttribute('width') || '') +'" height="'+ YAHOO.lang.escapeHTML(this.getAttribute('height') || '') +'" style="'+ YAHOO.lang.escapeHTML(this.getAttribute('style') || '') +'">';
79517			swfNode += '<param name="movie" value="'+ YAHOO.lang.escapeHTML(this.getAttribute('swf') || '') +'" />';
79518			params = this.getParams();
79519			for(key in params)
79520			{
79521				if(params.hasOwnProperty(key))
79522				{
79523					swfNode += '<param name="'+ YAHOO.lang.escapeHTML(key || '') +'" value="'+ YAHOO.lang.escapeHTML(params[key] || '') +'" />';
79524				}
79525			}
79526			pairs = this.getVariablePairs().join("&");
79527			if(pairs.length > 0) {swfNode += '<param name="flashvars" value="'+ pairs +'" />';}
79528			swfNode += "</object>";
79529		}
79530		return swfNode;
79531	},
79532	write: function(elementId)
79533	{
79534		if(this.getAttribute('useExpressInstall')) {
79535			// check to see if we need to do an express install
79536			var expressInstallReqVer = new YAHOO.deconcept.PlayerVersion([6,0,65]);
79537			if (this.installedVer.versionIsValid(expressInstallReqVer) && !this.installedVer.versionIsValid(this.getAttribute('version'))) {
79538				this.setAttribute('doExpressInstall', true);
79539				this.addVariable("MMredirectURL", escape(this.getAttribute('xiRedirectUrl')));
79540				document.title = document.title.slice(0, 47) + " - Flash Player Installation";
79541				this.addVariable("MMdoctitle", document.title);
79542			}
79543		}
79544		if(this.skipDetect || this.getAttribute('doExpressInstall') || this.installedVer.versionIsValid(this.getAttribute('version')))
79545		{
79546			var n = (typeof elementId == 'string') ? document.getElementById(elementId) : elementId;
79547			n.innerHTML = this.getSWFHTML();
79548			return true;
79549		}
79550		else
79551		{
79552			if(this.getAttribute('redirectUrl') !== "")
79553			{
79554				document.location.replace(this.getAttribute('redirectUrl'));
79555			}
79556		}
79557		return false;
79558	}
79559};
79560
79561/* ---- detection functions ---- */
79562YAHOO.deconcept.SWFObjectUtil.getPlayerVersion = function()
79563{
79564	var axo = null;
79565	var PlayerVersion = new YAHOO.deconcept.PlayerVersion([0,0,0]);
79566	if(navigator.plugins && navigator.mimeTypes.length)
79567	{
79568		var x = navigator.plugins["Shockwave Flash"];
79569		if(x && x.description)
79570		{
79571			PlayerVersion = new YAHOO.deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".").split("."));
79572		}
79573	}
79574	else if (navigator.userAgent && navigator.userAgent.indexOf("Windows CE") >= 0)
79575	{ // if Windows CE
79576		var counter = 3;
79577		while(axo)
79578		{
79579			try
79580			{
79581				counter++;
79582				axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+ counter);
79583//				document.write("player v: "+ counter);
79584				PlayerVersion = new YAHOO.deconcept.PlayerVersion([counter,0,0]);
79585			}
79586			catch(e)
79587			{
79588				axo = null;
79589			}
79590		}
79591	}
79592	else
79593	{ // Win IE (non mobile)
79594		// do minor version lookup in IE, but avoid fp6 crashing issues
79595		// see http://blog.deconcept.com/2006/01/11/getvariable-setvariable-crash-internet-explorer-flash-6/
79596		try
79597		{
79598			axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
79599		}
79600		catch(e)
79601		{
79602			try
79603			{
79604				axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");
79605				PlayerVersion = new YAHOO.deconcept.PlayerVersion([6,0,21]);
79606				axo.AllowScriptAccess = "always"; // error if player version < 6.0.47 (thanks to Michael Williams @ Adobe for this code)
79607			}
79608			catch(e)
79609			{
79610				if(PlayerVersion.major == 6)
79611				{
79612					return PlayerVersion;
79613				}
79614			}
79615			try
79616			{
79617				axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
79618			}
79619			catch(e) {}
79620		}
79621		
79622		if(axo !== null)
79623		{
79624			PlayerVersion = new YAHOO.deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(","));
79625		}
79626	}
79627	return PlayerVersion;
79628};
79629
79630YAHOO.deconcept.PlayerVersion = function(arrVersion)
79631{
79632	this.major = arrVersion[0] !== null ? parseInt(arrVersion[0], 0) : 0;
79633	this.minor = arrVersion[1] !== null ? parseInt(arrVersion[1], 0) : 0;
79634	this.rev = arrVersion[2] !== null ? parseInt(arrVersion[2], 0) : 0;
79635};
79636
79637YAHOO.deconcept.PlayerVersion.prototype.versionIsValid = function(fv)
79638{
79639	if(this.major < fv.major)
79640	{
79641		return false;
79642	}
79643	if(this.major > fv.major)
79644	{
79645		return true;
79646	}
79647	if(this.minor < fv.minor)
79648	{
79649		return false;
79650	}
79651	if(this.minor > fv.minor)
79652	{
79653		return true;
79654	}
79655	if(this.rev < fv.rev)
79656	{
79657		return false;
79658	}
79659	return true;
79660};
79661
79662/* ---- get value of query string param ---- */
79663YAHOO.deconcept.util =
79664{
79665	getRequestParameter: function(param)
79666	{
79667		var q = document.location.search || document.location.hash;
79668		if(param === null) { return q; }
79669		if(q)
79670		{
79671			var pairs = q.substring(1).split("&");
79672			for(var i=0; i < pairs.length; i++)
79673			{
79674				if (pairs[i].substring(0, pairs[i].indexOf("=")) == param)
79675				{
79676					return pairs[i].substring((pairs[i].indexOf("=") + 1));
79677				}
79678			}
79679		}
79680		return "";
79681	}
79682};
79683
79684/* fix for video streaming bug */
79685YAHOO.deconcept.SWFObjectUtil.cleanupSWFs = function()
79686{
79687	var objects = document.getElementsByTagName("OBJECT");
79688	for(var i = objects.length - 1; i >= 0; i--)
79689	{
79690		objects[i].style.display = 'none';
79691		for(var x in objects[i])
79692		{
79693			if(typeof objects[i][x] == 'function')
79694			{
79695				objects[i][x] = function(){};
79696			}
79697		}
79698	}
79699};
79700
79701// fixes bug in some fp9 versions see http://blog.deconcept.com/2006/07/28/swfobject-143-released/
79702if(YAHOO.deconcept.SWFObject.doPrepUnload)
79703{
79704	if(!YAHOO.deconcept.unloadSet)
79705	{
79706		YAHOO.deconcept.SWFObjectUtil.prepUnload = function()
79707		{
79708			__flash_unloadHandler = function(){};
79709			__flash_savedUnloadHandler = function(){};
79710			window.attachEvent("onunload", YAHOO.deconcept.SWFObjectUtil.cleanupSWFs);
79711		};
79712		window.attachEvent("onbeforeunload", YAHOO.deconcept.SWFObjectUtil.prepUnload);
79713		YAHOO.deconcept.unloadSet = true;
79714	}
79715}
79716
79717/* add document.getElementById if needed (mobile IE < 5) */
79718if(!document.getElementById && document.all)
79719{
79720	document.getElementById = function(id) { return document.all[id]; };
79721}
79722/**
79723 * Wraps Flash embedding functionality and allows communication with SWF through
79724 * attributes.
79725 *
79726 * @namespace YAHOO.widget
79727 * @class FlashAdapter
79728 * @uses YAHOO.util.AttributeProvider
79729 */
79730YAHOO.widget.FlashAdapter = function(swfURL, containerID, attributes, buttonSkin)
79731{
79732	
79733	this._queue = this._queue || [];
79734	this._events = this._events || {};
79735	this._configs = this._configs || {};
79736	attributes = attributes || {};
79737	
79738	//the Flash Player external interface code from Adobe doesn't play nicely
79739	//with the default value, yui-gen, in IE
79740	this._id = attributes.id = attributes.id || YAHOO.util.Dom.generateId(null, "yuigen");
79741	attributes.version = attributes.version || "9.0.45";
79742	attributes.backgroundColor = attributes.backgroundColor || "#ffffff";
79743	
79744	//we can't use the initial attributes right away
79745	//so save them for once the SWF finishes loading
79746	this._attributes = attributes;
79747	
79748	this._swfURL = swfURL;
79749	this._containerID = containerID;
79750	
79751	//embed the SWF file in the page
79752	this._embedSWF(this._swfURL, this._containerID, attributes.id, attributes.version,
79753		attributes.backgroundColor, attributes.expressInstall, attributes.wmode, buttonSkin);
79754	
79755	/**
79756	 * Fires when the SWF is initialized and communication is possible.
79757	 * @event contentReady
79758	 */
79759	//Fix for iframe cross-domain issue with FF2x 
79760	try
79761	{
79762		this.createEvent("contentReady");
79763	}
79764	catch(e){}
79765};
79766
79767YAHOO.widget.FlashAdapter.owners = YAHOO.widget.FlashAdapter.owners || {};
79768
79769YAHOO.extend(YAHOO.widget.FlashAdapter, YAHOO.util.AttributeProvider,
79770{
79771	/**
79772	 * The URL of the SWF file.
79773	 * @property _swfURL
79774	 * @type String
79775	 * @private
79776	 */
79777	_swfURL: null,
79778
79779	/**
79780	 * The ID of the containing DIV.
79781	 * @property _containerID
79782	 * @type String
79783	 * @private
79784	 */
79785	_containerID: null,
79786
79787	/**
79788	 * A reference to the embedded SWF file.
79789	 * @property _swf
79790	 * @private
79791	 */
79792	_swf: null,
79793
79794	/**
79795	 * The id of this instance.
79796	 * @property _id
79797	 * @type String
79798	 * @private
79799	 */
79800	_id: null,
79801
79802	/**
79803	 * Indicates whether the SWF has been initialized and is ready
79804	 * to communicate with JavaScript
79805	 * @property _initialized
79806	 * @type Boolean
79807	 * @private
79808	 */
79809	_initialized: false,
79810	
79811	/**
79812	 * The initializing attributes are stored here until the SWF is ready.
79813	 * @property _attributes
79814	 * @type Object
79815	 * @private
79816	 */
79817	_attributes: null, //the intializing attributes
79818
79819	/**
79820	 * Public accessor to the unique name of the FlashAdapter instance.
79821	 *
79822	 * @method toString
79823	 * @return {String} Unique name of the FlashAdapter instance.
79824	 */
79825	toString: function()
79826	{
79827		return "FlashAdapter " + this._id;
79828	},
79829
79830	/**
79831	 * Nulls out the entire FlashAdapter instance and related objects and removes attached
79832	 * event listeners and clears out DOM elements inside the container. After calling
79833	 * this method, the instance reference should be expliclitly nulled by implementer,
79834	 * as in myChart = null. Use with caution!
79835	 *
79836	 * @method destroy
79837	 */
79838	destroy: function()
79839	{
79840		//kill the Flash Player instance
79841		if(this._swf)
79842		{
79843			var container = YAHOO.util.Dom.get(this._containerID);
79844			container.removeChild(this._swf);
79845		}
79846		
79847		var instanceName = this._id;
79848		
79849		//null out properties
79850		for(var prop in this)
79851		{
79852			if(YAHOO.lang.hasOwnProperty(this, prop))
79853			{
79854				this[prop] = null;
79855			}
79856		}
79857		
79858		YAHOO.log("FlashAdapter instance destroyed: " + instanceName);
79859	},
79860
79861	/**
79862	 * Embeds the SWF in the page and associates it with this instance.
79863	 *
79864	 * @method _embedSWF
79865	 * @private
79866	 */
79867	_embedSWF: function(swfURL, containerID, swfID, version, backgroundColor, expressInstall, wmode, buttonSkin)
79868	{
79869		//standard SWFObject embed
79870		var swfObj = new YAHOO.deconcept.SWFObject(swfURL, swfID, "100%", "100%", version, backgroundColor);
79871
79872		if(expressInstall)
79873		{
79874			swfObj.useExpressInstall(expressInstall);
79875		}
79876
79877		//make sure we can communicate with ExternalInterface
79878		swfObj.addParam("allowScriptAccess", "always");
79879		
79880		if(wmode)
79881		{
79882			swfObj.addParam("wmode", wmode);
79883		}
79884		
79885		swfObj.addParam("menu", "false");
79886		
79887		//again, a useful ExternalInterface trick
79888		swfObj.addVariable("allowedDomain", document.location.hostname);
79889
79890		//tell the SWF which HTML element it is in
79891		swfObj.addVariable("YUISwfId", swfID);
79892
79893		// set the name of the function to call when the swf has an event
79894		swfObj.addVariable("YUIBridgeCallback", "YAHOO.widget.FlashAdapter.eventHandler");
79895		if (buttonSkin) {
79896		swfObj.addVariable("buttonSkin", buttonSkin);
79897		}
79898		var container = YAHOO.util.Dom.get(containerID);
79899		var result = swfObj.write(container);
79900		if(result)
79901		{
79902			this._swf = YAHOO.util.Dom.get(swfID);
79903			YAHOO.widget.FlashAdapter.owners[swfID] = this;
79904		}
79905		else
79906		{
79907			YAHOO.log("Unable to load SWF " + swfURL);
79908		}
79909	},
79910
79911	/**
79912	 * Handles or re-dispatches events received from the SWF.
79913	 *
79914	 * @method _eventHandler
79915	 * @private
79916	 */
79917	_eventHandler: function(event)
79918	{
79919		var type = event.type;
79920		switch(type)
79921		{
79922			case "swfReady":
79923   				this._loadHandler();
79924				return;
79925			case "log":
79926				YAHOO.log(event.message, event.category, this.toString());
79927				return;
79928		}
79929		
79930		
79931		//be sure to return after your case or the event will automatically fire!
79932		this.fireEvent(type, event);
79933	},
79934
79935	/**
79936	 * Called when the SWF has been initialized.
79937	 *
79938	 * @method _loadHandler
79939	 * @private
79940	 */
79941	_loadHandler: function()
79942	{
79943		this._initialized = false;
79944		this._initAttributes(this._attributes);
79945		this.setAttributes(this._attributes, true);
79946		
79947		this._initialized = true;
79948		this.fireEvent("contentReady");
79949	},
79950	
79951	set: function(name, value)
79952	{
79953		//save all the attributes in case the swf reloads
79954		//so that we can pass them in again
79955		this._attributes[name] = value;
79956		YAHOO.widget.FlashAdapter.superclass.set.call(this, name, value);
79957	},
79958	
79959	/**
79960	 * Initializes the attributes.
79961	 *
79962	 * @method _initAttributes
79963	 * @private
79964	 */
79965	_initAttributes: function(attributes)
79966	{
79967		//should be overridden if other attributes need to be set up
79968
79969		/**
79970		 * @attribute wmode
79971		 * @description Sets the window mode of the Flash Player control. May be
79972		 *		"window", "opaque", or "transparent". Only available in the constructor
79973		 *		because it may not be set after Flash Player has been embedded in the page.
79974		 * @type String
79975		 */
79976		 
79977		/**
79978		 * @attribute expressInstall
79979		 * @description URL pointing to a SWF file that handles Flash Player's express
79980		 *		install feature. Only available in the constructor because it may not be
79981		 *		set after Flash Player has been embedded in the page.
79982		 * @type String
79983		 */
79984
79985		/**
79986		 * @attribute version
79987		 * @description Minimum required version for the SWF file. Only available in the constructor because it may not be
79988		 *		set after Flash Player has been embedded in the page.
79989		 * @type String
79990		 */
79991
79992		/**
79993		 * @attribute backgroundColor
79994		 * @description The background color of the SWF. Only available in the constructor because it may not be
79995		 *		set after Flash Player has been embedded in the page.
79996		 * @type String
79997		 */
79998		 
79999		/**
80000		 * @attribute altText
80001		 * @description The alternative text to provide for screen readers and other assistive technology.
80002		 * @type String
80003		 */
80004		this.getAttributeConfig("altText",
80005		{
80006			method: this._getAltText
80007		});
80008		this.setAttributeConfig("altText",
80009		{
80010			method: this._setAltText
80011		});
80012		
80013		/**
80014		 * @attribute swfURL
80015		 * @description Absolute or relative URL to the SWF displayed by the FlashAdapter. Only available in the constructor because it may not be
80016		 *		set after Flash Player has been embedded in the page.
80017		 * @type String
80018		 */
80019		this.getAttributeConfig("swfURL",
80020		{
80021			method: this._getSWFURL
80022		});
80023	},
80024	
80025	/**
80026	 * Getter for swfURL attribute.
80027	 *
80028	 * @method _getSWFURL
80029	 * @private
80030	 */
80031	_getSWFURL: function()
80032	{
80033		return this._swfURL;
80034	},
80035	
80036	/**
80037	 * Getter for altText attribute.
80038	 *
80039	 * @method _getAltText
80040	 * @private
80041	 */
80042	_getAltText: function()
80043	{
80044		return this._swf.getAltText();
80045	},
80046
80047	/**
80048	 * Setter for altText attribute.
80049	 *
80050	 * @method _setAltText
80051	 * @private
80052	 */
80053	_setAltText: function(value)
80054	{
80055		return this._swf.setAltText(value);
80056	}
80057});
80058
80059
80060/**
80061 * Receives event messages from SWF and passes them to the correct instance
80062 * of FlashAdapter.
80063 *
80064 * @method YAHOO.widget.FlashAdapter.eventHandler
80065 * @static
80066 * @private
80067 */
80068YAHOO.widget.FlashAdapter.eventHandler = function(elementID, event)
80069{
80070
80071	if(!YAHOO.widget.FlashAdapter.owners[elementID])
80072	{
80073		//fix for ie: if owner doesn't exist yet, try again in a moment
80074		setTimeout(function() { YAHOO.widget.FlashAdapter.eventHandler( elementID, event ); }, 0);
80075	}
80076	else
80077	{
80078		YAHOO.widget.FlashAdapter.owners[elementID]._eventHandler(event);
80079	}
80080};
80081
80082/**
80083 * The number of proxy functions that have been created.
80084 * @static
80085 * @private
80086 */
80087YAHOO.widget.FlashAdapter.proxyFunctionCount = 0;
80088
80089/**
80090 * Creates a globally accessible function that wraps a function reference.
80091 * Returns the proxy function's name as a string for use by the SWF through
80092 * ExternalInterface.
80093 *
80094 * @method YAHOO.widget.FlashAdapter.createProxyFunction
80095 * @static
80096 * @private
80097 */
80098YAHOO.widget.FlashAdapter.createProxyFunction = function(func)
80099{
80100	var index = YAHOO.widget.FlashAdapter.proxyFunctionCount;
80101	YAHOO.widget.FlashAdapter["proxyFunction" + index] = function()
80102	{
80103		return func.apply(null, arguments);
80104	};
80105	YAHOO.widget.FlashAdapter.proxyFunctionCount++;
80106	return "YAHOO.widget.FlashAdapter.proxyFunction" + index.toString();
80107};
80108
80109/**
80110 * Removes a function created with createProxyFunction()
80111 * 
80112 * @method YAHOO.widget.FlashAdapter.removeProxyFunction
80113 * @static
80114 * @private
80115 */
80116YAHOO.widget.FlashAdapter.removeProxyFunction = function(funcName)
80117{
80118	//quick error check
80119	if(!funcName || funcName.indexOf("YAHOO.widget.FlashAdapter.proxyFunction") < 0)
80120	{
80121		return;
80122	}
80123	
80124	funcName = funcName.substr(26);
80125	YAHOO.widget.FlashAdapter[funcName] = null;
80126};
80127/**
80128 * The YUI Uploader Control
80129 * @module uploader
80130 * @description <p>YUI Uploader provides file upload functionality that goes beyond the basic browser-based methods. 
80131 * Specifically, the YUI Uploader allows for:
80132 * <ol>
80133 * <li> Multiple file selection in a single "Open File" dialog.</li>
80134 * <li> File extension filters to facilitate the user's selection.</li>
80135 * <li> Progress tracking for file uploads.</li>
80136 * <li> A range of file metadata: filename, size, date created, date modified, and author.</li>
80137 * <li> A set of events dispatched on various aspects of the file upload process: file selection, upload progress, upload completion, etc.</li>
80138 * <li> Inclusion of additional data in the file upload POST request.</li>
80139 * <li> Faster file upload on broadband connections due to the modified SEND buffer size.</li>
80140 * <li> Same-page server response upon completion of the file upload.</li>
80141 * </ol>
80142 * </p>
80143 * @title Uploader
80144 * @namespace YAHOO.widget
80145 * @requires yahoo, dom, element, event
80146 */
80147/**
80148 * Uploader class for the YUI Uploader component.
80149 *
80150 * @namespace YAHOO.widget
80151 * @class Uploader
80152 * @uses YAHOO.widget.FlashAdapter
80153 * @constructor
80154 * @param containerId {HTMLElement} Container element for the Flash Player instance.
80155 * @param buttonSkin {String} [optional]. If defined, the uploader is 
80156 * rendered as a button. This parameter must provide the URL of a button
80157 * skin sprite image. Acceptable types are: jpg, gif, png and swf. The 
80158 * sprite is divided evenly into four sections along its height (e.g., if
80159 * the sprite is 200 px tall, it's divided into four sections 50px each).
80160 * Each section is used as a skin for a specific state of the button: top
80161 * section is "up", second section is "over", third section is "down", and
80162 * fourth section is "disabled". 
80163 * If the parameter is not supplied, the uploader is rendered transparent,
80164 * and it's the developer's responsibility to create a visible UI below it.
80165 * @param forceTransparent {Boolean} This parameter, if true, forces the Flash
80166 * UI to be rendered with wmode set to "transparent". This behavior is useful 
80167 * in conjunction with non-rectangular button skins with PNG transparency. 
80168 * The parameter is false by default, and ignored if no buttonSkin is defined.
80169  */
80170YAHOO.widget.Uploader = function(containerId, buttonSkin, forceTransparent)
80171{
80172	var newWMode = "window";
80173
80174	if (!(buttonSkin) || (buttonSkin && forceTransparent)) {
80175		newWMode = "transparent";
80176	}
80177
80178	
80179 	YAHOO.widget.Uploader.superclass.constructor.call(this, YAHOO.widget.Uploader.SWFURL, containerId, {wmode:newWMode}, buttonSkin);
80180
80181	/**
80182	 * Fires when the mouse is pressed over the Uploader.
80183	 * Only fires when the Uploader UI is enabled and
80184	 * the render type is 'transparent'.
80185	 *
80186	 * @event mouseDown
80187	 * @param event.type {String} The event type
80188	 */
80189	this.createEvent("mouseDown");
80190	
80191	/**
80192	 * Fires when the mouse is released over the Uploader.
80193	 * Only fires when the Uploader UI is enabled and
80194	 * the render type is 'transparent'.
80195	 *
80196	 * @event mouseUp
80197	 * @param event.type {String} The event type
80198	 */
80199	this.createEvent("mouseUp");
80200
80201	/**
80202	 * Fires when the mouse rolls over the Uploader.
80203	 *
80204	 * @event rollOver
80205	 * @param event.type {String} The event type
80206	 */
80207	this.createEvent("rollOver");
80208	
80209	/**
80210	 * Fires when the mouse rolls out of the Uploader.
80211	 *
80212	 * @event rollOut
80213	 * @param event.type {String} The event type
80214	 */
80215	this.createEvent("rollOut");
80216	
80217	/**
80218	 * Fires when the uploader is clicked.
80219	 *
80220	 * @event click
80221	 * @param event.type {String} The event type
80222	 */
80223	this.createEvent("click");
80224	
80225	/**
80226	 * Fires when the user has finished selecting files in the "Open File" dialog.
80227	 *
80228	 * @event fileSelect
80229	 * @param event.type {String} The event type
80230	 * @param event.fileList {Object} A dictionary of objects with file information
80231	 * @param event.fileList[].size {Number} File size in bytes for a specific file in fileList
80232	 * @param event.fileList[].cDate {Date} Creation date for a specific file in fileList
80233	 * @param event.fileList[].mDate {Date} Modification date for a specific file in fileList
80234	 * @param event.fileList[].name {String} File name for a specific file in fileList
80235	 * @param event.fileList[].id {String} Unique file id of a specific file in fileList
80236	 */
80237	this.createEvent("fileSelect");
80238
80239	/**
80240	 * Fires when an upload of a specific file has started.
80241	 *
80242	 * @event uploadStart
80243	 * @param event.type {String} The event type
80244	 * @param event.id {String} The id of the file that's started to upload
80245	 */
80246	this.createEvent("uploadStart");
80247
80248	/**
80249	 * Fires when new information about the upload progress for a specific file is available.
80250	 *
80251	 * @event uploadProgress
80252	 * @param event.type {String} The event type
80253	 * @param event.id {String} The id of the file with which the upload progress data is associated
80254	 * @param bytesLoaded {Number} The number of bytes of the file uploaded so far
80255	 * @param bytesTotal {Number} The total size of the file
80256	 */
80257	this.createEvent("uploadProgress");
80258	
80259	/**
80260	 * Fires when an upload for a specific file is cancelled.
80261	 *
80262	 * @event uploadCancel
80263	 * @param event.type {String} The event type
80264	 * @param event.id {String} The id of the file with which the upload has been cancelled.
80265	 */	
80266	this.createEvent("uploadCancel");
80267
80268	/**
80269	 * Fires when an upload for a specific file is complete.
80270	 *
80271	 * @event uploadComplete
80272	 * @param event.type {String} The event type
80273	 * @param event.id {String} The id of the file for which the upload has been completed.
80274	 */	
80275	this.createEvent("uploadComplete");
80276
80277	/**
80278	 * Fires when the server sends data in response to a completed upload.
80279	 *
80280	 * @event uploadCompleteData
80281	 * @param event.type {String} The event type
80282	 * @param event.id {String} The id of the file for which the upload has been completed.
80283	 * @param event.data {String} The raw data returned by the server in response to the upload.
80284	 */	
80285	this.createEvent("uploadCompleteData");
80286	
80287	/**
80288	 * Fires when an upload error occurs.
80289	 *
80290	 * @event uploadError
80291	 * @param event.type {String} The event type
80292	 * @param event.id {String} The id of the file that was being uploaded when the error has occurred.
80293	 * @param event.status {String} The status message associated with the error.
80294	 */	
80295	this.createEvent("uploadError");
80296}
80297
80298/**
80299 * Location of the Uploader SWF
80300 *
80301 * @property Chart.SWFURL
80302 * @private
80303 * @static
80304 * @final
80305 * @default "assets/uploader.swf"
80306 */
80307YAHOO.widget.Uploader.SWFURL = "assets/uploader.swf";
80308
80309YAHOO.extend(YAHOO.widget.Uploader, YAHOO.widget.FlashAdapter,
80310{	
80311/**
80312 * Starts the upload of the file specified by fileID to the location specified by uploadScriptPath.
80313 *
80314 * @param fileID {String} The id of the file to start uploading.
80315 * @param uploadScriptPath {String} The URL of the upload location.
80316 * @param method {String} Either "GET" or "POST", specifying how the variables accompanying the file upload POST request should be submitted. "GET" by default.
80317 * @param vars {Object}
80317 The object containing variables to be sent in the same request as the file upload.
80318 * @param fieldName {String} The name of the variable in the POST request containing the file data. "Filedata" by default.
80319 * </code> 
80320 */
80321	upload: function(fileID, uploadScriptPath, method, vars, fieldName)
80322	{
80323		this._swf.upload(fileID, uploadScriptPath, method, vars, fieldName);
80324	},
80325	
80326/**
80327 * Starts the upload of the files specified by fileIDs, or adds them to a currently running queue. The upload queue is automatically managed.
80328 *
80329 * @param fileIDs {Array} The ids of the files to start uploading.
80330 * @param uploadScriptPath {String} The URL of the upload location.
80331 * @param method {String} Either "GET" or "POST", specifying how the variables accompanying the file upload POST request should be submitted. "GET" by default.
80332 * @param vars {Object} The object containing variables to be sent in the same request as the file upload.
80333 * @param fieldName {String} The name of the variable in the POST request containing the file data. "Filedata" by default.
80334 * </code> 
80335 */
80336	uploadThese: function(fileIDs, uploadScriptPath, method, vars, fieldName)
80337	{
80338		this._swf.uploadThese(fileIDs, uploadScriptPath, method, vars, fieldName);
80339	},
80340	
80341/**
80342 * Starts uploading all files in the queue. If this function is called, the upload queue is automatically managed.
80343 *
80344 * @param uploadScriptPath {String} The URL of the upload location.
80345 * @param method {String} Either "GET" or "POST", specifying how the variables accompanying the file upload POST request should be submitted. "GET" by default.
80346 * @param vars {Object} The object containing variables to be sent in the same request as the file upload.
80347 * @param fieldName {String} The name of the variable in the POST request containing the file data. "Filedata" by default.
80348 * </code> 
80349 */
80350	uploadAll: function(uploadScriptPath, method, vars, fieldName)
80351	{
80352		this._swf.uploadAll(uploadScriptPath, method, vars, fieldName);
80353	},
80354
80355/**
80356 * Cancels the upload of a specified file. If no file id is specified, all ongoing uploads are cancelled.
80357 *
80358 * @param fileID {String} The ID of the file whose upload should be cancelled.
80359 */
80360	cancel: function(fileID)
80361	{
80362		this._swf.cancel(fileID);
80363	},
80364
80365/**
80366 * Clears the list of files queued for upload.
80367 *
80368 */
80369	clearFileList: function()
80370	{
80371		this._swf.clearFileList();
80372	},
80373	
80374/**
80375 * Removes the specified file from the upload queue. 
80376 *
80377 * @param fileID {String} The id of the file to remove from the upload queue. 
80378 */
80379	removeFile: function (fileID) 
80380	{
80381		this._swf.removeFile(fileID);
80382	},
80383
80384/**
80385 * Turns the logging functionality on.
80386 * Uses Flash internal trace logging, as well as YUI Logger, if available.
80387 *
80388 * @param allowLogging {Boolean} If true, logs are output; otherwise, no logs are produced.
80389 */
80390    setAllowLogging: function (allowLogging)
80391    {
80392      	this._swf.setAllowLogging(allowLogging);
80393    },
80394
80395/**
80396 * Sets the number of simultaneous uploads when using uploadAll()
80397 * The minimum value is 1, and maximum value is 5. The default value is 2.
80398 *
80399 * @param simUploadLimit {int} Number of simultaneous uploads, between 1 and 5.
80400 */
80401    setSimUploadLimit : function (simUploadLimit)
80402    {
80403       this._swf.setSimUploadLimit(simUploadLimit);
80404    },
80405
80406/**
80407 * Sets the flag allowing users to select multiple files for the upload.
80408 *
80409 * @param allowMultipleFiles {Boolean} If true, multiple files can be selected. False by default.
80410 */     
80411    setAllowMultipleFiles : function (allowMultipleFiles) 
80412    {
80413       this._swf.setAllowMultipleFiles(allowMultipleFiles);
80414    },
80415
80416/**
80417 * Sets the file filters for the "Browse" dialog.
80418 *
80419 *  @param newFilterArray An array of sets of key-value pairs of the form
80420 *  {extensions: extensionString, description: descriptionString, [optional]macType: macTypeString}
80421 *  The extensions string is a semicolon-delimited list of elements of the form "*.xxx", 
80422 *  e.g. "*.jpg;*.gif;*.png". 
80423 */       
80424    setFileFilters : function (fileFilters) 
80425    {
80426       this._swf.setFileFilters(fileFilters);
80427    },
80428
80429	/**
80430	 * Enables the mouse events on the Uploader.
80431	 * If the uploader is being rendered as a button,
80432	 * then the button's skin is set to "up"
80433	 * (first section of the button skin sprite).
80434	 *
80435	 */
80436	enable : function ()
80437	{
80438		this._swf.enable();
80439	},
80440
80441	/**
80442	 * Disables the mouse events on the Uploader.
80443	 * If the uploader is being rendered as a button,
80444	 * then the button's skin is set to "disabled"
80445	 * (fourth section of the button skin sprite).
80446	 *
80447	 */
80448	disable : function () 
80449	{
80450		this._swf.disable();
80451	}
80452});
80453YAHOO.register("uploader", YAHOO.widget.Uploader, {version: "2.9.0", build: "2800"});
80454/*
80455Copyright (c) 2011, Yahoo! Inc. All rights reserved.
80456Code licensed under the BSD License:
80457http://developer.yahoo.com/yui/license.html
80458version: 2.9.0
80459*/
80460(function () {
80461
80462    /**
80463    * Config is a utility used within an Object to allow the implementer to
80464    * maintain a list of local configuration properties and listen for changes 
80465    * to those properties dynamically using CustomEvent. The initial values are 
80466    * also maintained so that the configuration can be reset at any given point 
80467    * to its initial state.
80468    * @namespace YAHOO.util
80469    * @class Config
80470    * @constructor
80471    * @param {Object} owner The owner Object to which this Config Object belongs
80472    */
80473    YAHOO.util.Config = function (owner) {
80474
80475        if (owner) {
80476            this.init(owner);
80477        }
80478
80479        if (!owner) {  YAHOO.log("No owner specified for Config object", "error", "Config"); }
80480
80481    };
80482
80483
80484    var Lang = YAHOO.lang,
80485        CustomEvent = YAHOO.util.CustomEvent,
80486        Config = YAHOO.util.Config;
80487
80488
80489    /**
80490     * Constant representing the CustomEvent type for the config changed event.
80491     * @property YAHOO.util.Config.CONFIG_CHANGED_EVENT
80492     * @private
80493     * @static
80494     * @final
80495     */
80496    Config.CONFIG_CHANGED_EVENT = "configChanged";
80497    
80498    /**
80499     * Constant representing the boolean type string
80500     * @property YAHOO.util.Config.BOOLEAN_TYPE
80501     * @private
80502     * @static
80503     * @final
80504     */
80505    Config.BOOLEAN_TYPE = "boolean";
80506    
80507    Config.prototype = {
80508     
80509        /**
80510        * Object reference to the owner of this Config Object
80511        * @property owner
80512        * @type Object
80513        */
80514        owner: null,
80515        
80516        /**
80517        * Boolean flag that specifies whether a queue is currently 
80518        * being executed
80519        * @property queueInProgress
80520        * @type Boolean
80521        */
80522        queueInProgress: false,
80523        
80524        /**
80525        * Maintains the local collection of configuration property objects and 
80526        * their specified values
80527        * @property config
80528        * @private
80529        * @type Object
80530        */ 
80531        config: null,
80532        
80533        /**
80534        * Maintains the local collection of configuration property objects as 
80535        * they were initially applied.
80536        * This object is used when resetting a property.
80537        * @property initialConfig
80538        * @private
80539        * @type Object
80540        */ 
80541        initialConfig: null,
80542        
80543        /**
80544        * Maintains the local, normalized CustomEvent queue
80545        * @property eventQueue
80546        * @private
80547        * @type Object
80548        */ 
80549        eventQueue: null,
80550        
80551        /**
80552        * Custom Event, notifying subscribers when Config properties are set 
80553        * (setProperty is called without the silent flag
80554        * @event configChangedEvent
80555        */
80556        configChangedEvent: null,
80557    
80558        /**
80559        * Initializes the configuration Object and all of its local members.
80560        * @method init
80561        * @param {Object} owner The owner Object to which this Config 
80562        * Object belongs
80563        */
80564        init: function (owner) {
80565    
80566            this.owner = owner;
80567    
80568            this.configChangedEvent = 
80569                this.createEvent(Config.CONFIG_CHANGED_EVENT);
80570    
80571            this.configChangedEvent.signature = CustomEvent.LIST;
80572            this.queueInProgress = false;
80573            this.config = {};
80574            this.initialConfig = {};
80575            this.eventQueue = [];
80576        
80577        },
80578        
80579        /**
80580        * Validates that the value passed in is a Boolean.
80581        * @method checkBoolean
80582        * @param {Object} val The value to validate
80583        * @return {Boolean} true, if the value is valid
80584        */ 
80585        checkBoolean: function (val) {
80586            return (typeof val == Config.BOOLEAN_TYPE);
80587        },
80588        
80589        /**
80590        * Validates that the value passed in is a number.
80591        * @method checkNumber
80592        * @param {Object} val The value to validate
80593        * @return {Boolean} true, if the value is valid
80594        */
80595        checkNumber: function (val) {
80596            return (!isNaN(val));
80597        },
80598        
80599        /**
80600        * Fires a configuration property event using the specified value. 
80601        * @method fireEvent
80602        * @private
80603        * @param {String} key The configuration property's name
80604        * @param {value} Object The value of the correct type for the property
80605        */ 
80606        fireEvent: function ( key, value ) {
80607            YAHOO.log("Firing Config event: " + key + "=" + value, "info", "Config");
80608            var property = this.config[key];
80609        
80610            if (property && property.event) {
80611                property.event.fire(value);
80612            } 
80613        },
80614        
80615        /**
80616        * Adds a property to the Config Object's private config hash.
80617        * @method addProperty
80618        * @param {String} key The configuration property's name
80619        * @param {Object} propertyObject The Object containing all of this 
80620        * property's arguments
80621        */
80622        addProperty: function ( key, propertyObject ) {
80623            key = key.toLowerCase();
80624            YAHOO.log("Added property: " + key, "info", "Config");
80625        
80626            this.config[key] = propertyObject;
80627        
80628            propertyObject.event = this.createEvent(key, { scope: this.owner });
80629            propertyObject.event.signature = CustomEvent.LIST;
80630            
80631            
80632            propertyObject.key = key;
80633        
80634            if (propertyObject.handler) {
80635                propertyObject.event.subscribe(propertyObject.handler, 
80636                    this.owner);
80637            }
80638        
80639            this.setProperty(key, propertyObject.value, true);
80640            
80641            if (! propertyObject.suppressEvent) {
80642                this.queueProperty(key, propertyObject.value);
80643            }
80644            
80645        },
80646        
80647        /**
80648        * Returns a key-value configuration map of the values currently set in  
80649        * the Config Object.
80650        * @method getConfig
80651        * @return {Object} The current config, represented in a key-value map
80652        */
80653        getConfig: function () {
80654        
80655            var cfg = {},
80656                currCfg = this.config,
80657                prop,
80658                property;
80659                
80660            for (prop in currCfg) {
80661                if (Lang.hasOwnProperty(currCfg, prop)) {
80662                    property = currCfg[prop];
80663                    if (property && property.event) {
80664                        cfg[prop] = property.value;
80665                    }
80666                }
80667            }
80668
80669            return cfg;
80670        },
80671        
80672        /**
80673        * Returns the value of specified property.
80674        * @method getProperty
80675        * @param {String} key The name of the property
80676        * @return {Object}  The value of the specified property
80677        */
80678        getProperty: function (key) {
80679            var property = this.config[key.toLowerCase()];
80680            if (property && property.event) {
80681                return property.value;
80682            } else {
80683                return undefined;
80684            }
80685        },
80686        
80687        /**
80688        * Resets the specified property's value to its initial value.
80689        * @method resetProperty
80690        * @param {String} key The name of the property
80691        * @return {Boolean} True is the property was reset, false if not
80692        */
80693        resetProperty: function (key) {
80694            key = key.toLowerCase();
80695
80696            var property = this.config[key];
80697
80698            if (property && property.event) {
80699                if (key in this.initialConfig) {
80700                    this.setProperty(key, this.initialConfig[key]);
80701                    return true;
80702                }
80703            } else {
80704                return false;
80705            }
80706        },
80707        
80708        /**
80709        * Sets the value of a property. If the silent property is passed as 
80710        * true, the property's event will not be fired.
80711        * @method setProperty
80712        * @param {String} key The name of the property
80713        * @param {String} value The value to set the property to
80714        * @param {Boolean} silent Whether the value should be set silently, 
80715        * without firing the property event.
80716        * @return {Boolean} True, if the set was successful, false if it failed.
80717        */
80718        setProperty: function (key, value, silent) {
80719        
80720            var property;
80721        
80722            key = key.toLowerCase();
80723            YAHOO.log("setProperty: " + key + "=" + value, "info", "Config");
80724        
80725            if (this.queueInProgress && ! silent) {
80726                // Currently running through a queue... 
80727                this.queueProperty(key,value);
80728                return true;
80729    
80730            } else {
80731                property = this.config[key];
80732                if (property && property.event) {
80733                    if (property.validator && !property.validator(value)) {
80734                        return false;
80735                    } else {
80736                        property.value = value;
80737                        if (! silent) {
80738                            this.fireEvent(key, value);
80739                            this.configChangedEvent.fire([key, value]);
80740                        }
80741                        return true;
80742                    }
80743                } else {
80744                    return false;
80745                }
80746            }
80747        },
80748        
80749        /**
80750        * Sets the value of a property and queues its event to execute. If the 
80751        * event is already scheduled to execute, it is
80752        * moved from its current position to the end of the queue.
80753        * @method queueProperty
80754        * @param {String} key The name of the property
80755        * @param {String} value The value to set the property to
80756        * @return {Boolean}  true, if the set was successful, false if 
80757        * it failed.
80758        */ 
80759        queueProperty: function (key, value) {
80760        
80761            key = key.toLowerCase();
80762            YAHOO.log("queueProperty: " + key + "=" + value, "info", "Config");
80763        
80764            var property = this.config[key],
80765                foundDuplicate = false,
80766                iLen,
80767                queueItem,
80768                queueItemKey,
80769                queueItemValue,
80770                sLen,
80771                supercedesCheck,
80772                qLen,
80773                queueItemCheck,
80774                queueItemCheckKey,
80775                queueItemCheckValue,
80776                i,
80777                s,
80778                q;
80779                                
80780            if (property && property.event) {
80781    
80782                if (!Lang.isUndefined(value) && property.validator && 
80783                    !property.validator(value)) { // validator
80784                    return false;
80785                } else {
80786        
80787                    if (!Lang.isUndefined(value)) {
80788                        property.value = value;
80789                    } else {
80790                        value = property.value;
80791                    }
80792        
80793                    foundDuplicate = false;
80794                    iLen = this.eventQueue.length;
80795        
80796                    for (i = 0; i < iLen; i++) {
80797                        queueItem = this.eventQueue[i];
80798        
80799                        if (queueItem) {
80800                            queueItemKey = queueItem[0];
80801                            queueItemValue = queueItem[1];
80802
80803                            if (queueItemKey == key) {
80804    
80805                                /*
80806                                    found a dupe... push to end of queue, null 
80807                                    current item, and break
80808                                */
80809    
80810                                this.eventQueue[i] = null;
80811    
80812                                this.eventQueue.push(
80813                                    [key, (!Lang.isUndefined(value) ? 
80814                                    value : queueItemValue)]);
80815    
80816                                foundDuplicate = true;
80817                                break;
80818                            }
80819                        }
80820                    }
80821                    
80822                    // this is a refire, or a new property in the queue
80823    
80824                    if (! foundDuplicate && !Lang.isUndefined(value)) { 
80825                        this.eventQueue.push([key, value]);
80826                    }
80827                }
80828        
80829                if (property.supercedes) {
80830
80831                    sLen = property.supercedes.length;
80832
80833                    for (s = 0; s < sLen; s++) {
80834
80835                        supercedesCheck = property.supercedes[s];
80836                        qLen = this.eventQueue.length;
80837
80838                        for (q = 0; q < qLen; q++) {
80839                            queueItemCheck = this.eventQueue[q];
80840
80841                            if (queueItemCheck) {
80842                                queueItemCheckKey = queueItemCheck[0];
80843                                queueItemCheckValue = queueItemCheck[1];
80844
80845                                if (queueItemCheckKey == 
80846                                    supercedesCheck.toLowerCase() ) {
80847
80848                                    this.eventQueue.push([queueItemCheckKey, 
80849                                        queueItemCheckValue]);
80850
80851                                    this.eventQueue[q] = null;
80852                                    break;
80853
80854                                }
80855                            }
80856                        }
80857                    }
80858                }
80859
80860                YAHOO.log("Config event queue: " + this.outputEventQueue(), "info", "Config");
80861
80862                return true;
80863            } else {
80864                return false;
80865            }
80866        },
80867        
80868        /**
80869        * Fires the event for a property using the property's current value.
80870        * @method refireEvent
80871        * @param {String} key The name of the property
80872        */
80873        refireEvent: function (key) {
80874    
80875            key = key.toLowerCase();
80876        
80877            var property = this.config[key];
80878    
80879            if (property && property.event && 
80880    
80881                !Lang.isUndefined(property.value)) {
80882    
80883                if (this.queueInProgress) {
80884    
80885                    this.queueProperty(key);
80886    
80887                } else {
80888    
80889                    this.fireEvent(key, property.value);
80890    
80891                }
80892    
80893            }
80894        },
80895        
80896        /**
80897        * Applies a key-value Object literal to the configuration, replacing  
80898        * any existing values, and queueing the property events.
80899        * Although the values will be set, fireQueue() must be called for their 
80900        * associated events to execute.
80901        * @method applyConfig
80902        * @param {Object} userConfig The configuration Object literal
80903        * @param {Boolean} init  When set to true, the initialConfig will 
80904        * be set to the userConfig passed in, so that calling a reset will 
80905        * reset the properties to the passed values.
80906        */
80907        applyConfig: function (userConfig, init) {
80908        
80909            var sKey,
80910                oConfig;
80911
80912            if (init) {
80913                oConfig = {};
80914                for (sKey in userConfig) {
80915                    if (Lang.hasOwnProperty(userConfig, sKey)) {
80916                        oConfig[sKey.toLowerCase()] = userConfig[sKey];
80917                    }
80918                }
80919                this.initialConfig = oConfig;
80920            }
80921
80922            for (sKey in userConfig) {
80923                if (Lang.hasOwnProperty(userConfig, sKey)) {
80924                    this.queueProperty(sKey, userConfig[sKey]);
80925                }
80926            }
80927        },
80928        
80929        /**
80930        * Refires the events for all configuration properties using their 
80931        * current values.
80932        * @method refresh
80933        */
80934        refresh: function () {
80935
80936            var prop;
80937
80938            for (prop in this.config) {
80939                if (Lang.hasOwnProperty(this.config, prop)) {
80940                    this.refireEvent(prop);
80941                }
80942            }
80943        },
80944        
80945        /**
80946        * Fires the normalized list of queued property change events
80947        * @method fireQueue
80948        */
80949        fireQueue: function () {
80950        
80951            var i, 
80952                queueItem,
80953                key,
80954                value,
80955                property;
80956        
80957            this.queueInProgress = true;
80958            for (i = 0;i < this.eventQueue.length; i++) {
80959                queueItem = this.eventQueue[i];
80960                if (queueItem) {
80961        
80962                    key = queueItem[0];
80963                    value = queueItem[1];
80964                    property = this.config[key];
80965
80966                    property.value = value;
80967
80968                    // Clear out queue entry, to avoid it being 
80969                    // re-added to the queue by any queueProperty/supercedes
80970                    // calls which are invoked during fireEvent
80971                    this.eventQueue[i] = null;
80972
80973                    this.fireEvent(key,value);
80974                }
80975            }
80976            
80977            this.queueInProgress = false;
80978            this.eventQueue = [];
80979        },
80980        
80981        /**
80982        * Subscribes an external handler to the change event for any 
80983        * given property. 
80984        * @method subscribeToConfigEvent
80985        * @param {String} key The property name
80986        * @param {Function} handler The handler function to use subscribe to 
80987        * the property's event
80988        * @param {Object} obj The Object to use for scoping the event handler 
80989        * (see CustomEvent documentation)
80990        * @param {Boolean} overrideContext Optional. If true, will override
80991        * "this" within the handler to map to the scope Object passed into the
80992        * method.
80993        * @return {Boolean} True, if the subscription was successful, 
80994        * otherwise false.
80995        */ 
80996        subscribeToConfigEvent: function (key, handler, obj, overrideContext) {
80997    
80998            var property = this.config[key.toLowerCase()];
80999    
81000            if (property && property.event) {
81001                if (!Config.alreadySubscribed(property.event, handler, obj)) {
81002                    property.event.subscribe(handler, obj, overrideContext);
81003                }
81004                return true;
81005            } else {
81006                return false;
81007            }
81008    
81009        },
81010        
81011        /**
81012        * Unsubscribes an external handler from the change event for any 
81013        * given property. 
81014        * @method unsubscribeFromConfigEvent
81015        * @param {String} key The property name
81016        * @param {Function} handler The handler function to use subscribe to 
81017        * the property's event
81018        * @param {Object} obj The Object to use for scoping the event 
81019        * handler (see CustomEvent documentation)
81020        * @return {Boolean} True, if the unsubscription was successful, 
81021        * otherwise false.
81022        */
81023        unsubscribeFromConfigEvent: function (key, handler, obj) {
81024            var property = this.config[key.toLowerCase()];
81025            if (property && property.event) {
81026                return property.event.unsubscribe(handler, obj);
81027            } else {
81028                return false;
81029            }
81030        },
81031        
81032        /**
81033        * Returns a string representation of the Config object
81034        * @method toString
81035        * @return {String} The Config object in string format.
81036        */
81037        toString: function () {
81038            var output = "Config";
81039            if (this.owner) {
81040                output += " [" + this.owner.toString() + "]";
81041            }
81042            return output;
81043        },
81044        
81045        /**
81046        * Returns a string representation of the Config object's current 
81047        * CustomEvent queue
81048        * @method outputEventQueue
81049        * @return {String} The string list of CustomEvents currently queued 
81050        * for execution
81051        */
81052        outputEventQueue: function () {
81053
81054            var output = "",
81055                queueItem,
81056                q,
81057                nQueue = this.eventQueue.length;
81058              
81059            for (q = 0; q < nQueue; q++) {
81060                queueItem = this.eventQueue[q];
81061                if (queueItem) {
81062                    output += queueItem[0] + "=" + queueItem[1] + ", ";
81063                }
81064            }
81065            return output;
81066        },
81067
81068        /**
81069        * Sets all properties to null, unsubscribes all listeners from each 
81070        * property's change event and all listeners from the configChangedEvent.
81071        * @method destroy
81072        */
81073        destroy: function () {
81074
81075            var oConfig = this.config,
81076                sProperty,
81077                oProperty;
81078
81079
81080            for (sProperty in oConfig) {
81081            
81082                if (Lang.hasOwnProperty(oConfig, sProperty)) {
81083
81084                    oProperty = oConfig[sProperty];
81085
81086                    oProperty.event.unsubscribeAll();
81087                    oProperty.event = null;
81088
81089                }
81090            
81091            }
81092            
81093            this.configChangedEvent.unsubscribeAll();
81094            
81095            this.configChangedEvent = null;
81096            this.owner = null;
81097            this.config = null;
81098            this.initialConfig = null;
81099            this.eventQueue = null;
81100        
81101        }
81102
81103    };
81104    
81105    
81106    
81107    /**
81108    * Checks to determine if a particular function/Object pair are already 
81109    * subscribed to the specified CustomEvent
81110    * @method YAHOO.util.Config.alreadySubscribed
81111    * @static
81112    * @param {YAHOO.util.CustomEvent} evt The CustomEvent for which to check 
81113    * the subscriptions
81114    * @param {Function} fn The function to look for in the subscribers list
81115    * @param {Object} obj The execution scope Object for the subscription
81116    * @return {Boolean} true, if the function/Object pair is already subscribed 
81117    * to the CustomEvent passed in
81118    */
81119    Config.alreadySubscribed = function (evt, fn, obj) {
81120    
81121        var nSubscribers = evt.subscribers.length,
81122            subsc,
81123            i;
81124
81125        if (nSubscribers > 0) {
81126            i = nSubscribers - 1;
81127            do {
81128                subsc = evt.subscribers[i];
81129                if (subsc && subsc.obj == obj && subsc.fn == fn) {
81130                    return true;
81131                }
81132            }
81133            while (i--);
81134        }
81135
81136        return false;
81137
81138    };
81139
81140    YAHOO.lang.augmentProto(Config, YAHOO.util.EventProvider);
81141
81142}());
81143/**
81144* The datemath module provides utility methods for basic JavaScript Date object manipulation and 
81145* comparison. 
81146* 
81147* @module datemath
81148*/
81149
81150/**
81151* YAHOO.widget.DateMath is used for simple date manipulation. The class is a static utility
81152* used for adding, subtracting, and comparing dates.
81153* @namespace YAHOO.widget
81154* @class DateMath
81155*/
81156YAHOO.widget.DateMath = {
81157    /**
81158    * Constant field representing Day
81159    * @property DAY
81160    * @static
81161    * @final
81162    * @type String
81163    */
81164    DAY : "D",
81165
81166    /**
81167    * Constant field representing Week
81168    * @property WEEK
81169    * @static
81170    * @final
81171    * @type String
81172    */
81173    WEEK : "W",
81174
81175    /**
81176    * Constant field representing Year
81177    * @property YEAR
81178    * @static
81179    * @final
81180    * @type String
81181    */
81182    YEAR : "Y",
81183
81184    /**
81185    * Constant field representing Month
81186    * @property MONTH
81187    * @static
81188    * @final
81189    * @type String
81190    */
81191    MONTH : "M",
81192
81193    /**
81194    * Constant field representing one day, in milliseconds
81195    * @property ONE_DAY_MS
81196    * @static
81197    * @final
81198    * @type Number
81199    */
81200    ONE_DAY_MS : 1000*60*60*24,
81201    
81202    /**
81203     * Constant field representing the date in first week of January
81204     * which identifies the first week of the year.
81205     * <p>
81206     * In the U.S, Jan 1st is normally used based on a Sunday start of week.
81207     * ISO 8601, used widely throughout Europe, uses Jan 4th, based on a Monday start of week.
81208     * </p>
81209     * @property WEEK_ONE_JAN_DATE
81210     * @static
81211     * @type Number
81212     */
81213    WEEK_ONE_JAN_DATE : 1,
81214
81215    /**
81216    * Adds the specified amount of time to the this instance.
81217    * @method add
81218    * @param {Date} date The JavaScript Date object to perform addition on
81219    * @param {String} field The field constant to be used for performing addition.
81220    * @param {Number} amount The number of units (measured in the field constant) to add to the date.
81221    * @return {Date} The resulting Date object
81222    */
81223    add : function(date, field, amount) {
81224        var d = new Date(date.getTime());
81225        switch (field) {
81226            case this.MONTH:
81227                var newMonth = date.getMonth() + amount;
81228                var years = 0;
81229
81230                if (newMonth < 0) {
81231                    while (newMonth < 0) {
81232                        newMonth += 12;
81233                        years -= 1;
81234                    }
81235                } else if (newMonth > 11) {
81236                    while (newMonth > 11) {
81237                        newMonth -= 12;
81238                        years += 1;
81239                    }
81240                }
81241
81242                d.setMonth(newMonth);
81243                d.setFullYear(date.getFullYear() + years);
81244                break;
81245            case this.DAY:
81246                this._addDays(d, amount);
81247                // d.setDate(date.getDate() + amount);
81248                break;
81249            case this.YEAR:
81250                d.setFullYear(date.getFullYear() + amount);
81251                break;
81252            case this.WEEK:
81253                this._addDays(d, (amount * 7));
81254                // d.setDate(date.getDate() + (amount * 7));
81255                break;
81256        }
81257        return d;
81258    },
81259
81260    /**
81261     * Private helper method to account for bug in Safari 2 (webkit < 420)
81262     * when Date.setDate(n) is called with n less than -128 or greater than 127.
81263     * <p>
81264     * Fix approach and original findings are available here:
81265     * http://brianary.blogspot.com/2006/03/safari-date-bug.html
81266     * </p>
81267     * @method _addDays
81268     * @param {Date} d JavaScript date object
81269     * @param {Number} nDays The number of days to add to the date object (can be negative)
81270     * @private
81271     */
81272    _addDays : function(d, nDays) {
81273        if (YAHOO.env.ua.webkit && YAHOO.env.ua.webkit < 420) {
81274            if (nDays < 0) {
81275                // Ensure we don't go below -128 (getDate() is always 1 to 31, so we won't go above 127)
81276                for(var min = -128; nDays < min; nDays -= min) {
81277                    d.setDate(d.getDate() + min);
81278                }
81279            } else {
81280                // Ensure we don't go above 96 + 31 = 127
81281                for(var max = 96;
81281 nDays > max; nDays -= max) {
81282                    d.setDate(d.getDate() + max);
81283                }
81284            }
81285            // nDays should be remainder between -128 and 96
81286        }
81287        d.setDate(d.getDate() + nDays);
81288    },
81289
81290    /**
81291    * Subtracts the specified amount of time from the this instance.
81292    * @method subtract
81293    * @param {Date} date The JavaScript Date object to perform subtraction on
81294    * @param {Number} field The this field constant to be used for performing subtraction.
81295    * @param {Number} amount The number of units (measured in the field constant) to subtract from the date.
81296    * @return {Date} The resulting Date object
81297    */
81298    subtract : function(date, field, amount) {
81299        return this.add(date, field, (amount*-1));
81300    },
81301
81302    /**
81303    * Determines whether a given date is before another date on the calendar.
81304    * @method before
81305    * @param {Date} date  The Date object to compare with the compare argument
81306    * @param {Date} compareTo The Date object to use for the comparison
81307    * @return {Boolean} true if the date occurs before the compared date; false if not.
81308    */
81309    before : function(date, compareTo) {
81310        var ms = compareTo.getTime();
81311        if (date.getTime() < ms) {
81312            return true;
81313        } else {
81314            return false;
81315        }
81316    },
81317
81318    /**
81319    * Determines whether a given date is after another date on the calendar.
81320    * @method after
81321    * @param {Date} date  The Date object to compare with the compare argument
81322    * @param {Date} compareTo The Date object to use for the comparison
81323    * @return {Boolean} true if the date occurs after the compared date; false if not.
81324    */
81325    after : function(date, compareTo) {
81326        var ms = compareTo.getTime();
81327        if (date.getTime() > ms) {
81328            return true;
81329        } else {
81330            return false;
81331        }
81332    },
81333
81334    /**
81335    * Determines whether a given date is between two other dates on the calendar.
81336    * @method between
81337    * @param {Date} date  The date to check for
81338    * @param {Date} dateBegin The start of the range
81339    * @param {Date} dateEnd  The end of the range
81340    * @return {Boolean} true if the date occurs between the compared dates; false if not.
81341    */
81342    between : function(date, dateBegin, dateEnd) {
81343        if (this.after(date, dateBegin) && this.before(date, dateEnd)) {
81344            return true;
81345        } else {
81346            return false;
81347        }
81348    },
81349    
81350    /**
81351    * Retrieves a JavaScript Date object representing January 1 of any given year.
81352    * @method getJan1
81353    * @param {Number} calendarYear  The calendar year for which to retrieve January 1
81354    * @return {Date} January 1 of the calendar year specified.
81355    */
81356    getJan1 : function(calendarYear) {
81357        return this.getDate(calendarYear,0,1);
81358    },
81359
81360    /**
81361    * Calculates the number of days the specified date is from January 1 of the specified calendar year.
81362    * Passing January 1 to this function would return an offset value of zero.
81363    * @method getDayOffset
81364    * @param {Date} date The JavaScript date for which to find the offset
81365    * @param {Number} calendarYear The calendar year to use for determining the offset
81366    * @return {Number} The number of days since January 1 of the given year
81367    */
81368    getDayOffset : function(date, calendarYear) {
81369        var beginYear = this.getJan1(calendarYear); // Find the start of the year. This will be in week 1.
81370        
81371        // Find the number of days the passed in date is away from the calendar year start
81372        var dayOffset = Math.ceil((date.getTime()-beginYear.getTime()) / this.ONE_DAY_MS);
81373        return dayOffset;
81374    },
81375
81376    /**
81377    * Calculates the week number for the given date. Can currently support standard
81378    * U.S. week numbers, based on Jan 1st defining the 1st week of the year, and 
81379    * ISO8601 week numbers, based on Jan 4th defining the 1st week of the year.
81380    * 
81381    * @method getWeekNumber
81382    * @param {Date} date The JavaScript date for which to find the week number
81383    * @param {Number} firstDayOfWeek The index of the first day of the week (0 = Sun, 1 = Mon ... 6 = Sat).
81384    * Defaults to 0
81385    * @param {Number} janDate The date in the first week of January which defines week one for the year
81386    * Defaults to the value of YAHOO.widget.DateMath.WEEK_ONE_JAN_DATE, which is 1 (Jan 1st). 
81387    * For the U.S, this is normally Jan 1st. ISO8601 uses Jan 4th to define the first week of the year.
81388    * 
81389    * @return {Number} The number of the week containing the given date.
81390    */
81391    getWeekNumber : function(date, firstDayOfWeek, janDate) {
81392
81393        // Setup Defaults
81394        firstDayOfWeek = firstDayOfWeek || 0;
81395        janDate = janDate || this.WEEK_ONE_JAN_DATE;
81396
81397        var targetDate = this.clearTime(date),
81398            startOfWeek,
81399            endOfWeek;
81400
81401        if (targetDate.getDay() === firstDayOfWeek) { 
81402            startOfWeek = targetDate;
81403        } else {
81404            startOfWeek = this.getFirstDayOfWeek(targetDate, firstDayOfWeek);
81405        }
81406
81407        var startYear = startOfWeek.getFullYear();
81408
81409        // DST shouldn't be a problem here, math is quicker than setDate();
81410        endOfWeek = new Date(startOfWeek.getTime() + 6*this.ONE_DAY_MS);
81411
81412        var weekNum;
81413        if (startYear !== endOfWeek.getFullYear() && endOfWeek.getDate() >= janDate) {
81414            // If years don't match, endOfWeek is in Jan. and if the 
81415            // week has WEEK_ONE_JAN_DATE in it, it's week one by definition.
81416            weekNum = 1;
81417        } else {
81418            // Get the 1st day of the 1st week, and 
81419            // find how many days away we are from it.
81420            var weekOne = this.clearTime(this.getDate(startYear, 0, janDate)),
81421                weekOneDayOne = this.getFirstDayOfWeek(weekOne, firstDayOfWeek);
81422
81423            // Round days to smoothen out 1 hr DST diff
81424            var daysDiff  = Math.round((targetDate.getTime() - weekOneDayOne.getTime())/this.ONE_DAY_MS);
81425
81426            // Calc. Full Weeks
81427            var rem = daysDiff % 7;
81428            var weeksDiff = (daysDiff - rem)/7;
81429            weekNum = weeksDiff + 1;
81430        }
81431        return weekNum;
81432    },
81433
81434    /**
81435     * Get the first day of the week, for the give date. 
81436     * @param {Date} dt The date in the week for which the first day is required.
81437     * @param {Number} startOfWeek The index for the first day of the week, 0 = Sun, 1 = Mon ... 6 = Sat (defaults to 0)
81438     * @return {Date} The first day of the week
81439     */
81440    getFirstDayOfWeek : function (dt, startOfWeek) {
81441        startOfWeek = startOfWeek || 0;
81442        var dayOfWeekIndex = dt.getDay(),
81443            dayOfWeek = (dayOfWeekIndex - startOfWeek + 7) % 7;
81444
81445        return this.subtract(dt, this.DAY, dayOfWeek);
81446    },
81447
81448    /**
81449    * Determines if a given week overlaps two different years.
81450    * @method isYearOverlapWeek
81451    * @param {Date} weekBeginDate The JavaScript Date representing the first day of the week.
81452    * @return {Boolean} true if the date overlaps two different years.
81453    */
81454    isYearOverlapWeek : function(weekBeginDate) {
81455        var overlaps = false;
81456        var nextWeek = this.add(weekBeginDate, this.DAY, 6);
81457        if (nextWeek.getFullYear() != weekBeginDate.getFullYear()) {
81458            overlaps = true;
81459        }
81460        return overlaps;
81461    },
81462
81463    /**
81464    * Determines if a given week overlaps two different months.
81465    * @method isMonthOverlapWeek
81466    * @param {Date} weekBeginDate The JavaScript Date representing the first day of the week.
81467    * @return {Boolean} true if the date overlaps two different months.
81468    */
81469    isMonthOverlapWeek : function(weekBeginDate) {
81470        var overlaps = false;
81471        var nextWeek = this.add(weekBeginDate, this.DAY, 6);
81472        if (nextWeek.getMonth() != weekBeginDate.getMonth()) {
81473            overlaps = true;
81474        }
81475        return overlaps;
81476    },
81477
81478    /**
81479    * Gets the first day of a month containing a given date.
81480    * @method findMonthStart
81481    * @param {Date} date The JavaScript Date used to calculate the month start
81482    * @return {Date}  The JavaScript Date representing the first day of the month
81483    */
81484    findMonthStart : function(date) {
81485        var start = this.getDate(date.getFullYear(), date.getMonth(), 1);
81486        return start;
81487    },
81488
81489    /**
81490    * Gets the last day of a month containing a given date.
81491    * @method findMonthEnd
81492    * @param {Date} date The JavaScript Date used to calculate the month end
81493    * @return {Date}  The JavaScript Date representing the last day of the month
81494    */
81495    findMonthEnd : function(date) {
81496        var start = this.findMonthStart(date);
81497        var nextMonth = this.add(start, this.MONTH, 1);
81498        var end = this.subtract(nextMonth, this.DAY, 1);
81499        return end;
81500    },
81501
81502    /**
81503    * Clears the time fields from a given date, effectively setting the time to 12 noon.
81504    * @method clearTime
81505    * @param {Date} date The JavaScript Date for which the time fields will be cleared
81506    * @return {Date}  The JavaScript Date cleared of all time fields
81507    */
81508    clearTime : function(date) {
81509        date.setHours(12,0,0,0);
81510        return date;
81511    },
81512
81513    /**
81514     * Returns a new JavaScript Date object, representing the given year, month and date. Time fields (hr, min, sec, ms) on the new Date object
81515     * are set to 0. The method allows Date instances to be created with the a year less than 100. "new Date(year, month, date)" implementations 
81516     * set the year to 19xx if a year (xx) which is less than 100 is provided.
81517     * <p>
81518     * <em>NOTE:</em>Validation on argument values is not performed. It is the caller's responsibility to ensure
81519     * arguments are valid as per the ECMAScript-262 Date object specification for the new Date(year, month[, date]) constructor.
81520     * </p>
81521     * @method getDate
81522     * @param {Number} y Year.
81523     * @param {Number} m Month index from 0 (Jan) to 11 (Dec).
81524     * @param {Number} d (optional) Date from 1 to 31. If not provided, defaults to 1.
81525     * @return {Date} The JavaScript date object with year, month, date set as provided.
81526     */
81527    getDate : function(y, m, d) {
81528        var dt = null;
81529        if (YAHOO.lang.isUndefined(d)) {
81530            d = 1;
81531        }
81532        if (y >= 100) {
81533            dt = new Date(y, m, d);
81534        } else {
81535            dt = new Date();
81536            dt.setFullYear(y);
81537            dt.setMonth(m);
81538            dt.setDate(d);
81539            dt.setHours(0,0,0,0);
81540        }
81541        return dt;
81542    }
81543};
81544/**
81545* The Calendar component is a UI control that enables users to choose one or more dates from a graphical calendar presented in a one-month or
81546* multi-month interface. Calendars are generated entirely via script and can be navigated without any page refreshes.
81547* @module    calendar
81548* @title    Calendar
81549* @namespace  YAHOO.widget
81550* @requires  yahoo,dom,event
81551*/
81552(function(){
81553
81554    var Dom = YAHOO.util.Dom,
81555        Event = YAHOO.util.Event,
81556        Lang = YAHOO.lang,
81557        DateMath = YAHOO.widget.DateMath;
81558
81559/**
81560* Calendar is the base class for the Calendar widget. In its most basic
81561* implementation, it has the ability to render a calendar widget on the page
81562* that can be manipulated to select a single date, move back and forth between
81563* months and years.
81564* <p>To construct the placeholder for the calendar widget, the code is as
81565* follows:
81566*   <xmp>
81567*       <div id="calContainer"></div>
81568*   </xmp>
81569* </p>
81570* <p>
81571* <strong>NOTE: As of 2.4.0, the constructor's ID argument is optional.</strong>
81572* The Calendar can be constructed by simply providing a container ID string, 
81573* or a reference to a container DIV HTMLElement (the element needs to exist 
81574* in the document).
81575* 
81576* E.g.:
81577*   <xmp>
81578*       var c = new YAHOO.widget.Calendar("calContainer", configOptions);
81579*   </xmp>
81580* or:
81581*   <xmp>
81582*       var containerDiv = YAHOO.util.Dom.get("calContainer");
81583*       var c = new YAHOO.widget.Calendar(containerDiv, configOptions);
81584*   </xmp>
81585* </p>
81586* <p>
81587* If not provided, the ID will be generated from the container DIV ID by adding an "_t" suffix.
81588* For example if an ID is not provided, and the container's ID is "calContainer", the Calendar's ID will be set to "calContainer_t".
81589* </p>
81590* 
81591* @namespace YAHOO.widget
81592* @class Calendar
81593* @constructor
81594* @param {String} id optional The id of the table element that will represent the Calendar widget. As of 2.4.0, this argument is optional.
81595* @param {String | HTMLElement} container The id of the container div element that will wrap the Calendar table, or a reference to a DIV element which exists in the document.
81596* @param {Object}
81596 config optional The configuration object containing the initial configuration values for the Calendar.
81597*/
81598function Calendar(id, containerId, config) {
81599    this.init.apply(this, arguments);
81600}
81601
81602/**
81603* The path to be used for images loaded for the Calendar
81604* @property YAHOO.widget.Calendar.IMG_ROOT
81605* @static
81606* @deprecated   You can now customize images by overriding the calclose, calnavleft and calnavright default CSS classes for the close icon, left arrow and right arrow respectively
81607* @type String
81608*/
81609Calendar.IMG_ROOT = null;
81610
81611/**
81612* Type constant used for renderers to represent an individual date (M/D/Y)
81613* @property YAHOO.widget.Calendar.DATE
81614* @static
81615* @final
81616* @type String
81617*/
81618Calendar.DATE = "D";
81619
81620/**
81621* Type constant used for renderers to represent an individual date across any year (M/D)
81622* @property YAHOO.widget.Calendar.MONTH_DAY
81623* @static
81624* @final
81625* @type String
81626*/
81627Calendar.MONTH_DAY = "MD";
81628
81629/**
81630* Type constant used for renderers to represent a weekday
81631* @property YAHOO.widget.Calendar.WEEKDAY
81632* @static
81633* @final
81634* @type String
81635*/
81636Calendar.WEEKDAY = "WD";
81637
81638/**
81639* Type constant used for renderers to represent a range of individual dates (M/D/Y-M/D/Y)
81640* @property YAHOO.widget.Calendar.RANGE
81641* @static
81642* @final
81643* @type String
81644*/
81645Calendar.RANGE = "R";
81646
81647/**
81648* Type constant used for renderers to represent a month across any year
81649* @property YAHOO.widget.Calendar.MONTH
81650* @static
81651* @final
81652* @type String
81653*/
81654Calendar.MONTH = "M";
81655
81656/**
81657* Constant that represents the total number of date cells that are displayed in a given month
81658* @property YAHOO.widget.Calendar.DISPLAY_DAYS
81659* @static
81660* @final
81661* @type Number
81662*/
81663Calendar.DISPLAY_DAYS = 42;
81664
81665/**
81666* Constant used for halting the execution of the remainder of the render stack
81667* @property YAHOO.widget.Calendar.STOP_RENDER
81668* @static
81669* @final
81670* @type String
81671*/
81672Calendar.STOP_RENDER = "S";
81673
81674/**
81675* Constant used to represent short date field string formats (e.g. Tu or Feb)
81676* @property YAHOO.widget.Calendar.SHORT
81677* @static
81678* @final
81679* @type String
81680*/
81681Calendar.SHORT = "short";
81682
81683/**
81684* Constant used to represent long date field string formats (e.g. Monday or February)
81685* @property YAHOO.widget.Calendar.LONG
81686* @static
81687* @final
81688* @type String
81689*/
81690Calendar.LONG = "long";
81691
81692/**
81693* Constant used to represent medium date field string formats (e.g. Mon)
81694* @property YAHOO.widget.Calendar.MEDIUM
81695* @static
81696* @final
81697* @type String
81698*/
81699Calendar.MEDIUM = "medium";
81700
81701/**
81702* Constant used to represent single character date field string formats (e.g. M, T, W)
81703* @property YAHOO.widget.Calendar.ONE_CHAR
81704* @static
81705* @final
81706* @type String
81707*/
81708Calendar.ONE_CHAR = "1char";
81709
81710/**
81711* The set of default Config property keys and values for the Calendar.
81712*
81713* <p>
81714* NOTE: This property is made public in order to allow users to change 
81715* the default values of configuration properties. Users should not 
81716* modify the key string, unless they are overriding the Calendar implementation
81717* </p>
81718*
81719* <p>
81720* The property is an object with key/value pairs, the key being the 
81721* uppercase configuration property name and the value being an object 
81722* literal with a key string property, and a value property, specifying the 
81723* default value of the property. To override a default value, you can set
81724* the value property, for example, <code>YAHOO.widget.Calendar.DEFAULT_CONFIG.MULTI_SELECT.value = true;</code>
81725* 
81726* @property YAHOO.widget.Calendar.DEFAULT_CONFIG
81727* @static
81728* @type Object
81729*/
81730
81731Calendar.DEFAULT_CONFIG = {
81732    YEAR_OFFSET : {key:"year_offset", value:0, supercedes:["pagedate", "selected", "mindate","maxdate"]},
81733    TODAY : {key:"today", value:new Date(), supercedes:["pagedate"]}, 
81734    PAGEDATE : {key:"pagedate", value:null},
81735    SELECTED : {key:"selected", value:[]},
81736    TITLE : {key:"title", value:""},
81737    CLOSE : {key:"close", value:false},
81738    IFRAME : {key:"iframe", value:(YAHOO.env.ua.ie && YAHOO.env.ua.ie <= 6) ? true : false},
81739    MINDATE : {key:"mindate", value:null},
81740    MAXDATE : {key:"maxdate", value:null},
81741    MULTI_SELECT : {key:"multi_select", value:false},
81742    OOM_SELECT : {key:"oom_select", value:false},
81743    START_WEEKDAY : {key:"start_weekday", value:0},
81744    SHOW_WEEKDAYS : {key:"show_weekdays", value:true},
81745    SHOW_WEEK_HEADER : {key:"show_week_header", value:false},
81746    SHOW_WEEK_FOOTER : {key:"show_week_footer", value:false},
81747    HIDE_BLANK_WEEKS : {key:"hide_blank_weeks", value:false},
81748    NAV_ARROW_LEFT: {key:"nav_arrow_left", value:null} ,
81749    NAV_ARROW_RIGHT : {key:"nav_arrow_right", value:null} ,
81750    MONTHS_SHORT : {key:"months_short", value:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]},
81751    MONTHS_LONG: {key:"months_long", value:["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "
81751November", "December"]},
81752    WEEKDAYS_1CHAR: {key:"weekdays_1char", value:["S", "M", "T", "W", "T", "F", "S"]},
81753    WEEKDAYS_SHORT: {key:"weekdays_short", value:["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]},
81754    WEEKDAYS_MEDIUM: {key:"weekdays_medium", value:["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]},
81755    WEEKDAYS_LONG: {key:"weekdays_long", value:["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]},
81756    LOCALE_MONTHS:{key:"locale_months", value:"long"},
81757    LOCALE_WEEKDAYS:{key:"locale_weekdays", value:"short"},
81758    DATE_DELIMITER:{key:"date_delimiter", value:","},
81759    DATE_FIELD_DELIMITER:{key:"date_field_delimiter", value:"/"},
81760    DATE_RANGE_DELIMITER:{key:"date_range_delimiter", value:"-"},
81761    MY_MONTH_POSITION:{key:"my_month_position", value:1},
81762    MY_YEAR_POSITION:{key:"my_year_position", value:2},
81763    MD_MONTH_POSITION:{key:"md_month_position", value:1},
81764    MD_DAY_POSITION:{key:"md_day_position", value:2},
81765    MDY_MONTH_POSITION:{key:"mdy_month_position", value:1},
81766    MDY_DAY_POSITION:{key:"mdy_day_position", value:2},
81767    MDY_YEAR_POSITION:{key:"mdy_year_position", value:3},
81768    MY_LABEL_MONTH_POSITION:{key:"my_label_month_position", value:1},
81769    MY_LABEL_YEAR_POSITION:{key:"my_label_year_position", value:2},
81770    MY_LABEL_MONTH_SUFFIX:{key:"my_label_month_suffix", value:" "},
81771    MY_LABEL_YEAR_SUFFIX:{key:"my_label_year_suffix", value:""},
81772    NAV: {key:"navigator", value: null},
81773    STRINGS : { 
81774        key:"strings",
81775        value: {
81776            previousMonth : "Previous Month",
81777            nextMonth : "Next Month",
81778            close: "Close"
81779        },
81780        supercedes : ["close", "title"]
81781    }
81782};
81783
81784/**
81785* The set of default Config property keys and values for the Calendar
81786* @property YAHOO.widget.Calendar._DEFAULT_CONFIG
81787* @deprecated Made public. See the public DEFAULT_CONFIG property for details
81788* @final
81789* @static
81790* @private
81791* @type Object
81792*/
81793Calendar._DEFAULT_CONFIG = Calendar.DEFAULT_CONFIG;
81794
81795var DEF_CFG = Calendar.DEFAULT_CONFIG;
81796
81797/**
81798* The set of Custom Event types supported by the Calendar
81799* @property YAHOO.widget.Calendar._EVENT_TYPES
81800* @final
81801* @static
81802* @private
81803* @type Object
81804*/
81805Calendar._EVENT_TYPES = {
81806    BEFORE_SELECT : "beforeSelect", 
81807    SELECT : "select",
81808    BEFORE_DESELECT : "beforeDeselect",
81809    DESELECT : "deselect",
81810    CHANGE_PAGE : "changePage",
81811    BEFORE_RENDER : "beforeRender",
81812    RENDER : "render",
81813    BEFORE_DESTROY : "beforeDestroy",
81814    DESTROY : "destroy",
81815    RESET : "reset",
81816    CLEAR : "clear",
81817    BEFORE_HIDE : "beforeHide",
81818    HIDE : "hide",
81819    BEFORE_SHOW : "beforeShow",
81820    SHOW : "show",
81821    BEFORE_HIDE_NAV : "beforeHideNav",
81822    HIDE_NAV : "hideNav",
81823    BEFORE_SHOW_NAV : "beforeShowNav",
81824    SHOW_NAV : "showNav",
81825    BEFORE_RENDER_NAV : "beforeRenderNav",
81826    RENDER_NAV : "renderNav"
81827};
81828
81829/**
81830* The set of default style constants for the Calendar
81831* @property YAHOO.widget.Calendar.STYLES
81832* @static
81833* @type Object An object with name/value pairs for the class name identifier/value.
81834*/
81835Calendar.STYLES = {
81836    CSS_ROW_HEADER: "calrowhead",
81837    CSS_ROW_FOOTER: "calrowfoot",
81838    CSS_CELL : "calcell",
81839    CSS_CELL_SELECTOR : "selector",
81840    CSS_CELL_SELECTED : "selected",
81841    CSS_CELL_SELECTABLE : "selectable",
81842    CSS_CELL_RESTRICTED : "restricted",
81843    CSS_CELL_TODAY : "today",
81844    CSS_CELL_OOM : "oom",
81845    CSS_CELL_OOB : "previous",
81846    CSS_HEADER : "calheader",
81847    CSS_HEADER_TEXT : "calhead",
81848    CSS_BODY : "calbody",
81849    CSS_WEEKDAY_CELL : "calweekdaycell",
81850    CSS_WEEKDAY_ROW : "calweekdayrow",
81851    CSS_FOOTER : "calfoot",
81852    CSS_CALENDAR : "yui-calendar",
81853    CSS_SINGLE : "single",
81854    CSS_CONTAINER : "yui-calcontainer",
81855    CSS_NAV_LEFT : "calnavleft",
81856    CSS_NAV_RIGHT : "calnavright",
81857    CSS_NAV : "calnav",
81858    CSS_CLOSE : "calclose",
81859    CSS_CELL_TOP : "calcelltop",
81860    CSS_CELL_LEFT : "calcellleft",
81861    CSS_CELL_RIGHT : "calcellright",
81862    CSS_CELL_BOTTOM : "calcellbottom",
81863    CSS_CELL_HOVER : "calcellhover",
81864    CSS_CELL_HIGHLIGHT1 : "highlight1",
81865    CSS_CELL_HIGHLIGHT2 : "highlight2",
81866    CSS_CELL_HIGHLIGHT3 : "highlight3",
81867    CSS_CELL_HIGHLIGHT4 : "highlight4",
81868    CSS_WITH_TITLE: "withtitle",
81869    CSS_FIXED_SIZE: "fixedsize",
81870    CSS_LINK_CLOSE: "link-close"
81871};
81872
81873/**
81874* The set of default style constants for the Calendar
81875* @property YAHOO.widget.Calendar._STYLES
81876* @deprecated Made public. See the public STYLES property for details
81877* @final
81878* @static
81879* @private
81880* @type Object
81881*/
81882Calendar._STYLES = Calendar.STYLES;
81883
81884Calendar.prototype = {
81885
81886    /**
81887    * The configuration object used to set up the calendars various locale and style options.
81888    * @property Config
81889    * @private
81890    * @deprecated Configuration properties should be set by calling Calendar.cfg.setProperty.
81891    * @type Object
81892    */
81893    Config : null,
81894
81895    /**
81896    * The parent CalendarGroup, only to be set explicitly by the parent group
81897    * @property parent
81898    * @type CalendarGroup
81899    */ 
81900    parent : null,
81901
81902    /**
81903    * The index of this item in the parent group
81904    * @property index
81905    * @type Number
81906    */
81907    index : -1,
81908
81909    /**
81910    * The collection of calendar table cells
81911    * @property cells
81912    * @type HTMLTableCellElement[]
81913    */
81914    cells : null,
81915
81916    /**
81917    * The collection of calendar cell dates that is parallel to the cells collection. The array contains dates field arrays in the format of [YYYY, M, D].
81918    * @property cellDates
81919    * @type Array[](Number[])
81920    */
81921    cellDates : null,
81922
81923    /**
81924    * The id that uniquely identifies this Calendar.
81925    * @property id
81926    * @type String
81927    */
81928    id : null,
81929
81930    /**
81931    * The unique id associated with the Calendar's container
81932    * @property containerId
81933    * @type String
81934    */
81935    containerId: null,
81936
81937    /**
81938    * The DOM element reference that points to this calendar's container element. The calendar will be inserted into this element when the shell is rendered.
81939    * @property oDomContainer
81940    * @type HTMLElement
81941    */
81942    oDomContainer : null,
81943
81944    /**
81945    * A Date object representing today's date.
81946    * @deprecated Use the "today" configuration property
81947    * @property today
81948    * @type Date
81949    */
81950    today : null,
81951
81952    /**
81953    * The list of render functions, along with required parameters, used to render cells. 
81954    * @property renderStack
81955    * @type Array[]
81956    */
81957    renderStack : null,
81958
81959    /**
81960    * A copy of the initial render functions created before rendering.
81961    * @property _renderStack
81962    * @private
81963    * @type Array
81964    */
81965    _renderStack : null,
81966
81967    /**
81968    * A reference to the CalendarNavigator instance created for this Calendar.
81969    * Will be null if the "navigator" configuration property has not been set
81970    * @property oNavigator
81971    * @type CalendarNavigator
81972    */
81973    oNavigator : null,
81974
81975    /**
81976    * The private list of initially selected dates.
81977    * @property _selectedDates
81978    * @private
81979    * @type Array
81980    */
81981    _selectedDates : null,
81982
81983    /**
81984    * A map of DOM event handlers to attach to cells associated with specific CSS class names
81985    * @property domEventMap
81986    * @type Object
81987    */
81988    domEventMap : null,
81989
81990    /**
81991     * Protected helper used to parse Calendar constructor/init arguments.
81992     *
81993     * As of 2.4.0, Calendar supports a simpler constructor 
81994     * signature. This method reconciles arguments
81995     * received in the pre 2.4.0 and 2.4.0 formats.
81996     * 
81997     * @protected
81998     * @method _parseArgs
81999     * @param {Array} Function "arguments" array
82000     * @return {Object} Object with id, container, config properties containing
82001     * the reconciled argument values.
82002     **/
82003    _parseArgs : function(args) {
82004        /*
82005           2.4.0 Constructors signatures
82006
82007           new Calendar(String)
82008           new Calendar(HTMLElement)
82009           new Calendar(String, ConfigObject)
82010           new Calendar(HTMLElement, ConfigObject)
82011
82012           Pre 2.4.0 Constructor signatures
82013
82014           new Calendar(String, String)
82015           new Calendar(String, HTMLElement)
82016           new Calendar(String, String, ConfigObject)
82017           new Calendar(String, HTMLElement, ConfigObject)
82018         */
82019        var nArgs = {id:null, container:null, config:null};
82020
82021        if (args && args.length && args.length > 0) {
82022            switch (args.length) {
82023                case 1:
82024                    nArgs.id = null;
82025                    nArgs.container = args[0];
82026                    nArgs.config = null;
82027                    break;
82028                case 2:
82029                    if (Lang.isObject(args[1]) && !args[1].tagName && !(args[1] instanceof String)) {
82030                        nArgs.id = null;
82031                        nArgs.container = args[0];
82032                        nArgs.config = args[1];
82033                    } else {
82034                        nArgs.id = args[0];
82035                        nArgs.container = args[1];
82036                        nArgs.config = null;
82037                    }
82038                    break;
82039                default: // 3+
82040                    nArgs.id = args[0];
82041                    nArgs.container = args[1];
82042                    nArgs.config = args[2];
82043                    break;
82044            }
82045        } else {
82046            this.logger.log("Invalid constructor/init arguments", "error");
82047        }
82048        return nArgs;
82049    },
82050
82051    /**
82052    * Initializes the Calendar widget.
82053    * @method init
82054    *
82055    * @param {String} id optional The id of the table element that will represent the Calendar widget. As of 2.4.0, this argument is optional.
82056    * @param {String | HTMLElement} container The id of the container div element that will wrap the Calendar table, or a reference to a DIV element which exists in the document.
82057    * @param {Object}
82057 config optional The configuration object containing the initial configuration values for the Calendar.
82058    */
82059    init : function(id, container, config) {
82060        // Normalize 2.4.0, pre 2.4.0 args
82061        var nArgs = this._parseArgs(arguments);
82062
82063        id = nArgs.id;
82064        container = nArgs.container;
82065        config = nArgs.config;
82066
82067        this.oDomContainer = Dom.get(container);
82068        // Removing due to order of operations issue [ logger/id ]. 
82069        // The log is kind of pointless because it'll barf on the next statement anyway.
82070        // Any code related changes are beyond the scope of 2.9.0 at this point 
82071        // if (!this.oDomContainer) { this.logger.log("Container not found in document.", "error"); }
82072
82073        this._oDoc = this.oDomContainer.ownerDocument;
82074
82075        if (!this.oDomContainer.id) {
82076            this.oDomContainer.id = Dom.generateId();
82077        }
82078
82079        if (!id) {
82080            id = this.oDomContainer.id + "_t";
82081        }
82082
82083        this.id = id;
82084        this.containerId = this.oDomContainer.id;
82085
82086        this.logger = new YAHOO.widget.LogWriter("Calendar " + this.id);
82087        this.initEvents();
82088
82089        /**
82090        * The Config object used to hold the configuration variables for the Calendar
82091        * @property cfg
82092        * @type YAHOO.util.Config
82093        */
82094        this.cfg = new YAHOO.util.Config(this);
82095
82096        /**
82097        * The local object which contains the Calendar's options
82098        * @property Options
82099        * @type Object
82100        */
82101        this.Options = {};
82102
82103        /**
82104        * The local object which contains the Calendar's locale settings
82105        * @property Locale
82106        * @type Object
82107        */
82108        this.Locale = {};
82109
82110        this.initStyles();
82111
82112        Dom.addClass(this.oDomContainer, this.Style.CSS_CONTAINER);
82113        Dom.addClass(this.oDomContainer, this.Style.CSS_SINGLE);
82114
82115        this.cellDates = [];
82116        this.cells = [];
82117        this.renderStack = [];
82118        this._renderStack = [];
82119
82120        this.setupConfig();
82121
82122        if (config) {
82123            this.cfg.applyConfig(config, true);
82124        }
82125
82126        this.cfg.fireQueue();
82127
82128        this.today = this.cfg.getProperty("today");
82129    },
82130
82131    /**
82132    * Default Config listener for the iframe property. If the iframe config property is set to true, 
82133    * renders the built-in IFRAME shim if the container is relatively or absolutely positioned.
82134    * 
82135    * @method configIframe
82136    */
82137    configIframe : function(type, args, obj) {
82138        var useIframe = args[0];
82139    
82140        if (!this.parent) {
82141            if (Dom.inDocument(this.oDomContainer)) {
82142                if (useIframe) {
82143                    var pos = Dom.getStyle(this.oDomContainer, "position");
82144                    
82145                    if (pos == "absolute" || pos == "relative") {
82146                        
82147                        if (!Dom.inDocument(this.iframe)) {
82148                            this.iframe = document.createElement("iframe");
82149                            this.iframe.src = "javascript:false;";
82150    
82151                            Dom.setStyle(this.iframe, "opacity", "0");
82152    
82153                            if (YAHOO.env.ua.ie && YAHOO.env.ua.ie <= 6) {
82154                                Dom.addClass(this.iframe, this.Style.CSS_FIXED_SIZE);
82155                            }
82156    
82157                            this.oDomContainer.insertBefore(this.iframe, this.oDomContainer.firstChild);
82158                        }
82159                    }
82160                } else {
82161                    if (this.iframe) {
82162                        if (this.iframe.parentNode) {
82163                            this.iframe.parentNode.removeChild(this.iframe);
82164                        }
82165                        this.iframe = null;
82166                    }
82167                }
82168            }
82169        }
82170    },
82171
82172    /**
82173    * Default handler for the "title" property
82174    * @method configTitle
82175    */
82176    configTitle : function(type, args, obj) {
82177        var title = args[0];
82178
82179        // "" disables title bar
82180        if (title) {
82181            this.createTitleBar(title);
82182        } else {
82183            var close = this.cfg.getProperty(DEF_CFG.CLOSE.key);
82184            if (!close) {
82185                this.removeTitleBar();
82186            } else {
82187                this.createTitleBar("&#160;");
82188            }
82189        }
82190    },
82191    
82192    /**
82193    * Default handler for the "close" property
82194    * @method configClose
82195    */
82196    configClose : function(type, args, obj) {
82197        var close = args[0],
82198            title = this.cfg.getProperty(DEF_CFG.TITLE.key);
82199    
82200        if (close) {
82201            if (!title) {
82202                this.createTitleBar("&#160;");
82203            }
82204            this.createCloseButton();
82205        } else {
82206            this.removeCloseButton();
82207            if (!title) {
82208                this.removeTitleBar();
82209            }
82210        }
82211    },
82212
82213    /**
82214    * Initializes Calendar's built-in CustomEvents
82215    * @method initEvents
82216    */
82217    initEvents : function() {
82218
82219        var defEvents = Calendar._EVENT_TYPES,
82220            CE = YAHOO.util.CustomEvent,
82221            cal = this; // To help with minification
82222
82223        /**
82224        * Fired before a date selection is made
82225        * @event beforeSelectEvent
82226        */
82227        cal.beforeSelectEvent = new CE(defEvents.BEFORE_SELECT); 
82228
82229        /**
82230        * Fired when a date selection is made
82231        * @event selectEvent
82232        * @param {Array} Array of Date field arrays in the format [YYYY, MM, DD].
82233        */
82234        cal.selectEvent = new CE(defEvents.SELECT);
82235
82236        /**
82237        * Fired before a date or set of dates is deselected
82238        * @event beforeDeselectEvent
82239        */
82240        cal.beforeDeselectEvent = new CE(defEvents.BEFORE_DESELECT);
82241
82242        /**
82243        * Fired when a date or set of dates is deselected
82244        * @event deselectEvent
82245        * @param {Array} Array of Date field arrays in the format [YYYY, MM, DD].
82246        */
82247        cal.deselectEvent = new CE(defEvents.DESELECT);
82248    
82249        /**
82250        * Fired when the Calendar page is changed
82251        * @event changePageEvent
82252        * @param {Date} prevDate The date before the page was changed
82253        * @param {Date} newDate The date after the page was changed
82254        */
82255        cal.changePageEvent = new CE(defEvents.CHANGE_PAGE);
82256    
82257        /**
82258        * Fired before the Calendar is rendered
82259        * @event beforeRenderEvent
82260        */
82261        cal.beforeRenderEvent = new CE(defEvents.BEFORE_RENDER);
82262    
82263        /**
82264        * Fired when the Calendar is rendered
82265        * @event renderEvent
82266        */
82267        cal.renderEvent = new CE(defEvents.RENDER);
82268
82269        /**
82270        * Fired just before the Calendar is to be destroyed
82271        * @event beforeDestroyEvent
82272        */
82273        cal.beforeDestroyEvent = new CE(defEvents.BEFORE_DESTROY);
82274
82275        /**
82276        * Fired after the Calendar is destroyed. This event should be used
82277        * for notification only. When this event is fired, important Calendar instance
82278        * properties, dom references and event listeners have already been 
82279        * removed/dereferenced, and hence the Calendar instance is not in a usable 
82280        * state.
82281        *
82282        * @event destroyEvent
82283        */
82284        cal.destroyEvent = new CE(defEvents.DESTROY);
82285
82286        /**
82287        * Fired when the Calendar is reset
82288        * @event resetEvent
82289        */
82290        cal.resetEvent = new CE(defEvents.RESET);
82291
82292        /**
82293        * Fired when the Calendar is cleared
82294        * @event clearEvent
82295        */
82296        cal.clearEvent = new CE(defEvents.CLEAR);
82297
82298        /**
82299        * Fired just before the Calendar is to be shown
82300        * @event beforeShowEvent
82301        */
82302        cal.beforeShowEvent = new CE(defEvents.BEFORE_SHOW);
82303
82304        /**
82305        * Fired after the Calendar is shown
82306        * @event showEvent
82307        */
82308        cal.showEvent = new CE(defEvents.SHOW);
82309
82310        /**
82311        * Fired just before the Calendar is to be hidden
82312        * @event beforeHideEvent
82313        */
82314        cal.beforeHideEvent = new CE(defEvents.BEFORE_HIDE);
82315
82316        /**
82317        * Fired after the Calendar is hidden
82318        * @event hideEvent
82319        */
82320        cal.hideEvent = new CE(defEvents.HIDE);
82321
82322        /**
82323        * Fired just before the CalendarNavigator is to be shown
82324        * @event beforeShowNavEvent
82325        */
82326        cal.beforeShowNavEvent = new CE(defEvents.BEFORE_SHOW_NAV);
82327    
82328        /**
82329        * Fired after the CalendarNavigator is shown
82330        * @event showNavEvent
82331        */
82332        cal.showNavEvent = new CE(defEvents.SHOW_NAV);
82333    
82334        /**
82335        * Fired just before the CalendarNavigator is to be hidden
82336        * @event beforeHideNavEvent
82337        */
82338        cal.beforeHideNavEvent = new CE(defEvents.BEFORE_HIDE_NAV);
82339    
82340        /**
82341        * Fired after the CalendarNavigator is hidden
82342        * @event hideNavEvent
82343        */
82344        cal.hideNavEvent = new CE(defEvents.HIDE_NAV);
82345
82346        /**
82347        * Fired just before the CalendarNavigator is to be rendered
82348        * @event beforeRenderNavEvent
82349        */
82350        cal.beforeRenderNavEvent = new CE(defEvents.BEFORE_RENDER_NAV);
82351
82352        /**
82353        * Fired after the CalendarNavigator is rendered
82354        * @event renderNavEvent
82355        */
82356        cal.renderNavEvent = new CE(defEvents.RENDER_NAV);
82357
82358        cal.beforeSelectEvent.subscribe(cal.onBeforeSelect, this, true);
82359        cal.selectEvent.subscribe(cal.onSelect, this, true);
82360        cal.beforeDeselectEvent.subscribe(cal.onBeforeDeselect, this, true);
82361        cal.deselectEvent.subscribe(cal.onDeselect, this, true);
82362        cal.changePageEvent.subscribe(cal.onChangePage, this, true);
82363        cal.renderEvent.subscribe(cal.onRender, this, true);
82364        cal.resetEvent.subscribe(cal.onReset, this, true);
82365        cal.clearEvent.subscribe(cal.onClear, this, true);
82366    },
82367
82368    /**
82369    * The default event handler for clicks on the "Previous Month" navigation UI
82370    *
82371    * @method doPreviousMonthNav
82372    * @param {DOMEvent} e The DOM event
82373    * @param {Calendar} cal A reference to the calendar
82374    */
82375    doPreviousMonthNav : function(e, cal) {
82376        Event.preventDefault(e);
82377        // previousMonth invoked in a timeout, to allow
82378        // event to bubble up, with correct target. Calling
82379        // previousMonth, will call render which will remove 
82380        // HTML which generated the event, resulting in an 
82381        // invalid event target in certain browsers.
82382        setTimeout(function() {
82383            cal.previousMonth();
82384            var navs = Dom.getElementsByClassName(cal.Style.CSS_NAV_LEFT, "a", cal.oDomContainer);
82385            if (navs && navs[0]) {
82386                try {
82387                    navs[0].focus();
82388                } catch (ex) {
82389                    // ignore
82390                }
82391            }
82392        }, 0);
82393    },
82394
82395    /**
82396     * The default event handler for clicks on the "Next Month" navigation UI
82397     *
82398     * @method doNextMonthNav
82399     * @param {DOMEvent} e The DOM event
82400     * @param {Calendar} cal A reference to the calendar
82401     */
82402    doNextMonthNav : function(e, cal) {
82403        Event.preventDefault(e);
82404        setTimeout(function() {
82405            cal.nextMonth();
82406            var navs = Dom.getElementsByClassName(cal.Style.CSS_NAV_RIGHT, "a", cal.oDomContainer);
82407            if (navs && navs[0]) {
82408                try {
82409                    navs[0].focus();
82410                } catch (ex) {
82411                    // ignore
82412                }
82413            }
82414        }, 0);
82415    },
82416
82417    /**
82418    * The default event handler for date cell selection. Currently attached to 
82419    * the Calendar's bounding box, referenced by it's <a href="#property_oDomContainer">oDomContainer</a> property.
82420    *
82421    * @method doSelectCell
82422    * @param {DOMEvent} e The DOM event
82423    * @param {Calendar} cal A reference to the calendar
82424    */
82425    doSelectCell : function(e, cal) {
82426        var cell, d, date, index;
82427
82428        var target = Event.getTarget(e),
82429            tagName = target.tagName.toLowerCase(),
82430            defSelector = false;
82431
82432        while (tagName != "td" && !Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
82433
82434            if (!defSelector && tagName == "a" && Dom.hasClass(target, cal.Style.CSS_CELL_SELECTOR)) {
82435                defSelector = true;
82436            }
82437
82438            target = target.parentNode;
82439            tagName = target.tagName.toLowerCase();
82440
82441            if (target == this.oDomContainer || tagName == "html") {
82442                return;
82443            }
82444        }
82445
82446        if (defSelector) {
82447            // Stop link href navigation for default renderer
82448            Event.preventDefault(e);
82449        }
82450    
82451        cell = target;
82452
82453        if (Dom.hasClass(cell, cal.Style.CSS_CELL_SELECTABLE)) {
82454            index = cal.getIndexFromId(cell.id);
82455            if (index > -1) {
82456                d = cal.cellDates[index];
82457                if (d) {
82458                    date = DateMath.getDate(d[0],d[1]-1,d[2]);
82459                
82460                    var link;
82461
82462                    cal.logger.log("Selecting cell " + index + " via click", "info");
82463                    if (cal.Options.MULTI_SELECT) {
82464                        link = cell.getElementsByTagName("a")[0];
82465                        if (link) {
82466                            link.blur();
82467                        }
82468
82469                        var cellDate = cal.cellDates[index];
82470                        var cellDateIndex = cal._indexOfSelectedFieldArray(cellDate);
82471
82472                        if (cellDateIndex > -1) { 
82473                            cal.deselectCell(index);
82474                        } else {
82475                            cal.selectCell(index);
82476                        }
82477
82478                    } else {
82479                        link = cell.getElementsByTagName("a")[0];
82480                        if (link) {
82481                            link.blur();
82482                        }
82483                        cal.selectCell(index);
82484                    }
82485                }
82486            }
82487        }
82488    },
82489
82490    /**
82491    * The event that is executed when the user hovers over a cell
82492    * @method doCellMouseOver
82493    * @param {DOMEvent} e The event
82494    * @param {Calendar} cal A reference to the calendar passed by the Event utility
82495    */
82496    doCellMouseOver : function(e, cal) {
82497        var target;
82498        if (e) {
82499            target = Event.getTarget(e);
82500        } else {
82501            target = this;
82502        }
82503
82504        while (target.tagName && target.tagName.toLowerCase() != "td") {
82505            target = target.parentNode;
82506            if (!target.tagName || target.tagName.toLowerCase() == "html") {
82507                return;
82508            }
82509        }
82510
82511        if (Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
82512            Dom.addClass(target, cal.Style.CSS_CELL_HOVER);
82513        }
82514    },
82515
82516    /**
82517    * The event that is executed when the user moves the mouse out of a cell
82518    * @method doCellMouseOut
82519    * @param {DOMEvent} e The event
82520    * @param {Calendar} cal A reference to the calendar passed by the Event utility
82521    */
82522    doCellMouseOut : function(e, cal) {
82523        var target;
82524        if (e) {
82525            target = Event.getTarget(e);
82526        } else {
82527            target = this;
82528        }
82529
82530        while (target.tagName && target.tagName.toLowerCase() != "td") {
82531            target = target.parentNode;
82532            if (!target.tagName || target.tagName.toLowerCase() == "html") {
82533                return;
82534            }
82535        }
82536
82537        if (Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
82538            Dom.removeClass(target, cal.Style.CSS_CELL_HOVER);
82539        }
82540    },
82541
82542    setupConfig : function() {
82543
82544        var cfg = this.cfg;
82545
82546        /**
82547        * The date to use to represent "Today".
82548        *
82549        * @config today
82550        * @type Date
82551        * @default The client side date (new Date()) when the Calendar is instantiated.
82552        */
82553        cfg.addProperty(DEF_CFG.TODAY.key, { value: new Date(DEF_CFG.TODAY.value.getTime()), supercedes:DEF_CFG.TODAY.supercedes, handler:this.configToday, suppressEvent:true } );
82554
82555        /**
82556        * The month/year representing the current visible Calendar date (mm/yyyy)
82557        * @config pagedate
82558        * @type String | Date
82559        * @default Today's date
82560        */
82561        cfg.addProperty(DEF_CFG.PAGEDATE.key, { value: DEF_CFG.PAGEDATE.value || new Date(DEF_CFG.TODAY.value.getTime()), handler:this.configPageDate } );
82562
82563        /**
82564        * The date or range of dates representing the current Calendar selection
82565        * @config selected
82566        * @type String
82567        * @default []
82568        */
82569        cfg.addProperty(DEF_CFG.SELECTED.key, { value:DEF_CFG.SELECTED.value.concat(), handler:this.configSelected } );
82570
82571        /**
82572        * The title to display above the Calendar's month header. The title is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.   
82573        * @config title
82574        * @type HTML
82575        * @default ""
82576        */
82577        cfg.addProperty(DEF_CFG.TITLE.key, { value:DEF_CFG.TITLE.value, handler:this.configTitle } );
82578
82579        /**
82580        * Whether or not a close button should be displayed for this Calendar
82581        * @config close
82582        * @type Boolean
82583        * @default false
82584        */
82585        cfg.addProperty(DEF_CFG.CLOSE.key, { value:DEF_CFG.CLOSE.value, handler:this.configClose } );
82586
82587        /**
82588        * Whether or not an iframe shim should be placed under the Calendar to prevent select boxes from bleeding through in Internet Explorer 6 and below.
82589        * This property is enabled by default for IE6 and below. It is disabled by default for other browsers for performance reasons, but can be 
82590        * enabled if required.
82591        * 
82592        * @config iframe
82593        * @type Boolean
82594        * @default true for IE6 and below, false for all other browsers
82595        */
82596        cfg.addProperty(DEF_CFG.IFRAME.key, { value:DEF_CFG.IFRAME.value, handler:this.configIframe, validator:cfg.checkBoolean } );
82597
82598        /**
82599        * The minimum selectable date in the current Calendar (mm/dd/yyyy)
82600        * @config mindate
82601        * @type String | Date
82602        * @default null
82603        */
82604        cfg.addProperty(DEF_CFG.MINDATE.key, { value:DEF_CFG.MINDATE.value, handler:this.configMinDate } );
82605
82606        /**
82607        * The maximum selectable date in the current Calendar (mm/dd/yyyy)
82608        * @config maxdate
82609        * @type String | Date
82610        * @default null
82611        */
82612        cfg.addProperty(DEF_CFG.MAXDATE.key, { value:DEF_CFG.MAXDATE.value, handler:this.configMaxDate } );
82613
82614        // Options properties
82615    
82616        /**
82617        * True if the Calendar should allow multiple selections. False by default.
82618        * @config MULTI_SELECT
82619        * @type Boolean
82620        * @default false
82621        */
82622        cfg.addProperty(DEF_CFG.MULTI_SELECT.key, { value:DEF_CFG.MULTI_SELECT.value, handler:this.configOptions, validator:cfg.checkBoolean } );
82623
82624        /**
82625        * True if the Calendar should allow selection of out-of-month dates. False by default.
82626        * @config OOM_SELECT
82627        * @type Boolean
82628        * @default false
82629        */
82630        cfg.addProperty(DEF_CFG.OOM_SELECT.key, { value:DEF_CFG.OOM_SELECT.value, handler:this.configOptions, validator:cfg.checkBoolean } );
82631
82632        /**
82633        * The weekday the week begins on. Default is 0 (Sunday = 0, Monday = 1 ... Saturday = 6).
82634        * @config START_WEEKDAY
82635        * @type number
82636        * @default 0
82637        */
82638        cfg.addProperty(DEF_CFG.START_WEEKDAY.key, { value:DEF_CFG.START_WEEKDAY.value, handler:this.configOptions, validator:cfg.checkNumber  } );
82639    
82640        /**
82641        * True if the Calendar should show weekday labels. True by default.
82642        * @config SHOW_WEEKDAYS
82643        * @type Boolean
82644        * @default true
82645        */
82646        cfg.addProperty(DEF_CFG.SHOW_WEEKDAYS.key, { value:DEF_CFG.SHOW_WEEKDAYS.value, handler:this.configOptions, validator:cfg.checkBoolean  } );
82647    
82648        /**
82649        * True if the Calendar should show week row headers. False by default.
82650        * @config SHOW_WEEK_HEADER
82651        * @type Boolean
82652        * @default false
82653        */
82654        cfg.addProperty(DEF_CFG.SHOW_WEEK_HEADER.key, { value:DEF_CFG.SHOW_WEEK_HEADER.value, handler:this.configOptions, validator:cfg.checkBoolean } );
82655    
82656        /**
82657        * True if the Calendar should show week row footers. False by default.
82658        * @config SHOW_WEEK_FOOTER
82659        * @type Boolean
82660        * @default false
82661        */ 
82662        cfg.addProperty(DEF_CFG.SHOW_WEEK_FOOTER.key,{ value:DEF_CFG.SHOW_WEEK_FOOTER.value, handler:this.configOptions, validator:cfg.checkBoolean } );
82663    
82664        /**
82665        * True if the Calendar should suppress weeks that are not a part of the current month. False by default.
82666        * @config HIDE_BLANK_WEEKS
82667        * @type Boolean
82668        * @default false
82669        */ 
82670        cfg.addProperty(DEF_CFG.HIDE_BLANK_WEEKS.key, { value:DEF_CFG.HIDE_BLANK_WEEKS.value, handler:this.configOptions, validator:cfg.checkBoolean } );
82671        
82672        /**
82673        * The image URL that should be used for the left navigation arrow. The image URL is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
82674        * @config NAV_ARROW_LEFT
82675        * @type String
82676        * @deprecated You can customize the image by overriding the default CSS class for the left arrow - "calnavleft"  
82677        * @default null
82678        */ 
82679        cfg.addProperty(DEF_CFG.NAV_ARROW_LEFT.key, { value:DEF_CFG.NAV_ARROW_LEFT.value, handler:this.configOptions } );
82680    
82681        /**
82682        * The image URL that should be used for the right navigation arrow. The image URL is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
82683        * @config NAV_ARROW_RIGHT
82684        * @type String
82685        * @deprecated You can customize the image by overriding the default CSS class for the right arrow - "calnavright"
82686        * @default null
82687        */ 
82688        cfg.addProperty(DEF_CFG.NAV_ARROW_RIGHT.key, { value:DEF_CFG.NAV_ARROW_RIGHT.value, handler:this.configOptions } );
82689    
82690        // Locale properties
82691    
82692        /**
82693        * The short month labels for the current locale. The month labels are inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
82694        * @config MONTHS_SHORT
82695        * @type HTML[]
82696        * @default ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
82697        */
82698        cfg.addProperty(DEF_CFG.MONTHS_SHORT.key, { value:DEF_CFG.MONTHS_SHORT.value, handler:this.configLocale } );
82699
82700        /**
82701        * The long month labels for the current locale. The month labels are inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
82702        * @config MONTHS_LONG
82703        * @type HTML[]
82704        * @default ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "
82704November", "December"
82705        */ 
82706        cfg.addProperty(DEF_CFG.MONTHS_LONG.key,  { value:DEF_CFG.MONTHS_LONG.value, handler:this.configLocale } );
82707
82708        /**
82709        * The 1-character weekday labels for the current locale. The weekday labels are inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
82710        * @config WEEKDAYS_1CHAR
82711        * @type HTML[]
82712        * @default ["S", "M", "T", "W", "T", "F", "S"]
82713        */ 
82714        cfg.addProperty(DEF_CFG.WEEKDAYS_1CHAR.key, { value:DEF_CFG.WEEKDAYS_1CHAR.value, handler:this.configLocale } );
82715        
82716        /**
82717        * The short weekday labels for the current locale. The weekday labels are inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
82718        * @config WEEKDAYS_SHORT
82719        * @type HTML[]
82720        * @default ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
82721        */ 
82722        cfg.addProperty(DEF_CFG.WEEKDAYS_SHORT.key, { value:DEF_CFG.WEEKDAYS_SHORT.value, handler:this.configLocale } );
82723        
82724        /**
82725        * The medium weekday labels for the current locale. The weekday labels are inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
82726        * @config WEEKDAYS_MEDIUM
82727        * @type HTML[]
82728        * @default ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
82729        */ 
82730        cfg.addProperty(DEF_CFG.WEEKDAYS_MEDIUM.key, { value:DEF_CFG.WEEKDAYS_MEDIUM.value, handler:this.configLocale } );
82731        
82732        /**
82733        * The long weekday labels for the current locale. The weekday labels are inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
82734        * @config WEEKDAYS_LONG
82735        * @type HTML[]
82736        * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
82737        */ 
82738        cfg.addProperty(DEF_CFG.WEEKDAYS_LONG.key, { value:DEF_CFG.WEEKDAYS_LONG.value, handler:this.configLocale } );
82739
82740        /**
82741        * Refreshes the locale values used to build the Calendar.
82742        * @method refreshLocale
82743        * @private
82744        */
82745        var refreshLocale = function() {
82746            cfg.refireEvent(DEF_CFG.LOCALE_MONTHS.key);
82747            cfg.refireEvent(DEF_CFG.LOCALE_WEEKDAYS.key);
82748        };
82749    
82750        cfg.subscribeToConfigEvent(DEF_CFG.START_WEEKDAY.key, refreshLocale, this, true);
82751        cfg.subscribeToConfigEvent(DEF_CFG.MONTHS_SHORT.key, refreshLocale, this, true);
82752        cfg.subscribeToConfigEvent(DEF_CFG.MONTHS_LONG.key, refreshLocale, this, true);
82753        cfg.subscribeToConfigEvent(DEF_CFG.WEEKDAYS_1CHAR.key, refreshLocale, this, true);
82754        cfg.subscribeToConfigEvent(DEF_CFG.WEEKDAYS_SHORT.key, refreshLocale, this, true);
82755        cfg.subscribeToConfigEvent(DEF_CFG.WEEKDAYS_MEDIUM.key, refreshLocale, this, true);
82756        cfg.subscribeToConfigEvent(DEF_CFG.WEEKDAYS_LONG.key, refreshLocale, this, true);
82757       
82758        /**
82759        * The setting that determines which length of month labels should be used. Possible values are "short" and "long".
82760        * @config LOCALE_MONTHS
82761        * @type String
82762        * @default "long"
82763        */ 
82764        cfg.addProperty(DEF_CFG.LOCALE_MONTHS.key, { value:DEF_CFG.LOCALE_MONTHS.value, handler:this.configLocaleValues } );
82765        
82766        /**
82767        * The setting that determines which length of weekday labels should be used. Possible values are "1char", "short", "medium", and "long".
82768        * @config LOCALE_WEEKDAYS
82769        * @type String
82770        * @default "short"
82771        */ 
82772        cfg.addProperty(DEF_CFG.LOCALE_WEEKDAYS.key, { value:DEF_CFG.LOCALE_WEEKDAYS.value, handler:this.configLocaleValues } );
82773
82774        /**
82775        * The positive or negative year offset from the Gregorian calendar year (assuming a January 1st rollover) to 
82776        * be used when displaying and parsing dates. NOTE: All JS Date objects returned by methods, or expected as input by
82777        * methods will always represent the Gregorian year, in order to maintain date/month/week values. 
82778        *
82779        * @config YEAR_OFFSET
82780        * @type Number
82781        * @default 0
82782        */
82783        cfg.addProperty(DEF_CFG.YEAR_OFFSET.key, { value:DEF_CFG.YEAR_OFFSET.value, supercedes:DEF_CFG.YEAR_OFFSET.supercedes, handler:this.configLocale  } );
82784    
82785        /**
82786        * The value used to delimit individual dates in a date string passed to various Calendar functions.
82787        * @config DATE_DELIMITER
82788        * @type String
82789        * @default ","
82790        */ 
82791        cfg.addProperty(DEF_CFG.DATE_DELIMITER.key,  { value:DEF_CFG.DATE_DELIMITER.value, handler:this.configLocale } );
82792    
82793        /**
82794        * The value used to delimit date fields in a date string passed to various Calendar functions.
82795        * @config DATE_FIELD_DELIMITER
82796        * @type String
82797        * @default "/"
82798        */ 
82799        cfg.addProperty(DEF_CFG.DATE_FIELD_DELIMITER.key, { value:DEF_CFG.DATE_FIELD_DELIMITER.value, handler:this.configLocale } );
82800    
82801        /**
82802        * The value used to delimit date ranges in a date string passed to various Calendar functions.
82803        * @config DATE_RANGE_DELIMITER
82804        * @type String
82805        * @default "-"
82806        */
82807        cfg.addProperty(DEF_CFG.DATE_RANGE_DELIMITER.key, { value:DEF_CFG.DATE_RANGE_DELIMITER.value, handler:this.configLocale } );
82808    
82809        /**
82810        * The position of the month in a month/year date string
82811        * @config MY_MONTH_POSITION
82812        * @type Number
82813        * @default 1
82814        */
82815        cfg.addProperty(DEF_CFG.MY_MONTH_POSITION.key, { value:DEF_CFG.MY_MONTH_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
82816    
82817        /**
82818        * The position of the year in a month/year date string
82819        * @config MY_YEAR_POSITION
82820        * @type Number
82821        * @default 2
82822        */
82823        cfg.addProperty(DEF_CFG.MY_YEAR_POSITION.key, { value:DEF_CFG.MY_YEAR_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
82824    
82825        /**
82826        * The position of the month in a month/day date string
82827        * @config MD_MONTH_POSITION
82828        * @type Number
82829        * @default 1
82830        */
82831        cfg.addProperty(DEF_CFG.MD_MONTH_POSITION.key, { value:DEF_CFG.MD_MONTH_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
82832    
82833        /**
82834        * The position of the day in a month/year date string
82835        * @config MD_DAY_POSITION
82836        * @type Number
82837        * @default 2
82838        */
82839        cfg.addProperty(DEF_CFG.MD_DAY_POSITION.key,  { value:DEF_CFG.MD_DAY_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
82840    
82841        /**
82842        * The position of the month in a month/day/year date string
82843        * @config MDY_MONTH_POSITION
82844        * @type Number
82845        * @default 1
82846        */
82847        cfg.addProperty(DEF_CFG.MDY_MONTH_POSITION.key, { value:DEF_CFG.MDY_MONTH_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
82848    
82849        /**
82850        * The position of the day in a month/day/year date string
82851        * @config MDY_DAY_POSITION
82852        * @type Number
82853        * @default 2
82854        */
82855        cfg.addProperty(DEF_CFG.MDY_DAY_POSITION.key, { value:DEF_CFG.MDY_DAY_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
82856    
82857        /**
82858        * The position of the year in a month/day/year date string
82859        * @config MDY_YEAR_POSITION
82860        * @type Number
82861        * @default 3
82862        */
82863        cfg.addProperty(DEF_CFG.MDY_YEAR_POSITION.key, { value:DEF_CFG.MDY_YEAR_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
82864        
82865        /**
82866        * The position of the month in the month year label string used as the Calendar header
82867        * @config MY_LABEL_MONTH_POSITION
82868        * @type Number
82869        * @default 1
82870        */
82871        cfg.addProperty(DEF_CFG.MY_LABEL_MONTH_POSITION.key, { value:DEF_CFG.MY_LABEL_MONTH_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
82872    
82873        /**
82874        * The position of the year in the month year label string used as the Calendar header
82875        * @config MY_LABEL_YEAR_POSITION
82876        * @type Number
82877        * @default 2
82878        */
82879        cfg.addProperty(DEF_CFG.MY_LABEL_YEAR_POSITION.key, { value:DEF_CFG.MY_LABEL_YEAR_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
82880        
82881        /**
82882        * The suffix used after the month when rendering the Calendar header. The suffix is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
82883        * @config MY_LABEL_MONTH_SUFFIX
82884        * @type HTML
82885        * @default " "
82886        */
82887        cfg.addProperty(DEF_CFG.MY_LABEL_MONTH_SUFFIX.key, { value:DEF_CFG.MY_LABEL_MONTH_SUFFIX.value, handler:this.configLocale } );
82888        
82889        /**
82890        * The suffix used after the year when rendering the Calendar header. The suffix is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
82891        * @config MY_LABEL_YEAR_SUFFIX
82892        * @type HTML
82893        * @default ""
82894        */
82895        cfg.addProperty(DEF_CFG.MY_LABEL_YEAR_SUFFIX.key, { value:DEF_CFG.MY_LABEL_YEAR_SUFFIX.value, handler:this.configLocale } );
82896
82897        /**
82898        * Configuration for the Month/Year CalendarNavigator UI which allows the user to jump directly to a 
82899        * specific Month/Year without having to scroll sequentially through months.
82900        * <p>
82901        * Setting this property to null (default value) or false, will disable the CalendarNavigator UI.
82902        * </p>
82903        * <p>
82904        * Setting this property to true will enable the CalendarNavigatior UI with the default CalendarNavigator configuration values.
82905        * </p>
82906        * <p>
82907        * This property can also be set to an object literal containing configuration properties for the CalendarNavigator UI.
82908        * The configuration object expects the the following case-sensitive properties, with the "strings" property being a nested object.
82909        * Any properties which are not provided will use the default values (defined in the CalendarNavigator class).
82910        * </p>
82911        * <dl>
82912        * <dt>strings</dt>
82913        * <dd><em>Object</em> :  An object with the properties shown below, defining the string labels to use in the Navigator's UI. The strings are inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source. 
82914        *     <dl>
82915        *         <dt>month</dt><dd><em>HTML</em> : The markup to use for the month label. Defaults to "Month".</d
82915d>
82916        *         <dt>year</dt><dd><em>HTML</em> : The markup to use for the year label. Defaults to "Year".</dd>
82917        *         <dt>submit</dt><dd><em>HTML</em> : The markup to use for the submit button label. Defaults to "Okay".</dd>
82918        *         <dt>cancel</dt><dd><em>HTML</em> : The markup to use for the cancel button label. Defaults to "Cancel".</dd>
82919        *         <dt>invalidYear</dt><dd><em>HTML</em> : The markup to use for invalid year values. Defaults to "Year needs to be a number".</dd>
82920        *     </dl>
82921        * </dd>
82922        * <dt>monthFormat</dt><dd><em>String</em> : The month format to use. Either YAHOO.widget.Calendar.LONG, or YAHOO.widget.Calendar.SHORT. Defaults to YAHOO.widget.Calendar.LONG</dd>
82923        * <dt>initialFocus</dt><dd><em>String</em> : Either "year" or "month" specifying which input control should get initial focus. Defaults to "year"</dd>
82924        * </dl>
82925        * <p>E.g.</p>
82926        * <pre>
82927        * var navConfig = {
82928        *   strings: {
82929        *    month:"Calendar Month",
82930        *    year:"Calendar Year",
82931        *    submit: "Submit",
82932        *    cancel: "Cancel",
82933        *    invalidYear: "Please enter a valid year"
82934        *   },
82935        *   monthFormat: YAHOO.widget.Calendar.SHORT,
82936        *   initialFocus: "month"
82937        * }
82938        * </pre>
82939        * @config navigator
82940        * @type {Object|Boolean}
82941        * @default null
82942        */
82943        cfg.addProperty(DEF_CFG.NAV.key, { value:DEF_CFG.NAV.value, handler:this.configNavigator } );
82944
82945        /**
82946         * The map of UI strings which the Calendar UI uses.
82947         *
82948         * @config strings
82949         * @type {Object}
82950         * @default An object with the properties shown below:
82951         *     <dl>
82952         *         <dt>previousMonth</dt><dd><em>HTML</em> : The markup to use for the "Previous Month" navigation label. Defaults to "Previous Month". The string is added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.</dd>
82953         *         <dt>nextMonth</dt><dd><em>HTML</em> : The markup to use for the "Next Month" navigation UI. Defaults to "Next Month". The string is added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.</dd>
82954         *         <dt>close</dt><dd><em>HTML</em> : The markup to use for the close button label. Defaults to "Close". The string is added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.</dd>
82955         *     </dl>
82956         */
82957        cfg.addProperty(DEF_CFG.STRINGS.key, { 
82958            value:DEF_CFG.STRINGS.value,
82959            handler:this.configStrings,
82960            validator: function(val) {
82961                return Lang.isObject(val);
82962            },
82963            supercedes:DEF_CFG.STRINGS.supercedes
82964        });
82965    },
82966
82967    /**
82968    * The default handler for the "strings" property
82969    * @method configStrings
82970    */
82971    configStrings : function(type, args, obj) {
82972        var val = Lang.merge(DEF_CFG.STRINGS.value, args[0]);
82973        this.cfg.setProperty(DEF_CFG.STRINGS.key, val, true);
82974    },
82975
82976    /**
82977    * The default handler for the "pagedate" property
82978    * @method configPageDate
82979    */
82980    configPageDate : function(type, args, obj) {
82981        this.cfg.setProperty(DEF_CFG.PAGEDATE.key, this._parsePageDate(args[0]), true);
82982    },
82983
82984    /**
82985    * The default handler for the "mindate" property
82986    * @method configMinDate
82987    */
82988    configMinDate : function(type, args, obj) {
82989        var val = args[0];
82990        if (Lang.isString(val)) {
82991            val = this._parseDate(val);
82992            this.cfg.setProperty(DEF_CFG.MINDATE.key, DateMath.getDate(val[0],(val[1]-1),val[2]));
82993        }
82994    },
82995
82996    /**
82997    * The default handler for the "maxdate" property
82998    * @method configMaxDate
82999    */
83000    configMaxDate : function(type, args, obj) {
83001        var val = args[0];
83002        if (Lang.isString(val)) {
83003            val = this._parseDate(val);
83004            this.cfg.setProperty(DEF_CFG.MAXDATE.key, DateMath.getDate(val[0],(val[1]-1),val[2]));
83005        }
83006    },
83007
83008    /**
83009    * The default handler for the "today" property
83010    * @method configToday
83011    */
83012    configToday : function(type, args, obj) {
83013        // Only do this for initial set. Changing the today property after the initial
83014        // set, doesn't affect pagedate
83015        var val = args[0];
83016        if (Lang.isString(val)) {
83017            val = this._parseDate(val);
83018        }
83019        var today = DateMath.clearTime(val);
83020        if (!this.cfg.initialConfig[DEF_CFG.PAGEDATE.key]) {
83021            this.cfg.setProperty(DEF_CFG.PAGEDATE.key, today);
83022        }
83023        this.today = today;
83024        this.cfg.setProperty(DEF_CFG.TODAY.key, today, true);
83025    },
83026
83027    /**
83028    * The default handler for the "selected" property
83029    * @method configSelected
83030    */
83031    configSelected : function(type, args, obj) {
83032        var selected = args[0],
83033            cfgSelected = DEF_CFG.SELECTED.key;
83034        
83035        if (selected) {
83036            if (Lang.isString(selected)) {
83037                this.cfg.setProperty(cfgSelected, this._parseDates(selected), true);
83038            } 
83039        }
83040        if (! this._selectedDates) {
83041            this._selectedDates = this.cfg.getProperty(cfgSelected);
83042        }
83043    },
83044    
83045    /**
83046    * The default handler for all configuration options properties
83047    * @method configOptions
83048    */
83049    configOptions : function(type, args, obj) {
83050        this.Options[type.toUpperCase()] = args[0];
83051    },
83052
83053    /**
83054    * The default handler for all configuration locale properties
83055    * @method configLocale
83056    */
83057    configLocale : function(type, args, obj) {
83058        this.Locale[type.toUpperCase()] = args[0];
83059
83060        this.cfg.refireEvent(DEF_CFG.LOCALE_MONTHS.key);
83061        this.cfg.refireEvent(DEF_CFG.LOCALE_WEEKDAYS.key);
83062    },
83063    
83064    /**
83065    * The default handler for all configuration locale field length properties
83066    * @method configLocaleValues
83067    */
83068    configLocaleValues : function(type, args, obj) {
83069
83070        type = type.toLowerCase();
83071
83072        var val = args[0],
83073            cfg = this.cfg,
83074            Locale = this.Locale;
83075
83076        switch (type) {
83077            case DEF_CFG.LOCALE_MONTHS.key:
83078                switch (val) {
83079                    case Calendar.SHORT:
83080                        Locale.LOCALE_MONTHS = cfg.getProperty(DEF_CFG.MONTHS_SHORT.key).concat();
83081                        break;
83082                    case Calendar.LONG:
83083                        Locale.LOCALE_MONTHS = cfg.getProperty(DEF_CFG.MONTHS_LONG.key).concat();
83084                        break;
83085                }
83086                break;
83087            case DEF_CFG.LOCALE_WEEKDAYS.key:
83088                switch (val) {
83089                    case Calendar.ONE_CHAR:
83090                        Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_1CHAR.key).concat();
83091                        break;
83092                    case Calendar.SHORT:
83093                        Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_SHORT.key).concat();
83094                        break;
83095                    case Calendar.MEDIUM:
83096                        Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_MEDIUM.key).concat();
83097                        break;
83098                    case Calendar.LONG:
83099                        Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_LONG.key).concat();
83100                        break;
83101                }
83102                
83103                var START_WEEKDAY = cfg.getProperty(DEF_CFG.START_WEEKDAY.key);
83104    
83105                if (START_WEEKDAY > 0) {
83106                    for (var w=0; w < START_WEEKDAY; ++w) {
83107                        Locale.LOCALE_WEEKDAYS.push(Locale.LOCALE_WEEKDAYS.shift());
83108                    }
83109                }
83110                break;
83111        }
83112    },
83113
83114    /**
83115     * The default handler for the "navigator" property
83116     * @method configNavigator
83117     */
83118    configNavigator : function(type, args, obj) {
83119        var val = args[0];
83120        if (YAHOO.widget.CalendarNavigator && (val === true || Lang.isObject(val))) {
83121            if (!this.oNavigator) {
83122                this.oNavigator = new YAHOO.widget.CalendarNavigator(this);
83123                // Cleanup DOM Refs/Events before innerHTML is removed.
83124                this.beforeRenderEvent.subscribe(function () {
83125                    if (!this.pages) {
83126                        this.oNavigator.erase();
83127                    }
83128                }, this, true);
83129            }
83130        } else {
83131            if (this.oNavigator) {
83132                this.oNavigator.destroy();
83133                this.oNavigator = null;
83134            }
83135        }
83136    },
83137
83138    /**
83139    * Defines the class names used by Calendar when rendering to DOM. NOTE: The class names are added to the DOM as HTML and should be escaped by the implementor if coming from an external source. 
83140    * @method initStyles
83141    */
83142    initStyles : function() {
83143
83144        var defStyle = Calendar.STYLES;
83145
83146        this.Style = {
83147            /**
83148            * @property Style.CSS_ROW_HEADER
83149            */
83150            CSS_ROW_HEADER: defStyle.CSS_ROW_HEADER,
83151            /**
83152            * @property Style.CSS_ROW_FOOTER
83153            */
83154            CSS_ROW_FOOTER: defStyle.CSS_ROW_FOOTER,
83155            /**
83156            * @property Style.CSS_CELL
83157            */
83158            CSS_CELL : defStyle.CSS_CELL,
83159            /**
83160            * @property Style.CSS_CELL_SELECTOR
83161            */
83162            CSS_CELL_SELECTOR : defStyle.CSS_CELL_SELECTOR,
83163            /**
83164            * @property Style.CSS_CELL_SELECTED
83165            */
83166            CSS_CELL_SELECTED : defStyle.CSS_CELL_SELECTED,
83167            /**
83168            * @property Style.CSS_CELL_SELECTABLE
83169            */
83170            CSS_CELL_SELECTABLE : defStyle.CSS_CELL_SELECTABLE,
83171            /**
83172            * @property Style.CSS_CELL_RESTRICTED
83173            */
83174            CSS_CELL_RESTRICTED : defStyle.CSS_CELL_RESTRICTED,
83175            /**
83176            * @property Style.CSS_CELL_TODAY
83177            */
83178            CSS_CELL_TODAY : defStyle.CSS_CELL_TODAY,
83179            /**
83180            * @property Style.CSS_CELL_OOM
83181            */
83182            CSS_CELL_OOM : defStyle.CSS_CELL_OOM,
83183            /**
83184            * @property Style.CSS_CELL_OOB
83185            */
83186            CSS_CELL_OOB : defStyle.CSS_CELL_OOB,
83187            /**
83188            * @property Style.CSS_HEADER
83189            */
83190            CSS_HEADER : defStyle.CSS_HEADER,
83191            /**
83192            * @property Style.CSS_HEADER_TEXT
83193            */
83194            CSS_HEADER_TEXT : defStyle.CSS_HEADER_TEXT,
83195            /**
83196            * @property Style.CSS_BODY
83197            */
83198            CSS_BODY : defStyle.CSS_BODY,
83199            /**
83200            * @property Style.CSS_WEEKDAY_CELL
83201            */
83202            CSS_WEEKDAY_CELL : defStyle.CSS_WEEKDAY_CELL,
83203            /**
83204            * @property Style.CSS_WEEKDAY_ROW
83205            */
83206            CSS_WEEKDAY_ROW : defStyle.CSS_WEEKDAY_ROW,
83207            /**
83208            * @property Style.CSS_FOOTER
83209            */
83210            CSS_FOOTER : defStyle.CSS_FOOTER,
83211            /**
83212            * @property Style.CSS_CALENDAR
83213            */
83214            CSS_CALENDAR : defStyle.CSS_CALENDAR,
83215            /**
83216            * @property Style.CSS_SINGLE
83217            */
83218            CSS_SINGLE : defStyle.CSS_SINGLE,
83219            /**
83220            * @property Style.CSS_CONTAINER
83221            */
83222            CSS_CONTAINER : defStyle.CSS_CONTAINER,
83223            /**
83224            * @property Style.CSS_NAV_LEFT
83225            */
83226            CSS_NAV_LEFT : defStyle.CSS_NAV_LEFT,
83227            /**
83228            * @property Style.CSS_NAV_RIGHT
83229            */
83230            CSS_NAV_RIGHT : defStyle.CSS_NAV_RIGHT,
83231            /**
83232            * @property Style.CSS_NAV
83233            */
83234            CSS_NAV : defStyle.CSS_NAV,
83235            /**
83236            * @property Style.CSS_CLOSE
83237            */
83238            CSS_CLOSE : defStyle.CSS_CLOSE,
83239            /**
83240            * @property Style.CSS_CELL_TOP
83241            */
83242            CSS_CELL_TOP : defStyle.CSS_CELL_TOP,
83243            /**
83244            * @property Style.CSS_CELL_LEFT
83245            */
83246            CSS_CELL_LEFT : defStyle.CSS_CELL_LEFT,
83247            /**
83248            * @property Style.CSS_CELL_RIGHT
83249            */
83250            CSS_CELL_RIGHT : defStyle.CSS_CELL_RIGHT,
83251            /**
83252            * @property Style.CSS_CELL_BOTTOM
83253            */
83254            CSS_CELL_BOTTOM : defStyle.CSS_CELL_BOTTOM,
83255            /**
83256            * @property Style.CSS_CELL_HOVER
83257            */
83258            CSS_CELL_HOVER : defStyle.CSS_CELL_HOVER,
83259            /**
83260            * @property Style.CSS_CELL_HIGHLIGHT1
83261            */
83262            CSS_CELL_HIGHLIGHT1 : defStyle.CSS_CELL_HIGHLIGHT1,
83263            /**
83264            * @property Style.CSS_CELL_HIGHLIGHT2
83265            */
83266            CSS_CELL_HIGHLIGHT2 : defStyle.CSS_CELL_HIGHLIGHT2,
83267            /**
83268            * @property Style.CSS_CELL_HIGHLIGHT3
83269            */
83270            CSS_CELL_HIGHLIGHT3 : defStyle.CSS_CELL_HIGHLIGHT3,
83271            /**
83272            * @property Style.CSS_CELL_HIGHLIGHT4
83273            */
83274            CSS_CELL_HIGHLIGHT4 : defStyle.CSS_CELL_HIGHLIGHT4,
83275            /**
83276             * @property Style.CSS_WITH_TITLE
83277             */
83278            CSS_WITH_TITLE : defStyle.CSS_WITH_TITLE,
83279             /**
83280             * @property Style.CSS_FIXED_SIZE
83281             */
83282            CSS_FIXED_SIZE : defStyle.CSS_FIXED_SIZE,
83283             /**
83284             * @property Style.CSS_LINK_CLOSE
83285             */
83286            CSS_LINK_CLOSE : defStyle.CSS_LINK_CLOSE
83287        };
83288    },
83289
83290    /**
83291    * Builds the date label that will be displayed in the calendar header or
83292    * footer, depending on configuration.
83293    * @method buildMonthLabel
83294    * @return {HTML} The formatted calendar month label
83295    */
83296    buildMonthLabel : function() {
83297        return this._buildMonthLabel(this.cfg.getProperty(DEF_CFG.PAGEDATE.key));
83298    },
83299
83300    /**
83301     * Helper method, to format a Month Year string, given a JavaScript Date, based on the 
83302     * Calendar localization settings
83303     * 
83304     * @method _buildMonthLabel
83305     * @private
83306     * @param {Date} date
83307     * @return {HTML} Formated month, year string
83308     */
83309    _buildMonthLabel : function(date) {
83310        var monthLabel  = this.Locale.LOCALE_MONTHS[date.getMonth()] + this.Locale.MY_LABEL_MONTH_SUFFIX,
83311            yearLabel = (date.getFullYear() + this.Locale.YEAR_OFFSET) + this.Locale.MY_LABEL_YEAR_SUFFIX;
83312
83313        if (this.Locale.MY_LABEL_MONTH_POSITION == 2 || this.Locale.MY_LABEL_YEAR_POSITION == 1) {
83314            return yearLabel + monthLabel;
83315        } else {
83316            return monthLabel + yearLabel;
83317        }
83318    },
83319
83320    /**
83321    * Builds the date digit that will be displayed in calendar cells
83322    * @method buildDayLabel
83323    * @param {Date} workingDate The current working date
83324    * @return {Number} The day
83325    */
83326    buildDayLabel : function(workingDate) {
83327        return workingDate.getDate();
83328    },
83329
83330    /**
83331     * Creates the title bar element and adds it to Calendar container DIV. NOTE: The title parameter passed into this method is added to the DOM as HTML and should be escaped by the implementor if coming from an external source.  
83332     * 
83333     * @method createTitleBar
83334     * @param {HTML} strTitle The title to display in the title bar
83335     * @return The title bar element
83336     */
83337    createTitleBar : function(strTitle) {
83338        var tDiv = Dom.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE, "div", this.oDomContainer)[0] || document.createElement("div");
83339        tDiv.className = YAHOO.widget.CalendarGroup.CSS_2UPTITLE;
83340        tDiv.innerHTML = strTitle;
83341        this.oDomContainer.insertBefore(tDiv, this.oDomContainer.firstChild);
83342    
83343        Dom.addClass(this.oDomContainer, this.Style.CSS_WITH_TITLE);
83344    
83345        return tDiv;
83346    },
83347    
83348    /**
83349     * Removes the title bar element from the DOM
83350     * 
83351     * @method removeTitleBar
83352     */
83353    removeTitleBar : function() {
83354        var tDiv = Dom.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE, "div", this.oDomContainer)[0] || null;
83355        if (tDiv) {
83356            Event.purgeElement(tDiv);
83357            this.oDomContainer.removeChild(tDiv);
83358        }
83359        Dom.removeClass(this.oDomContainer, this.Style.CSS_WITH_TITLE);
83360    },
83361
83362    /**
83363     * Creates the close button HTML element and adds it to Calendar container DIV
83364     * 
83365     * @method createCloseButton
83366     * @return {HTMLElement} The close HTML element created
83367     */
83368    createCloseButton : function() {
83369        var cssClose = YAHOO.widget.CalendarGroup.CSS_2UPCLOSE,
83370            cssLinkClose = this.Style.CSS_LINK_CLOSE,
83371            DEPR_CLOSE_PATH = "us/my/bn/x_d.gif",
83372
83373            lnk = Dom.getElementsByClassName(cssLinkClose, "a", this.oDomContainer)[0],
83374            strings = this.cfg.getProperty(DEF_CFG.STRINGS.key),
83375            closeStr = (strings && strings.close) ? strings.close : "";
83376
83377        if (!lnk) {
83378            lnk = document.createElement("a");
83379            Event.addListener(lnk, "click", function(e, cal) {
83380                cal.hide(); 
83381                Event.preventDefault(e);
83382            }, this);
83383        }
83384
83385        lnk.href = "#";
83386        lnk.className = cssLinkClose;
83387
83388        if (Calendar.IMG_ROOT !== null) {
83389            var img = Dom.getElementsByClassName(cssClose, "img", lnk)[0] || document.createElement("img");
83390            img.src = Calendar.IMG_ROOT + DEPR_CLOSE_PATH;
83391            img.className = cssClose;
83392            lnk.appendChild(img);
83393        } else {
83394            lnk.innerHTML = '<span class="' + cssClose + ' ' + this.Style.CSS_CLOSE + '">' + closeStr + '</span>';
83395        }
83396        this.oDomContainer.appendChild(lnk);
83397
83398        return lnk;
83399    },
83400    
83401    /**
83402     * Removes the close button HTML element from the DOM
83403     * 
83404     * @method removeCloseButton
83405     */
83406    removeCloseButton : function() {
83407        var btn = Dom.getElementsByClassName(this.Style.CSS_LINK_CLOSE, "a", this.oDomContainer)[0] || null;
83408        if (btn) {
83409            Event.purgeElement(btn);
83410            this.oDomContainer.removeChild(btn);
83411        }
83412    },
83413
83414    /**
83415    * Renders the calendar header. NOTE: The contents of the array passed into this method are added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.
83416    * @method renderHeader
83417    * @param {HTML[]} html The current working HTML array
83418    * @return {HTML[]} The current working HTML array
83419    */
83420    renderHeader : function(html) {
83421
83422        this.logger.log("Rendering header", "render");
83423
83424        var colSpan = 7,
83425            DEPR_NAV_LEFT = "us/tr/callt.gif",
83426            DEPR_NAV_RIGHT = "us/tr/calrt.gif",
83427            cfg = this.cfg,
83428            pageDate = cfg.getProperty(DEF_CFG.PAGEDATE.key),
83429            strings= cfg.getProperty(DEF_CFG.STRINGS.key),
83430            prevStr = (strings && strings.previousMonth) ?  strings.previousMonth : "",
83431            nextStr = (strings && strings.nextMonth) ? strings.nextMonth : "",
83432            monthLabel;
83433
83434        if (cfg.getProperty(DEF_CFG.SHOW_WEEK_HEADER.key)) {
83435            colSpan += 1;
83436        }
83437    
83438        if (cfg.getProperty(DEF_CFG.SHOW_WEEK_FOOTER.key)) {
83439            colSpan += 1;
83440        }
83441
83442        html[html.length] = "<thead>";
83443        html[html.length] =  "<tr>";
83444        html[html.length] =   '<th colspan="' + colSpan + '" class="' + this.Style.CSS_HEADER_TEXT + '">';
83445        html[html.length] =    '<div class="' + this.Style.CSS_HEADER + '">';
83446
83447        var renderLeft, renderRight = false;
83448
83449        if (this.parent) {
83450            if (this.index === 0) {
83451                renderLeft = true;
83452            }
83453            if (this.index == (this.parent.cfg.getProperty("pages") -1)) {
83454                renderRight = true;
83455            }
83456        } else {
83457            renderLeft = true;
83458            renderRight = true;
83459        }
83460
83461        if (renderLeft) {
83462            monthLabel  = this._buildMonthLabel(DateMath.subtract(pageDate, DateMath.MONTH, 1));
83463
83464            var leftArrow = cfg.getProperty(DEF_CFG.NAV_ARROW_LEFT.key);
83465            // Check for deprecated customization - If someone set IMG_ROOT, but didn't set NAV_ARROW_LEFT, then set NAV_ARROW_LEFT to the old deprecated value
83466            if (leftArrow === null && Calendar.IMG_ROOT !== null) {
83467                leftArrow = Calendar.IMG_ROOT + DEPR_NAV_LEFT;
83468            }
83469            var leftStyle = (leftArrow === null) ? "" : ' style="background-image:url(' + leftArrow + ')"';
83470            html[html.length] = '<a class="' + this.Style.CSS_NAV_LEFT + '"' + leftStyle + ' href="#">' + prevStr + ' (' + monthLabel + ')' + '</a>';
83471        }
83472
83473        var lbl = this.buildMonthLabel();
83474        var cal = this.parent || this;
83475        if (cal.cfg.getProperty("navigator")) {
83476            lbl = "<a class=\"" + this.Style.CSS_NAV + "\" href=\"#\">" + lbl + "</a>";
83477        }
83478        html[html.length] = lbl;
83479
83480        if (renderRight) {
83481            monthLabel  = this._buildMonthLabel(DateMath.add(pageDate, DateMath.MONTH, 1));
83482
83483            var rightArrow = cfg.getProperty(DEF_CFG.NAV_ARROW_RIGHT.key);
83484            if (rightArrow === null && Calendar.IMG_ROOT !== null) {
83485                rightArrow = Calendar.IMG_ROOT + DEPR_NAV_RIGHT;
83486            }
83487            var rightStyle = (rightArrow === null) ? "" : ' style="background-image:url(' + rightArrow + ')"';
83488            html[html.length] = '<a class="' + this.Style.CSS_NAV_RIGHT + '"' + rightStyle + ' href="#">
83488' + nextStr + ' (' + monthLabel + ')' + '</a>';
83489        }
83490
83491        html[html.length] = '</div>\n</th>\n</tr>';
83492
83493        if (cfg.getProperty(DEF_CFG.SHOW_WEEKDAYS.key)) {
83494            html = this.buildWeekdays(html);
83495        }
83496        
83497        html[html.length] = '</thead>';
83498    
83499        return html;
83500    },
83501
83502    /**
83503    * Renders the Calendar's weekday headers. NOTE: The contents of the array passed into this method are added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.
83504    * @method buildWeekdays
83505    * @param {HTML[]} html The current working HTML array
83506    * @return {HTML[]} The current working HTML array
83507    */
83508    buildWeekdays : function(html) {
83509
83510        html[html.length] = '<tr class="' + this.Style.CSS_WEEKDAY_ROW + '">';
83511
83512        if (this.cfg.getProperty(DEF_CFG.SHOW_WEEK_HEADER.key)) {
83513            html[html.length] = '<th>&#160;</th>';
83514        }
83515
83516        for(var i=0;i < this.Locale.LOCALE_WEEKDAYS.length; ++i) {
83517            html[html.length] = '<th class="' + this.Style.CSS_WEEKDAY_CELL + '">' + this.Locale.LOCALE_WEEKDAYS[i] + '</th>';
83518        }
83519
83520        if (this.cfg.getProperty(DEF_CFG.SHOW_WEEK_FOOTER.key)) {
83521            html[html.length] = '<th>&#160;</th>';
83522        }
83523
83524        html[html.length] = '</tr>';
83525
83526        return html;
83527    },
83528    
83529    /**
83530    * Renders the calendar body. NOTE: The contents of the array passed into this method are added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.
83531    * @method renderBody
83532    * @param {Date} workingDate The current working Date being used for the render process
83533    * @param {HTML[]} html The current working HTML array
83534    * @return {HTML[]} The current working HTML array
83535    */
83536    renderBody : function(workingDate, html) {
83537        this.logger.log("Rendering body", "render");
83538
83539        var startDay = this.cfg.getProperty(DEF_CFG.START_WEEKDAY.key);
83540
83541        this.preMonthDays = workingDate.getDay();
83542        if (startDay > 0) {
83543            this.preMonthDays -= startDay;
83544        }
83545        if (this.preMonthDays < 0) {
83546            this.preMonthDays += 7;
83547        }
83548
83549        this.monthDays = DateMath.findMonthEnd(workingDate).getDate();
83550        this.postMonthDays = Calendar.DISPLAY_DAYS-this.preMonthDays-this.monthDays;
83551
83552        this.logger.log(this.preMonthDays + " preciding out-of-month days", "render");
83553        this.logger.log(this.monthDays + " month days", "render");
83554        this.logger.log(this.postMonthDays + " post-month days", "render");
83555
83556        workingDate = DateMath.subtract(workingDate, DateMath.DAY, this.preMonthDays);
83557        this.logger.log("Calendar page starts on " + workingDate, "render");
83558    
83559        var weekNum,
83560            weekClass,
83561            weekPrefix = "w",
83562            cellPrefix = "_cell",
83563            workingDayPrefix = "wd",
83564            dayPrefix = "d",
83565            cellRenderers,
83566            renderer,
83567            t = this.today,
83568            cfg = this.cfg,
83569            oom,
83570            todayYear = t.getFullYear(),
83571            todayMonth = t.getMonth(),
83572            todayDate = t.getDate(),
83573            useDate = cfg.getProperty(DEF_CFG.PAGEDATE.key),
83574            hideBlankWeeks = cfg.getProperty(DEF_CFG.HIDE_BLANK_WEEKS.key),
83575            showWeekFooter = cfg.getProperty(DEF_CFG.SHOW_WEEK_FOOTER.key),
83576            showWeekHeader = cfg.getProperty(DEF_CFG.SHOW_WEEK_HEADER.key),
83577            oomSelect = cfg.getProperty(DEF_CFG.OOM_SELECT.key),
83578            mindate = cfg.getProperty(DEF_CFG.MINDATE.key),
83579            maxdate = cfg.getProperty(DEF_CFG.MAXDATE.key),
83580            yearOffset = this.Locale.YEAR_OFFSET;
83581
83582        if (mindate) {
83583            mindate = DateMath.clearTime(mindate);
83584        }
83585        if (maxdate) {
83586            maxdate = DateMath.clearTime(maxdate);
83587        }
83588
83589        html[html.length] = '<tbody class="m' + (useDate.getMonth()+1) + ' ' + this.Style.CSS_BODY + '">';
83590
83591        var i = 0,
83592            tempDiv = document.createElement("div"),
83593            cell = document.createElement("td");
83594
83595        tempDiv.appendChild(cell);
83596
83597        var cal = this.parent || this;
83598
83599        for (var r = 0; r < 6; r++) {
83600            weekNum = DateMath.getWeekNumber(workingDate, startDay);
83601            weekClass = weekPrefix + weekNum;
83602
83603            // Local OOM check for performance, since we already have pagedate
83604            if (r !== 0 && hideBlankWeeks === true && workingDate.getMonth() != useDate.getMonth()) {
83605                break;
83606            } else {
83607                html[html.length] = '<tr class="' + weekClass + '">';
83608
83609                if (showWeekHeader) { html = this.renderRowHeader(weekNum, html); }
83610
83611                for (var d=0; d < 7; d++){ // Render actual days
83612
83613                    cellRenderers = [];
83614
83615                    this.clearElement(cell);
83616                    cell.className = this.Style.CSS_CELL;
83617                    cell.id = this.id + cellPrefix + i;
83618                    this.logger.log("Rendering cell " + cell.id + " (" + workingDate.getFullYear() + yearOffset + "-" + (workingDate.getMonth()+1) + "-" + workingDate.getDate() + ")", "cellrender");
83619
83620                    if (workingDate.getDate()  == todayDate && 
83621                        workingDate.getMonth()  == todayMonth &&
83622                        workingDate.getFullYear() == todayYear) {
83623                        cellRenderers[cellRenderers.length]=cal.renderCellStyleToday;
83624                    }
83625
83626                    var workingArray = [workingDate.getFullYear(),workingDate.getMonth()+1,workingDate.getDate()];
83627                    this.cellDates[this.cellDates.length] = workingArray; // Add this date to cellDates
83628
83629                    // Local OOM check for performance, since we already have pagedate
83630                    oom = workingDate.getMonth() != useDate.getMonth(); 
83631                    if (oom && !oomSelect) {
83632                        cellRenderers[cellRenderers.length]=cal.renderCellNotThisMonth;
83633                    } else {
83634                        Dom.addClass(cell, workingDayPrefix + workingDate.getDay());
83635                        Dom.addClass(cell, dayPrefix + workingDate.getDate());
83636
83637                        // Concat, so that we're not splicing from an array 
83638                        // which we're also iterating
83639                        var rs = this.renderStack.concat();
83640
83641                        for (var s=0, l = rs.length; s < l; ++s) {
83642
83643                            renderer = null;
83644
83645                            var rArray = rs[s],
83646                                type = rArray[0],
83647                                month,
83648                                day,
83649                                year;
83650
83651                            switch (type) {
83652                                case Calendar.DATE:
83653                                    month = rArray[1][1];
83654                                    day = rArray[1][2];
83655                                    year = rArray[1][0];
83656
83657                                    if (workingDate.getMonth()+1 == month && workingDate.getDate() == day && workingDate.getFullYear() == year) {
83658                                        renderer = rArray[2];
83659                                        this.renderStack.splice(s,1);
83660                                    }
83661
83662                                    break;
83663                                case Calendar.MONTH_DAY:
83664                                    month = rArray[1][0];
83665                                    day = rArray[1][1];
83666
83667                                    if (workingDate.getMonth()+1 == month && workingDate.getDate() == day) {
83668                                        renderer = rArray[2];
83669                                        this.renderStack.splice(s,1);
83670                                    }
83671                                    break;
83672                                case Calendar.RANGE:
83673                                    var date1 = rArray[1][0],
83674                                        date2 = rArray[1][1],
83675                                        d1month = date1[1],
83676                                        d1day = date1[2],
83677                                        d1year = date1[0],
83678                                        d1 = DateMath.getDate(d1year, d1month-1, d1day),
83679                                        d2month = date2[1],
83680                                        d2day = date2[2],
83681                                        d2year = date2[0],
83682                                        d2 = DateMath.getDate(d2year, d2month-1, d2day);
83683
83684                                    if (workingDate.getTime() >= d1.getTime() && workingDate.getTime() <= d2.getTime()) {
83685                                        renderer = rArray[2];
83686
83687                                        if (workingDate.getTime()==d2.getTime()) { 
83688                                            this.renderStack.splice(s,1);
83689                                        }
83690                                    }
83691                                    break;
83692                                case Calendar.WEEKDAY:
83693                                    var weekday = rArray[1][0];
83694                                    if (workingDate.getDay()+1 == weekday) {
83695                                        renderer = rArray[2];
83696                                    }
83697                                    break;
83698                                case Calendar.MONTH:
83699                                    month = rArray[1][0];
83700                                    if (workingDate.getMonth()+1 == month) {
83701                                        renderer = rArray[2];
83702                                    }
83703                                    break;
83704                            }
83705
83706                            if (renderer) {
83707                                cellRenderers[cellRenderers.length]=renderer;
83708                            }
83709                        }
83710
83711                    }
83712
83713                    if (this._indexOfSelectedFieldArray(workingArray) > -1) {
83714                        cellRenderers[cellRenderers.length]=cal.renderCellStyleSelected; 
83715                    }
83716
83717                    if (oom) {
83718                        cellRenderers[cellRenderers.length] = cal.styleCellNotThisMonth; 
83719                    }
83720
83721                    if ((mindate && (workingDate.getTime() < mindate.getTime())) || (maxdate && (workingDate.getTime() > maxdate.getTime()))) {
83722                        cellRenderers[cellRenderers.length] = cal.renderOutOfBoundsDate;
83723                    } else {
83724                        cellRenderers[cellRenderers.length] = cal.styleCellDefault;
83725                        cellRenderers[cellRenderers.length] = cal.renderCellDefault;
83726                    }
83727
83728                    for (var x=0; x < cellRenderers.length; ++x) {
83729                        this.logger.log("renderer[" + x + "] for (" + workingDate.getFullYear() + yearOffset + "-" + (workingDate.getMonth()+1) + "-" + workingDate.getDate() + ")", "cellrender");
83730                        if (cellRenderers[x].call(cal, workingDate, cell) == Calendar.STOP_RENDER) {
83731                            break;
83732                        }
83733                    }
83734
83735                    workingDate.setTime(workingDate.getTime() + DateMath.ONE_DAY_MS);
83736                    // Just in case we crossed DST/Summertime boundaries
83737                    workingDate = DateMath.clearTime(workingDate);
83738
83739                    if (i >= 0 && i <= 6) {
83740                        Dom.addClass(cell, this.Style.CSS_CELL_TOP);
83741                    }
83742                    if ((i % 7) === 0) {
83743                        Dom.addClass(cell, this.Style.CSS_CELL_LEFT);
83744                    }
83745                    if (((i+1) % 7) === 0) {
83746                        Dom.addClass(cell, this.Style.CSS_CELL_RIGHT);
83747                    }
83748
83749                    var postDays = this.postMonthDays; 
83750                    if (hideBlankWeeks && postDays >= 7) {
83751                        var blankWeeks = Math.floor(postDays/7);
83752                        for (var p=0;p<blankWeeks;++p) {
83753                            postDays -= 7;
83754                        }
83755                    }
83756                    
83757                    if (i >= ((this.preMonthDays+postDays+this.monthDays)-7)) {
83758                        Dom.addClass(cell, this.Style.CSS_CELL_BOTTOM);
83759                    }
83760    
83761                    html[html.length] = tempDiv.innerHTML;
83762                    i++;
83763                }
83764    
83765                if (showWeekFooter) { html = this.renderRowFooter(weekNum, html); }
83766    
83767                html[html.length] = '</tr>';
83768            }
83769        }
83770    
83771        html[html.length] = '</tbody>';
83772    
83773        return html;
83774    },
83775    
83776    /**
83777    * Renders the calendar footer. In the default implementation, there is no footer. NOTE: The contents of the array passed into this method are added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.
83778    * @method renderFooter
83779    * @param {HTML[]} html The current working HTML array
83780    * @return {HTML[]} The current working HTML array
83781    */
83782    renderFooter : function(html) { return html; },
83783    
83784    /**
83785    * Renders the calendar after it has been configured. The render() method has a specific call chain that will execute
83786    * when the method is called: renderHeader, renderBody, renderFooter.
83787    * Refer to the documentation for those methods for information on individual render tasks.
83788    * @method render
83789    */
83790    render : function() {
83791        this.beforeRenderEvent.fire();
83792
83793        // Find starting day of the current month
83794        var workingDate = DateMath.findMonthStart(this.cfg.getProperty(DEF_CFG.PAGEDATE.key));
83795
83796        this.resetRenderers();
83797        this.cellDates.length = 0;
83798
83799        Event.purgeElement(this.oDomContainer, true);
83800
83801        var html = [], 
83802            table;
83803
83804        html[html.length] = '<table cellSpacing="0" class="' + this.Style.CSS_CALENDAR + ' y' + (workingDate.getFullYear() + this.Locale.YEAR_OFFSET) +'" id="' + this.id + '">';
83805        html = this.renderHeader(html);
83806        html = this.renderBody(workingDate, html);
83807        html = this.renderFooter(html);
83808        html[html.length] = '</table>';
83809
83810        this.oDomContainer.innerHTML = html.join("\n");
83811
83812        this.applyListeners();
83813
83814        // Using oDomContainer.ownerDocument, to allow for cross-frame rendering
83815        table = ((this._oDoc) && this._oDoc.getElementById(this.id)) || (this.id
83815);
83816
83817        this.cells = Dom.getElementsByClassName(this.Style.CSS_CELL, "td", table);
83818
83819        this.cfg.refireEvent(DEF_CFG.TITLE.key);
83820        this.cfg.refireEvent(DEF_CFG.CLOSE.key);
83821        this.cfg.refireEvent(DEF_CFG.IFRAME.key);
83822
83823        this.renderEvent.fire();
83824    },
83825
83826    /**
83827    * Applies the Calendar's DOM listeners to applicable elements.
83828    * @method applyListeners
83829    */
83830    applyListeners : function() {
83831        var root = this.oDomContainer,
83832            cal = this.parent || this,
83833            anchor = "a",
83834            click = "click";
83835
83836        var linkLeft = Dom.getElementsByClassName(this.Style.CSS_NAV_LEFT, anchor, root),
83837            linkRight = Dom.getElementsByClassName(this.Style.CSS_NAV_RIGHT, anchor, root);
83838
83839        if (linkLeft && linkLeft.length > 0) {
83840            this.linkLeft = linkLeft[0];
83841            Event.addListener(this.linkLeft, click, this.doPreviousMonthNav, cal, true);
83842        }
83843
83844        if (linkRight && linkRight.length > 0) {
83845            this.linkRight = linkRight[0];
83846            Event.addListener(this.linkRight, click, this.doNextMonthNav, cal, true);
83847        }
83848
83849        if (cal.cfg.getProperty("navigator") !== null) {
83850            this.applyNavListeners();
83851        }
83852
83853        if (this.domEventMap) {
83854            var el,elements;
83855            for (var cls in this.domEventMap) { 
83856                if (Lang.hasOwnProperty(this.domEventMap, cls)) {
83857                    var items = this.domEventMap[cls];
83858    
83859                    if (! (items instanceof Array)) {
83860                        items = [items];
83861                    }
83862    
83863                    for (var i=0;i<items.length;i++) {
83864                        var item = items[i];
83865                        elements = Dom.getElementsByClassName(cls, item.tag, this.oDomContainer);
83866    
83867                        for (var c=0;c<elements.length;c++) {
83868                            el = elements[c];
83869                             Event.addListener(el, item.event, item.handler, item.scope, item.correct );
83870                        }
83871                    }
83872                }
83873            }
83874        }
83875
83876        Event.addListener(this.oDomContainer, "click", this.doSelectCell, this);
83877        Event.addListener(this.oDomContainer, "mouseover", this.doCellMouseOver, this);
83878        Event.addListener(this.oDomContainer, "mouseout", this.doCellMouseOut, this);
83879    },
83880
83881    /**
83882    * Applies the DOM listeners to activate the Calendar Navigator.
83883    * @method applyNavListeners
83884    */
83885    applyNavListeners : function() {
83886        var calParent = this.parent || this,
83887            cal = this,
83888            navBtns = Dom.getElementsByClassName(this.Style.CSS_NAV, "a", this.oDomContainer);
83889
83890        if (navBtns.length > 0) {
83891
83892            Event.addListener(navBtns, "click", function (e, obj) {
83893                var target = Event.getTarget(e);
83894                // this == navBtn
83895                if (this === target || Dom.isAncestor(this, target)) {
83896                    Event.preventDefault(e);
83897                }
83898                var navigator = calParent.oNavigator;
83899                if (navigator) {
83900                    var pgdate = cal.cfg.getProperty("pagedate");
83901                    navigator.setYear(pgdate.getFullYear() + cal.Locale.YEAR_OFFSET);
83902                    navigator.setMonth(pgdate.getMonth());
83903                    navigator.show();
83904                }
83905            });
83906        }
83907    },
83908
83909    /**
83910    * Retrieves the Date object for the specified Calendar cell
83911    * @method getDateByCellId
83912    * @param {String} id The id of the cell
83913    * @return {Date} The Date object for the specified Calendar cell
83914    */
83915    getDateByCellId : function(id) {
83916        var date = this.getDateFieldsByCellId(id);
83917        return (date) ? DateMath.getDate(date[0],date[1]-1,date[2]) : null;
83918    },
83919    
83920    /**
83921    * Retrieves the Date object for the specified Calendar cell
83922    * @method getDateFieldsByCellId
83923    * @param {String} id The id of the cell
83924    * @return {Array} The array of Date fields for the specified Calendar cell
83925    */
83926    getDateFieldsByCellId : function(id) {
83927        id = this.getIndexFromId(id);
83928        return (id > -1) ? this.cellDates[id] : null;
83929    },
83930
83931    /**
83932     * Find the Calendar's cell index for a given date.
83933     * If the date is not found, the method returns -1.
83934     * <p>
83935     * The returned index can be used to lookup the cell HTMLElement  
83936     * using the Calendar's cells array or passed to selectCell to select 
83937     * cells by index. 
83938     * </p>
83939     *
83940     * See <a href="#cells">cells</a>, <a href="#selectCell">selectCell</a>.
83941     *
83942     * @method getCellIndex
83943     * @param {Date} date JavaScript Date object, for which to find a cell index.
83944     * @return {Number} The index of the date in Calendars cellDates/cells arrays, or -1 if the date 
83945     * is not on the curently rendered Calendar page.
83946     */
83947    getCellIndex : function(date) {
83948        var idx = -1;
83949        if (date) {
83950            var m = date.getMonth(),
83951                y = date.getFullYear(),
83952                d = date.getDate(),
83953                dates = this.cellDates;
83954
83955            for (var i = 0; i < dates.length; ++i) {
83956                var cellDate = dates[i];
83957                if (cellDate[0] === y && cellDate[1] === m+1 && cellDate[2] === d) {
83958                    idx = i;
83959                    break;
83960                }
83961            }
83962        }
83963        return idx;
83964    },
83965
83966    /**
83967     * Given the id used to mark each Calendar cell, this method
83968     * extracts the index number from the id.
83969     * 
83970     * @param {String} strId The cell id
83971     * @return {Number} The index of the cell, or -1 if id does not contain an index number
83972     */
83973    getIndexFromId : function(strId) {
83974        var idx = -1,
83975            li = strId.lastIndexOf("_cell");
83976
83977        if (li > -1) {
83978            idx = parseInt(strId.substring(li + 5), 10);
83979        }
83980
83981        return idx;
83982    },
83983    
83984    // BEGIN BUILT-IN TABLE CELL RENDERERS
83985    
83986    /**
83987    * Renders a cell that falls before the minimum date or after the maximum date.
83988    * @method renderOutOfBoundsDate
83989    * @param {Date}     workingDate  The current working Date object being used to generate the calendar
83990    * @param {HTMLTableCellElement} cell   The current working cell in the calendar
83991    * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
83992    *   should not be terminated
83993    */
83994    renderOutOfBoundsDate : function(workingDate, cell) {
83995        Dom.addClass(cell, this.Style.CSS_CELL_OOB);
83996        cell.innerHTML = workingDate.getDate();
83997        return Calendar.STOP_RENDER;
83998    },
83999
84000    /**
84001    * Renders the row header HTML for a week.
84002    *
84003    * @method renderRowHeader
84004    * @param {Number} weekNum The week number of the current row
84005    * @param {HTML[]} cell The current working HTML array
84006    */
84007    renderRowHeader : function(weekNum, html) {
84008        html[html.length] = '<th class="' + this.Style.CSS_ROW_HEADER + '">' + weekNum + '</th>';
84009        return html;
84010    },
84011
84012    /**
84013    * Renders the row footer HTML for a week.
84014    *
84015    * @method renderRowFooter
84016    * @param {Number} weekNum The week number of the current row
84017    * @param {HTML[]} cell The current working HTML array
84018    */
84019    renderRowFooter : function(weekNum, html) {
84020        html[html.length] = '<th class="' + this.Style.CSS_ROW_FOOTER + '">' + weekNum + '</th>';
84021        return html;
84022    },
84023
84024    /**
84025    * Renders a single standard calendar cell in the calendar widget table.
84026    *
84027    * All logic for determining how a standard default cell will be rendered is 
84028    * encapsulated in this method, and must be accounted for when extending the
84029    * widget class.
84030    *
84031    * @method renderCellDefault
84032    * @param {Date}     workingDate  The current working Date object being used to generate the calendar
84033    * @param {HTMLTableCellElement} cell   The current working cell in the calendar
84034    */
84035    renderCellDefault : function(workingDate, cell) {
84036        cell.innerHTML = '<a href="#" class="' + this.Style.CSS_CELL_SELECTOR + '">' + this.buildDayLabel(workingDate) + "</a>";
84037    },
84038    
84039    /**
84040    * Styles a selectable cell.
84041    * @method styleCellDefault
84042    * @param {Date}     workingDate  The current working Date object being used to generate the calendar
84043    * @param {HTMLTableCellElement} cell   The current working cell in the calendar
84044    */
84045    styleCellDefault : function(workingDate, cell) {
84046        Dom.addClass(cell, this.Style.CSS_CELL_SELECTABLE);
84047    },
84048    
84049    
84050    /**
84051    * Renders a single standard calendar cell using the CSS hightlight1 style
84052    * @method renderCellStyleHighlight1
84053    * @param {Date}     workingDate  The current working Date object being used to generate the calendar
84054    * @param {HTMLTableCellElement} cell   The current working cell in the calendar
84055    */
84056    renderCellStyleHighlight1 : function(workingDate, cell) {
84057        Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT1);
84058    },
84059    
84060    /**
84061    * Renders a single standard calendar cell using the CSS hightlight2 style
84062    * @method renderCellStyleHighlight2
84063    * @param {Date}     workingDate  The current working Date object being used to generate the calendar
84064    * @param {HTMLTableCellElement} cell   The current working cell in the calendar
84065    */
84066    renderCellStyleHighlight2 : function(workingDate, cell) {
84067        Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT2);
84068    },
84069    
84070    /**
84071    * Renders a single standard calendar cell using the CSS hightlight3 style
84072    * @method renderCellStyleHighlight3
84073    * @param {Date}     workingDate  The current working Date object being used to generate the calendar
84074    * @param {HTMLTableCellElement} cell   The current working cell in the calendar
84075    */
84076    renderCellStyleHighlight3 : function(workingDate, cell) {
84077        Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT3);
84078    },
84079    
84080    /**
84081    * Renders a single standard calendar cell using the CSS hightlight4 style
84082    * @method renderCellStyleHighlight4
84083    * @param {Date}     workingDate  The current working Date object being used to generate the calendar
84084    * @param {HTMLTableCellElement} cell   The current working cell in the calendar
84085    */
84086    renderCellStyleHighlight4 : function(workingDate, cell) {
84087        Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT4);
84088    },
84089    
84090    /**
84091    * Applies the default style used for rendering today's date to the current calendar cell
84092    * @method renderCellStyleToday
84093    * @param {Date}     workingDate  The current working Date object being used to generate the calendar
84094    * @param {HTMLTableCellElement} cell   The current working cell in the calendar
84095    */
84096    renderCellStyleToday : function(workingDate, cell) {
84097        Dom.addClass(cell, this.Style.CSS_CELL_TODAY);
84098    },
84099
84100    /**
84101    * Applies the default style used for rendering selected dates to the current calendar cell
84102    * @method renderCellStyleSelected
84103    * @param {Date}     workingDate  The current working Date object being used to generate the calendar
84104    * @param {HTMLTableCellElement} cell   The current working cell in the calendar
84105    * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
84106    *   should not be terminated
84107    */
84108    renderCellStyleSelected : function(workingDate, cell) {
84109        Dom.addClass(cell, this.Style.CSS_CELL_SELECTED);
84110    },
84111
84112    /**
84113    * Applies the default style used for rendering dates that are not a part of the current
84114    * month (preceding or trailing the cells for the current month)
84115    *
84116    * @method renderCellNotThisMonth
84117    * @param {Date}     workingDate  The current working Date object being used to generate the calendar
84118    * @param {HTMLTableCellElement} cell   The current working cell in the calendar
84119    * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
84120    *   should not be terminated
84121    */
84122    renderCellNotThisMonth : function(workingDate, cell) {
84123        this.styleCellNotThisMonth(workingDate, cell);
84124        cell.innerHTML=workingDate.getDate();
84125        return Calendar.STOP_RENDER;
84126    },
84127
84128    /** Applies the style used for rendering out-of-month dates to the current calendar cell
84129    * @method styleCellNotThisMonth
84130    * @param {Date}                 workingDate     The current working Date object being used to generate the calendar
84131    * @param {HTMLTableCellElement} cell            The current working cell in the calendar
84132    */
84133    styleCellNotThisMonth : function(workingDate, cell) {
84134        YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_OOM);
84135    },
84136
84137    /**
84138    * Renders the current calendar cell as a non-selectable "black-out" date using the default
84139    * restricted style.
84140    * @method renderBodyCellRestricted
84141    * @param {Date}     workingDate  The current working Date object being used to generate the calendar
84142    * @param {HTMLTableCellElement} cell   The current working cell in the calendar
84143    * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
84144    *   should not be terminated
84145    */
84146    renderBodyCellRestricted : function(workingDate, cell) {
84147        Dom.addClass(cell, this.Style.CSS_CELL);
84148        Dom.addClass(cell, this.Style.CSS_CELL_RESTRICTED);
84149        cell.innerHTML=workingDate.getDate();
84150        return Calendar.STOP_RENDER;
84151    },
84152    
84153    // END BUILT-IN TABLE CELL RENDERERS
84154    
84155    // BEGIN MONTH NAVIGATION METHODS
84156
84157    /**
84158    * Adds the designated number of months to the current calendar month, and sets the current
84159    * calendar page date to the new month.
84160    * @method addMonths
84161    * @param {Number} count The number of months to add to the current calendar
84162    */
84163    addMonths : function(count) {
84164        var cfgPageDate = DEF_CFG.PAGEDATE.key,
84165
84166        prevDate = this.cfg.getProperty(cfgPageDate),
84167        newDate = DateMath.add(prevDate, DateMath.MONTH, count);
84168
84169        this.cfg.setProperty(cfgPageDate, newDate);
84170        this.resetRenderers();
84171        this.changePageEvent.fire(prevDate, newDate);
84172    },
84173
84174    /**
84175    * Subtracts the designated number of months from the current calendar month, and sets the current
84176    * calendar page date to the new month.
84177    * @method subtractMonths
84178    * @param {Number} count The number of months to subtract from the current calendar
84179    */
84180    subtractMonths : function(count) {
84181        this.addMonths(-1*count);
84182    },
84183
84184    /**
84185    * Adds the designated number of years to the current calendar, and sets the current
84186    * calendar page date to the new month.
84187    * @method addYears
84188    * @param {Number} count The number of years to add to the current calendar
84189    */
84190    addYears : function(count) {
84191        var cfgPageDate = DEF_CFG.PAGEDATE.key,
84192
84193        prevDate = this.cfg.getProperty(cfgPageDate),
84194        newDate = DateMath.add(prevDate, DateMath.YEAR, count);
84195
84196        this.cfg.setProperty(cfgPageDate, newDate);
84197        this.resetRenderers();
84198        this.changePageEvent.fire(prevDate, newDate);
84199    },
84200
84201    /**
84202    * Subtcats the designated number of years from the current calendar, and sets the current
84203    * calendar page date to the new month.
84204    * @method subtractYears
84205    * @param {Number} count The number of years to subtract from the current calendar
84206    */
84207    subtractYears : function(count) {
84208        this.addYears(-1*count);
84209    },
84210
84211    /**
84212    * Navigates to the next month page in the calendar widget.
84213    * @method nextMonth
84214    */
84215    nextMonth : function() {
84216        this.addMonths(1);
84217    },
84218    
84219    /**
84220    * Navigates to the previous month page in the calendar widget.
84221    * @method previousMonth
84222    */
84223    previousMonth : function() {
84224        this.addMonths(-1);
84225    },
84226    
84227    /**
84228    * Navigates to the next year in the currently selected month in the calendar widget.
84229    * @method nextYear
84230    */
84231    nextYear : function() {
84232        this.addYears(1);
84233    },
84234    
84235    /**
84236    * Navigates to the previous year in the currently selected month in the calendar widget.
84237    * @method previousYear
84238    */
84239    previousYear : function() {
84240        this.addYears(-1);
84241    },
84242
84243    // END MONTH NAVIGATION METHODS
84244    
84245    // BEGIN SELECTION METHODS
84246    
84247    /**
84248    * Resets the calendar widget to the originally selected month and year, and 
84249    * sets the calendar to the initial selection(s).
84250    * @method reset
84251    */
84252    reset : function() {
84253        this.cfg.resetProperty(DEF_CFG.SELECTED.key);
84254        this.cfg.resetProperty(DEF_CFG.PAGEDATE.key);
84255        this.resetEvent.fire();
84256    },
84257    
84258    /**
84259    * Clears the selected dates in the current calendar widget and sets the calendar
84260    * to the current month and year.
84261    * @method clear
84262    */
84263    clear : function() {
84264        this.cfg.setProperty(DEF_CFG.SELECTED.key, []);
84265        this.cfg.setProperty(DEF_CFG.PAGEDATE.key, new Date(this.today.getTime()));
84266        this.clearEvent.fire();
84267    },
84268    
84269    /**
84270    * Selects a date or a collection of dates on the current calendar. This method, by default,
84271    * does not call the render method explicitly. Once selection has completed, render must be 
84272    * called for the changes to be reflected visually.
84273    *
84274    * Any dates which are OOB (out of bounds, not selectable) will not be selected and the array of 
84275    * selected dates passed to the selectEvent will not contain OOB dates.
84276    * 
84277    * If all dates are OOB, the no state change will occur; beforeSelect and select events will not be fired.
84278    *
84279    * @method select
84280    * @param {String/Date/Date[]} date The date string of dates to select in the current calendar. Valid formats are
84281    *        individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
84282    *        Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
84283    *        This method can also take a JavaScript Date object or an array of Date objects.
84284    * @return {Date[]}   Array of JavaScript Date objects representing all individual dates that are currently selected.
84285    */
84286    select : function(date) {
84287        this.logger.log("Select: " + date, "info");
84288
84289        var aToBeSelected = this._toFieldArray(date),
84290            validDates = [],
84291            selected = [],
84292            cfgSelected = DEF_CFG.SELECTED.key;
84293
84294        this.logger.log("Selection field array: " + aToBeSelected, "info");
84295        
84296        for (var a=0; a < aToBeSelected.length; ++a) {
84297            var toSelect = aToBeSelected[a];
84298
84299            if (!this.isDateOOB(this._toDate(toSelect))) {
84300
84301                if (validDates.length === 0) {
84302                    this.beforeSelectEvent.fire();
84303                    selected = this.cfg.getProperty(cfgSelected);
84304                }
84305                validDates.push(toSelect);
84306
84307                if (this._indexOfSelectedFieldArray(toSelect) == -1) { 
84308                    selected[selected.length] = toSelect;
84309                }
84310            }
84311        }
84312
84313        if (validDates.length === 0) { this.logger.log("All provided dates were OOB. beforeSelect and select events not fired", "info"); }
84314
84315        if (validDates.length > 0) {
84316            if (this.parent) {
84317                this.parent.cfg.setProperty(cfgSelected, selected);
84318            } else {
84319                this.cfg.setProperty(cfgSelected, selected);
84320            }
84321            this.selectEvent.fire(validDates);
84322        }
84323
84324        return this.getSelectedDates();
84325    },
84326    
84327    /**
84328    * Selects a date on the current calendar by referencing the index of the cell that should be selected.
84329    * This method is used to easily select a single cell (usually with a mouse click) without having to do
84330    * a full render. The selected style is applied to the cell directly.
84331    *
84332    * If the cell is not marked with the CSS_CELL_SELECTABLE class (as is the case by default for out of month 
84333    * or out of bounds cells), it will not be selected and in such a case beforeSelect and select events will not be fired.
84334    * 
84335    * @method selectCell
84336    * @param {Number} cellIndex The index of the cell to select in the current calendar. 
84337    * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
84338    */
84339    selectCell : function(cellIndex) {
84340
84341        var cell = this.cells[cellIndex],
84342            cellDate = this.cellDates[cellIndex],
84343            dCellDate = this._toDate(cellDate),
84344            selectable = Dom.hasClass(cell, this.Style.CSS_CELL_SELECTABLE);
84345
84346        this.logger.log("Select: " + dCellDate, "info");
84347        if (!selectable) {this.logger.log("The cell at cellIndex:" + cellIndex + " is not a selectable cell. beforeSelect, select events not fired", "info"); }
84348
84349        if (selectable) {
84350    
84351            this.beforeSelectEvent.fire();
84352    
84353            var cfgSelected = DEF_CFG.SELECTED.key;
84354            var selected = this.cfg.getProperty(cfgSelected);
84355    
84356            var selectDate = cellDate.concat();
84357    
84358            if (this._indexOfSelectedFieldArray(selectDate) == -1) {
84359                selected[selected.length] = selectDate;
84360            }
84361            if (this.parent) {
84362                this.parent.cfg.setProperty(cfgSelected, selected);
84363            } else {
84364                this.cfg.setProperty(cfgSelected, selected);
84365            }
84366            this.renderCellStyleSelected(dCellDate,cell);
84367            this.selectEvent.fire([selectDate]);
84368    
84369            this.doCellMouseOut.call(cell, null, this);  
84370        }
84371    
84372        return this.getSelectedDates();
84373    },
84374    
84375    /**
84376    * Deselects a date or a collection of dates on the current calendar. This method, by default,
84377    * does not call the render method explicitly. Once deselection has completed, render must be 
84378    * called for the changes to be reflected visually.
84379    * 
84380    * The method will not attempt to deselect any dates which are OOB (out of bounds, and hence not selectable) 
84381    * and the array of deselected dates passed to the deselectEvent will not contain any OOB dates.
84382    * 
84383    * If all dates are OOB, beforeDeselect and deselect events will not be fired.
84384    * 
84385    * @method deselect
84386    * @param {String/Date/Date[]} date The date string of dates to deselect in the current calendar. Valid formats are
84387    *        individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
84388    *        Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
84389    *        This method can also take a JavaScript Date object or an array of Date objects. 
84390    * @return {Date[]}   Array of JavaScript Date objects representing all individual dates that are currently selected.
84391    */
84392    deselect : function(date) {
84393        this.logger.log("Deselect: " + date, "info");
84394
84395        var aToBeDeselected = this._toFieldArray(date),
84396            validDates = [],
84397            selected = [],
84398            cfgSelected = DEF_CFG.SELECTED.key;
84399
84400        this.logger.log("Deselection field array: " + aToBeDeselected, "info");
84401
84402        for (var a=0; a < aToBeDeselected.length; ++a) {
84403            var toDeselect = aToBeDeselected[a];
84404    
84405            if (!this.isDateOOB(this._toDate(toDeselect))) {
84406    
84407                if (validDates.length === 0) {
84408                    this.beforeDeselectEvent.fire();
84409                    selected = this.cfg.getProperty(cfgSelected);
84410                }
84411    
84412                validDates.push(toDeselect);
84413    
84414                var index = this._indexOfSelectedFieldArray(toDeselect);
84415                if (index != -1) { 
84416                    selected.splice(index,1);
84417                }
84418            }
84419        }
84420    
84421        if (validDates.length === 0) { this.logger.log("All provided dates were OOB. beforeDeselect and deselect events not fired");}
84422    
84423        if (validDates.length > 0) {
84424            if (this.parent) {
84425                this.parent.cfg.setProperty(cfgSelected, selected);
84426            } else {
84427                this.cfg.setProperty(cfgSelected, selected);
84428            }
84429            this.deselectEvent.fire(validDates);
84430        }
84431    
84432        return this.getSelectedDates();
84433    },
84434    
84435    /**
84436    * Deselects a date on the current calendar by referencing the index of the cell that should be deselected.
84437    * This method is used to easily deselect a single cell (usually with a mouse click) without having to do
84438    * a full render. The selected style is removed from the cell directly.
84439    * 
84440    * If the cell is not marked with the CSS_CELL_SELECTABLE class (as is the case by default for out of month 
84441    * or out of bounds cells), the method will not attempt to deselect it and in such a case, beforeDeselect and 
84442    * deselect events will not be fired.
84443    * 
84444    * @method deselectCell
84445    * @param {Number} cellIndex The index of the cell to deselect in the current calendar. 
84446    * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
84447    */
84448    deselectCell : function(cellIndex) {
84449        var cell = this.cells[cellIndex],
84450            cellDate = this.cellDates[cellIndex],
84451            cellDateIndex = this._indexOfSelectedFieldArray(cellDate);
84452
84453        var selectable = Dom.hasClass(cell, this.Style.CSS_CELL_SELECTABLE);
84454        if (!selectable) { this.logger.log("The cell at cellIndex:" + cellIndex + " is not a selectable/deselectable cell", "info"); }
84455
84456        if (selectable) {
84457
84458            this.beforeDeselectEvent.fire();
84459
84460            var selected = this.cfg.getProperty(DEF_CFG.SELECTED.key),
84461                dCellDate = this._toDate(cellDate),
84462                selectDate = cellDate.concat();
84463
84464            if (cellDateIndex > -1) {
84465                if ((this.cfg.getProperty(DEF_CFG.PAGEDATE.key).getMonth() == dCellDate.getMonth() &&
84466                    this.cfg.getProperty(DEF_CFG.PAGEDATE.key).getFullYear() == dCellDate.getFullYear()) || this.cfg.getProperty(DEF_CFG.OOM_SELECT.key)) {
84467                    Dom.removeClass(cell, this.Style.CSS_CELL_SELECTED);
84468                }
84469                selected.splice(cellDateIndex, 1);
84470            }
84471
84472            if (this.parent) {
84473                this.parent.cfg.setProperty(DEF_CFG.SELECTED.key, selected);
84474            } else {
84475                this.cfg.setProperty(DEF_CFG.SELECTED.key, selected);
84476            }
84477
84478            this.deselectEvent.fire([selectDate]);
84479        }
84480
84481        return this.getSelectedDates();
84482    },
84483
84484    /**
84485    * Deselects all dates on the current calendar.
84486    * @method deselectAll
84487    * @return {Date[]}  Array of JavaScript Date objects representing all individual dates that are currently selected.
84488    *      Assuming that this function executes properly, the return value should be an empty array.
84489    *      However, the empty array is returned for the sake of being able to check the selection status
84490    *      of the calendar.
84491    */
84492    deselectAll : function() {
84493        this.beforeDeselectEvent.fire();
84494        
84495        var cfgSelected = DEF_CFG.SELECTED.key,
84496            selected = this.cfg.getProperty(cfgSelected),
84497            count = selected.length,
84498            sel = selected.concat();
84499
84500        if (this.parent) {
84501            this.parent.cfg.setProperty(cfgSelected, []);
84502        } else {
84503            this.cfg.setProperty(cfgSelected, []);
84504        }
84505        
84506        if (count > 0) {
84507            this.deselectEvent.fire(sel);
84508        }
84509    
84510        return this.getSelectedDates();
84511    },
84512    
84513    // END SELECTION METHODS
84514    
84515    // BEGIN TYPE CONVERSION METHODS
84516    
84517    /**
84518    * Converts a date (either a JavaScript Date object, or a date string) to the internal data structure
84519    * used to represent dates: [[yyyy,mm,dd],[yyyy,mm,dd]].
84520    * @method _toFieldArray
84521    * @private
84522    * @param {String/Date/Date[]} date The date string of dates to deselect in the current calendar. Valid formats are
84523    *        individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
84524    *        Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
84525    *        This method can also take a JavaScript Date object or an array of Date objects. 
84526    * @return {Array[](Number[])} Array of date field arrays
84527    */
84528    _toFieldArray : function(date) {
84529        var returnDate = [];
84530    
84531        if (date instanceof Date) {
84532            returnDate = [[date.getFullYear(), date.getMonth()+1, date.getDate()]];
84533        } else if (Lang.isString(date)) {
84534            returnDate = this._parseDates(date);
84535        } else if (Lang.isArray(date)) {
84536            for (var i=0;i<date.length;++i) {
84537                var d = date[i];
84538                returnDate[returnDate.length] = [d.getFullYear(),d.getMonth()+1,d.getDate()];
84539            }
84540        }
84541        
84542        return returnDate;
84543    },
84544    
84545    /**
84546    * Converts a date field array [yyyy,mm,dd] to a JavaScript Date object. The date field array
84547    * is the format in which dates are as provided as arguments to selectEvent and deselectEvent listeners.
84548    * 
84549    * @method toDate
84550    * @param {Number[]} dateFieldArray The date field array to convert to a JavaScript Date.
84551    * @return {Date} JavaScript Date object representing the date field array.
84552    */
84553    toDate : function(dateFieldArray) {
84554        return this._toDate(dateFieldArray);
84555    },
84556    
84557    /**
84558    * Converts a date field array [yyyy,mm,dd] to a JavaScript Date object.
84559    * @method _toDate
84560    * @private
84561    * @deprecated Made public, toDate 
84562    * @param {Number[]}  dateFieldArray The date field array to convert to a JavaScript Date.
84563    * @return {Date} JavaScript Date object representing the date field array
84564    */
84565    _toDate : function(dateFieldArray) {
84566        if (dateFieldArray instanceof Date) {
84567            return dateFieldArray;
84568        } else {
84569            return DateMath.getDate(dateFieldArray[0],dateFieldArray[1]-1,dateFieldArray[2]);
84570        }
84571    },
84572    
84573    // END TYPE CONVERSION METHODS 
84574    
84575    // BEGIN UTILITY METHODS
84576    
84577    /**
84578    * Determines if 2 field arrays are equal.
84579    * @method _fieldArraysAreEqual
84580    * @private
84581    * @param {Number[]} array1 The first date field array to compare
84582    * @param {Number[]} array2 The first date field array to compare
84583    * @return {Boolean} The boolean that represents the equality of the two arrays
84584    */
84585    _fieldArraysAreEqual : function(array1, array2) {
84586        var match = false;
84587    
84588        if (array1[0]==array2[0]&&array1[1]==array2[1]&&array1[2]==array2[2]) {
84589            match=true; 
84590        }
84591    
84592        return match;
84593    },
84594    
84595    /**
84596    * Gets the index of a date field array [yyyy,mm,dd] in the current list of selected dates.
84597    * @method _indexOfSelectedFieldArray
84598    * @private
84599    * @param {Number[]}  find The date field array to search for
84600    * @return {Number}   The index of the date field array within the collection of selected dates.
84601    *        -1 will be returned if the date is not found.
84602    */
84603    _indexOfSelectedFieldArray : function(find) {
84604        var selected = -1,
84605            seldates = this.cfg.getProperty(DEF_CFG.SELECTED.key);
84606    
84607        for (var s=0;s<seldates.length;++s) {
84608            var sArray = seldates[s];
84609            if (find[0]==sArray[0]&&find[1]==sArray[1]&&find[2]==sArray[2]) {
84610                selected = s;
84611                break;
84612            }
84613        }
84614    
84615        return selected;
84616    },
84617    
84618    /**
84619    * Determines whether a given date is OOM (out of month).
84620    * @method isDateOOM
84621    * @param {Date} date The JavaScript Date object for which to check the OOM status
84622    * @return {Boolean} true if the date is OOM
84623    */
84624    isDateOOM : function(date) {
84625        return (date.getMonth() != this.cfg.getProperty(DEF_CFG.PAGEDATE.key).getMonth());
84626    },
84627    
84628    /**
84629    * Determines whether a given date is OOB (out of bounds - less than the mindate or more than the maxdate).
84630    *
84631    * @method isDateOOB
84632    * @param {Date} date The JavaScript Date object for which to check the OOB status
84633    * @return {Boolean} true if the date is OOB
84634    */
84635    isDateOOB : function(date) {
84636        var minDate = this.cfg.getProperty(DEF_CFG.MINDATE.key),
84637            maxDate = this.cfg.getProperty(DEF_CFG.MAXDATE.key),
84638            dm = DateMath;
84639        
84640        if (minDate) {
84641            minDate = dm.clearTime(minDate);
84642        } 
84643        if (maxDate) {
84644            maxDate = dm.clearTime(maxDate);
84645        }
84646    
84647        var clearedDate = new Date(date.getTime());
84648        clearedDate = dm.clearTime(clearedDate);
84649    
84650        return ((minDate && clearedDate.getTime() < minDate.getTime()) || (maxDate && clearedDate.getTime() > maxDate.getTime()));
84651    },
84652    
84653    /**
84654     * Parses a pagedate configuration property value. The value can either be specified as a string of form "mm/yyyy" or a Date object 
84655     * and is parsed into a Date object normalized to the first day of the month. If no value is passed in, the month and year from today's date are used to create the Date object 
84656     * @method _parsePageDate
84657     * @private
84658     * @param {Date|String} date Pagedate value which needs to be parsed
84659     * @return {Date} The Date object representing the pagedate
84660     */
84661    _parsePageDate : function(date) {
84662        var parsedDate;
84663
84664        if (date) {
84665            if (date instanceof Date) {
84666                parsedDate = DateMath.findMonthStart(date);
84667            } else {
84668                var month, year, aMonthYear;
84669                aMonthYear = date.split(this.cfg.getProperty(DEF_CFG.DATE_FIELD_DELIMITER.key));
84670                month = parseInt(aMonthYear[this.cfg.getProperty(DEF_CFG.MY_MONTH_POSITION.key)-1], 10)-1;
84671                year = parseInt(aMonthYear[this.cfg.getProperty(DEF_CFG.MY_YEAR_POSITION.key)-1], 10) - this.Locale.YEAR_OFFSET;
84672
84673                parsedDate = DateMath.getDate(year, month, 1);
84674            }
84675        } else {
84676            parsedDate = DateMath.getDate(this.today.getFullYear(), this.today.getMonth(), 1);
84677        }
84678        return parsedDate;
84679    },
84680    
84681    // END UTILITY METHODS
84682    
84683    // BEGIN EVENT HANDLERS
84684    
84685    /**
84686    * Event executed before a date is selected in the calendar widget.
84687    * @deprecated Event handlers for this event should be susbcribed to beforeSelectEvent.
84688    */
84689    onBeforeSelect : function() {
84690        if (this.cfg.getProperty(DEF_CFG.MULTI_SELECT.key) === false) {
84691            if (this.parent) {
84692                this.parent.callChildFunction("clearAllBodyCellStyles", this.Style.CSS_CELL_SELECTED);
84693                this.parent.deselectAll();
84694            } else {
84695                this.clearAllBodyCellStyles(this.Style.CSS_CELL_SELECTED);
84696                this.deselectAll();
84697            }
84698        }
84699    },
84700    
84701    /**
84702    * Event executed when a date is selected in the calendar widget.
84703    * @param {Array} selected An array of date field arrays representing which date or dates were selected. Example: [ [2006,8,6],[2006,8,7],[2006,8,8] ]
84704    * @deprecated Event handlers for this event should be susbcribed to selectEvent.
84705    */
84706    onSelect : function(selected) { },
84707    
84708    /**
84709    * Event executed before a date is deselected in the calendar widget.
84710    * @deprecated Event handlers for this event should be susbcribed to beforeDeselectEvent.
84711    */
84712    onBeforeDeselect : function() { },
84713    
84714    /**
84715    * Event executed when a date is deselected in the calendar widget.
84716    * @param {Array} selected An array of date field arrays representing which date or dates were deselected. Example: [ [2006,8,6],[2006,8,7],[2006,8,8] ]
84717    * @deprecated Event handlers for this event should be susbcribed to deselectEvent.
84718    */
84719    onDeselect : function(deselected) { },
84720    
84721    /**
84722    * Event executed when the user navigates to a different calendar page.
84723    * @deprecated Event handlers for this event should be susbcribed to changePageEvent.
84724    */
84725    onChangePage : function() {
84726        this.render();
84727    },
84728
84729    /**
84730    * Event executed when the calendar widget is rendered.
84731    * @deprecated Event handlers for this event should be susbcribed to renderEvent.
84732    */
84733    onRender : function() { },
84734
84735    /**
84736    * Event executed when the calendar widget is reset to its original state.
84737    * @deprecated Event handlers for this event should be susbcribed to resetEvemt.
84738    */
84739    onReset : function() { this.render(); },
84740
84741    /**
84742    * Event executed when the calendar widget is completely cleared to the current month with no selections.
84743    * @deprecated Event handlers for this event should be susbcribed to clearEvent.
84744    */
84745    onClear : function() { this.render(); },
84746    
84747    /**
84748    * Validates the calendar widget. This method has no default implementation
84749    * and must be extended by subclassing the widget.
84750    * @return Should return true if the widget validates, and false if
84751    * it doesn't.
84752    * @type Boolean
84753    */
84754    validate : function() { return true; },
84755    
84756    // END EVENT HANDLERS
84757    
84758    // BEGIN DATE PARSE METHODS
84759    
84760    /**
84761    * Converts a date string to a date field array
84762    * @private
84763    * @param {String} sDate   Date string. Valid formats are mm/dd and mm/dd/yyyy.
84764    * @return    A date field array representing the string passed to the method
84765    * @type Array[](Number[])
84766    */
84767    _parseDate : function(sDate) {
84768        var aDate = sDate.split(this.Locale.DATE_FIELD_DELIMITER),
84769            rArray;
84770
84771        if (aDate.length == 2) {
84772            rArray = [aDate[this.Locale.MD_MONTH_POSITION-1],aDate[this.Locale.MD_DAY_POSITION-1]];
84773            rArray.type = Calendar.MONTH_DAY;
84774        } else {
84775            rArray = [aDate[this.Locale.MDY_YEAR_POSITION-1] - this.Locale.YEAR_OFFSET, aDate[this.Locale.MDY_MONTH_POSITION-1],aDate[this.Locale.MDY_DAY_POSITION-1]];
84776            rArray.type = Calendar.DATE;
84777        }
84778
84779        for (var i=0;i<rArray.length;i++) {
84780            rArray[i] = parseInt(rArray[i], 10);
84781        }
84782    
84783        return rArray;
84784    },
84785    
84786    /**
84787    * Converts a multi or single-date string to an array of date field arrays
84788    * @private
84789    * @param {String} sDates  Date string with one or more comma-delimited dates. Valid formats are mm/dd, mm/dd/yyyy, mm/dd/yyyy-mm/dd/yyyy
84790    * @return       An array of date field arrays
84791    * @type Array[](Number[])
84792    */
84793    _parseDates : function(sDates) {
84794        var aReturn = [],
84795            aDates = sDates.split(this.Locale.DATE_DELIMITER);
84796        
84797        for (var d=0;d<aDates.length;++d) {
84798            var sDate = aDates[d];
84799    
84800            if (sDate.indexOf(this.Locale.DATE_RANGE_DELIMITER) != -1) {
84801                // This is a range
84802                var aRange = sDate.split(this.Locale.DATE_RANGE_DELIMITER),
84803                    dateStart = this._parseDate(aRange[0]),
84804                    dateEnd = this._parseDate(aRange[1]),
84805                    fullRange = this._parseRange(dateStart, dateEnd);
84806
84807                aReturn = aReturn.concat(fullRange);
84808            } else {
84809                // This is not a range
84810                var aDate = this._parseDate(sDate);
84811                aReturn.push(aDate);
84812            }
84813        }
84814        return aReturn;
84815    },
84816    
84817    /**
84818    * Converts a date range to the full list of included dates
84819    * @private
84820    * @param {Number[]} startDate Date field array representing the first date in the range
84821    * @param {Number[]} endDate  Date field array representing the last date in the range
84822    * @return       An array of date field arrays
84823    * @type Array[](Number[])
84824    */
84825    _parseRange : function(startDate, endDate) {
84826        var dCurrent = DateMath.add(DateMath.getDate(startDate[0],startDate[1]-1,startDate[2]),DateMath.DAY,1),
84827            dEnd     = DateMath.getDate(endDate[0],  endDate[1]-1,  endDate[2]),
84828            results = [];
84829
84830        results.push(startDate);
84831        while (dCurrent.getTime() <= dEnd.getTime()) {
84832            results.push([dCurrent.getFullYear(),dCurrent.getMonth()+1,dCurrent.getDate()]);
84833            dCurrent = DateMath.add(dCurrent,DateMath.DAY,1);
84834        }
84835        return results;
84836    },
84837    
84838    // END DATE PARSE METHODS
84839    
84840    // BEGIN RENDERER METHODS
84841    
84842    /**
84843    * Resets the render stack of the current calendar to its original pre-render value.
84844    */
84845    resetRenderers : function() {
84846        this.renderStack = this._renderStack.concat();
84847    },
84848
84849    /**
84850     * Removes all custom renderers added to the Calendar through the addRenderer, addMonthRenderer and 
84851     * addWeekdayRenderer methods. Calendar's render method needs to be called after remov
84851ing renderers 
84852     * to re-render the Calendar without custom renderers applied.
84853     */
84854    removeRenderers : function() {
84855        this._renderStack = [];
84856        this.renderStack = [];
84857    },
84858
84859    /**
84860    * Clears the inner HTML, CSS class and style information from the specified cell.
84861    * @method clearElement
84862    * @param {HTMLTableCellElement} cell The cell to clear
84863    */ 
84864    clearElement : function(cell) {
84865        cell.innerHTML = "&#160;";
84866        cell.className="";
84867    },
84868    
84869    /**
84870    * Adds a renderer to the render stack. The function reference passed to this method will be executed
84871    * when a date cell matches the conditions specified in the date string for this renderer.
84872    * 
84873    * <p>NOTE: The contents of the cell set by the renderer will be added to the DOM as HTML. The custom renderer implementation should 
84874    * escape markup used to set the cell contents, if coming from an external source.<p>
84875    * @method addRenderer
84876    * @param {String} sDates  A date string to associate with the specified renderer. Valid formats
84877    *         include date (12/24/2005), month/day (12/24), and range (12/1/2004-1/1/2005)
84878    * @param {Function} fnRender The function executed to render cells that match the render rules for this renderer.
84879    */
84880    addRenderer : function(sDates, fnRender) {
84881        var aDates = this._parseDates(sDates);
84882        for (var i=0;i<aDates.length;++i) {
84883            var aDate = aDates[i];
84884        
84885            if (aDate.length == 2) { // this is either a range or a month/day combo
84886                if (aDate[0] instanceof Array) { // this is a range
84887                    this._addRenderer(Calendar.RANGE,aDate,fnRender);
84888                } else { // this is a month/day combo
84889                    this._addRenderer(Calendar.MONTH_DAY,aDate,fnRender);
84890                }
84891            } else if (aDate.length == 3) {
84892                this._addRenderer(Calendar.DATE,aDate,fnRender);
84893            }
84894        }
84895    },
84896    
84897    /**
84898    * The private method used for adding cell renderers to the local render stack.
84899    * This method is called by other methods that set the renderer type prior to the method call.
84900    * @method _addRenderer
84901    * @private
84902    * @param {String} type  The type string that indicates the type of date renderer being added.
84903    *         Values are YAHOO.widget.Calendar.DATE, YAHOO.widget.Calendar.MONTH_DAY, YAHOO.widget.Calendar.WEEKDAY,
84904    *         YAHOO.widget.Calendar.RANGE, YAHOO.widget.Calendar.MONTH
84905    * @param {Array}  aDates  An array of dates used to construct the renderer. The format varies based
84906    *         on the renderer type
84907    * @param {Function} fnRender The function executed to render cells that match the render rules for this renderer.
84908    */
84909    _addRenderer : function(type, aDates, fnRender) {
84910        var add = [type,aDates,fnRender];
84911        this.renderStack.unshift(add); 
84912        this._renderStack = this.renderStack.concat();
84913    },
84914
84915    /**
84916    * Adds a month renderer to the render stack. The function reference passed to this method will be executed
84917    * when a date cell matches the month passed to this method
84918    * 
84919    * <p>NOTE: The contents of the cell set by the renderer will be added to the DOM as HTML. The custom renderer implementation should 
84920    * escape markup used to set the cell contents, if coming from an external source.<p>
84921    * @method addMonthRenderer
84922    * @param {Number} month  The month (1-12) to associate with this renderer
84923    * @param {Function} fnRender The function executed to render cells that match the render rules for this renderer.
84924    */
84925    addMonthRenderer : function(month, fnRender) {
84926        this._addRenderer(Calendar.MONTH,[month],fnRender);
84927    },
84928
84929    /**
84930    * Adds a weekday renderer to the render stack. The function reference passed to this method will be executed
84931    * when a date cell matches the weekday passed to this method.
84932    *
84933    * <p>NOTE: The contents of the cell set by the renderer will be added to the DOM as HTML. The custom renderer implementation should 
84934    * escape HTML used to set the cell contents, if coming from an external source.<p>
84935    *
84936    * @method addWeekdayRenderer
84937    * @param {Number} weekday  The weekday (Sunday = 1, Monday = 2 ... Saturday = 7) to associate with this renderer
84938    * @param {Function} fnRender The function executed to render cells that match the render rules for this renderer.
84939    */
84940    addWeekdayRenderer : function(weekday, fnRender) {
84941        this._addRenderer(Calendar.WEEKDAY,[weekday],fnRender);
84942    },
84943
84944    // END RENDERER METHODS
84945    
84946    // BEGIN CSS METHODS
84947    
84948    /**
84949    * Removes all styles from all body cells in the current calendar table.
84950    * @method clearAllBodyCellStyles
84951    * @param {style} style The CSS class name to remove from all calendar body cells
84952    */
84953    clearAllBodyCellStyles : function(style) {
84954        for (var c=0;c<this.cells.length;++c) {
84955            Dom.removeClass(this.cells[c],style);
84956        }
84957    },
84958    
84959    // END CSS METHODS
84960    
84961    // BEGIN GETTER/SETTER METHODS
84962    /**
84963    * Sets the calendar's month explicitly
84964    * @method setMonth
84965    * @param {Number} month  The numeric month, from 0 (January) to 11 (December)
84966    */
84967    setMonth : function(month) {
84968        var cfgPageDate = DEF_CFG.PAGEDATE.key,
84969            current = this.cfg.getProperty(cfgPageDate);
84970        current.setMonth(parseInt(month, 10));
84971        this.cfg.setProperty(cfgPageDate, current);
84972    },
84973
84974    /**
84975    * Sets the calendar's year explicitly.
84976    * @method setYear
84977    * @param {Number} year  The numeric 4-digit year
84978    */
84979    setYear : function(year) {
84980        var cfgPageDate = DEF_CFG.PAGEDATE.key,
84981            current = this.cfg.getProperty(cfgPageDate);
84982
84983        current.setFullYear(parseInt(year, 10) - this.Locale.YEAR_OFFSET);
84984        this.cfg.setProperty(cfgPageDate, current);
84985    },
84986
84987    /**
84988    * Gets the list of currently selected dates from the calendar.
84989    * @method getSelectedDates
84990    * @return {Date[]} An array of currently selected JavaScript Date objects.
84991    */
84992    getSelectedDates : function() {
84993        var returnDates = [],
84994            selected = this.cfg.getProperty(DEF_CFG.SELECTED.key);
84995
84996        for (var d=0;d<selected.length;++d) {
84997            var dateArray = selected[d];
84998
84999            var date = DateMath.getDate(dateArray[0],dateArray[1]-1,dateArray[2]);
85000            returnDates.push(date);
85001        }
85002
85003        returnDates.sort( function(a,b) { return a-b; } );
85004        return returnDates;
85005    },
85006
85007    /// END GETTER/SETTER METHODS ///
85008    
85009    /**
85010    * Hides the Calendar's outer container from view.
85011    * @method hide
85012    */
85013    hide : function() {
85014        if (this.beforeHideEvent.fire()) {
85015            this.oDomContainer.style.display = "none";
85016            this.hideEvent.fire();
85017        }
85018    },
85019
85020    /**
85021    * Shows the Calendar's outer container.
85022    * @method show
85023    */
85024    show : function() {
85025        if (this.beforeShowEvent.fire()) {
85026            this.oDomContainer.style.display = "block";
85027            this.showEvent.fire();
85028        }
85029    },
85030
85031    /**
85032    * Returns a string representing the current browser.
85033    * @deprecated As of 2.3.0, environment information is available in YAHOO.env.ua
85034    * @see YAHOO.env.ua
85035    * @property browser
85036    * @type String
85037    */
85038    browser : (function() {
85039                var ua = navigator.userAgent.toLowerCase();
85040                      if (ua.indexOf('opera')!=-1) { // Opera (check first in case of spoof)
85041                         return 'opera';
85042                      } else if (ua.indexOf('msie 7')!=-1) { // IE7
85043                         return 'ie7';
85044                      } else if (ua.indexOf('msie') !=-1) { // IE
85045                         return 'ie';
85046                      } else if (ua.indexOf('safari')!=-1) { // Safari (check before Gecko because it includes "like Gecko")
85047                         return 'safari';
85048                      } else if (ua.indexOf('gecko') != -1) { // Gecko
85049                         return 'gecko';
85050                      } else {
85051                         return false;
85052                      }
85053                })(),
85054    /**
85055    * Returns a string representation of the object.
85056    * @method toString
85057    * @return {String} A string representation of the Calendar object.
85058    */
85059    toString : function() {
85060        return "Calendar " + this.id;
85061    },
85062
85063    /**
85064     * Destroys the Calendar instance. The method will remove references
85065     * to HTML elements, remove any event listeners added by the Calendar,
85066     * and destroy the Config and CalendarNavigator instances it has created.
85067     *
85068     * @method destroy
85069     */
85070    destroy : function() {
85071
85072        if (this.beforeDestroyEvent.fire()) {
85073            var cal = this;
85074
85075            // Child objects
85076            if (cal.navigator) {
85077                cal.navigator.destroy();
85078            }
85079
85080            if (cal.cfg) {
85081                cal.cfg.destroy();
85082            }
85083
85084            // DOM event listeners
85085            Event.purgeElement(cal.oDomContainer, true);
85086
85087            // Generated markup/DOM - Not removing the container DIV since we didn't create it.
85088            Dom.removeClass(cal.oDomContainer, cal.Style.CSS_WITH_TITLE);
85089            Dom.removeClass(cal.oDomContainer, cal.Style.CSS_CONTAINER);
85090            Dom.removeClass(cal.oDomContainer, cal.Style.CSS_SINGLE);
85091            cal.oDomContainer.innerHTML = "";
85092
85093            // JS-to-DOM references
85094            cal.oDomContainer = null;
85095            cal.cells = null;
85096
85097            this.destroyEvent.fire();
85098        }
85099    }
85100};
85101
85102YAHOO.widget.Calendar = Calendar;
85103
85104/**
85105* @namespace YAHOO.widget
85106* @class Calendar_Core
85107* @extends YAHOO.widget.Calendar
85108* @deprecated The old Calendar_Core class is no longer necessary.
85109*/
85110YAHOO.widget.Calendar_Core = YAHOO.widget.Calendar;
85111
85112YAHOO.widget.Cal_Core = YAHOO.widget.Calendar;
85113
85114})();
85115(function() {
85116
85117    var Dom = YAHOO.util.Dom,
85118        DateMath = YAHOO.widget.DateMath,
85119        Event = YAHOO.util.Event,
85120        Lang = YAHOO.lang,
85121        Calendar = YAHOO.widget.Calendar;
85122
85123/**
85124* YAHOO.widget.CalendarGroup is a special container class for YAHOO.widget.Calendar. This class facilitates
85125* the ability to have multi-page calendar views that share a single dataset and are
85126* dependent on each other.
85127*
85128* The calendar group instance will refer to each of its elements using a 0-based index.
85129* For example, to construct the placeholder for a calendar group widget with id "cal1" and
85130* containerId of "cal1Container", the markup would be as follows:
85131*   <xmp>
85132*       <div id="cal1Container_0"></div>
85133*       <div id="cal1Container_1"></div>
85134*   </xmp>
85135* The tables for the calendars ("cal1_0" and "cal1_1") will be inserted into those containers.
85136*
85137* <p>
85138* <strong>NOTE: As of 2.4.0, the constructor's ID argument is optional.</strong>
85139* The CalendarGroup can be constructed by simply providing a container ID string, 
85140* or a reference to a container DIV HTMLElement (the element needs to exist 
85141* in the document).
85142* 
85143* E.g.:
85144*   <xmp>
85145*       var c = new YAHOO.widget.CalendarGroup("calContainer", configOptions);
85146*   </xmp>
85147* or:
85148*   <xmp>
85149*       var containerDiv = YAHOO.util.Dom.get("calContainer");
85150*       var c = new YAHOO.widget.CalendarGroup(containerDiv, configOptions);
85151*   </xmp>
85152* </p>
85153* <p>
85154* If not provided, the ID will be generated from the container DIV ID by adding an "_t" suffix.
85155* For example if an ID is not provided, and the container's ID is "calContainer", the CalendarGroup's ID will be set to "calContainer_t".
85156* </p>
85157* 
85158* @namespace YAHOO.widget
85159* @class CalendarGroup
85160* @constructor
85161* @param {String} id optional The id of the table element that will represent the CalendarGroup widget. As of 2.4.0, this argument is optional.
85162* @param {String | HTMLElement} container The id of the container div element that will wrap the CalendarGroup table, or a reference to a DIV element which exists in the document.
85163* @param {Object}
85163 config optional The configuration object containing the initial configuration values for the CalendarGroup.
85164*/
85165function CalendarGroup(id, containerId, config) {
85166    if (arguments.length > 0) {
85167        this.init.apply(this, arguments);
85168    }
85169}
85170
85171/**
85172* The set of default Config property keys and values for the CalendarGroup.
85173* 
85174* <p>
85175* NOTE: This property is made public in order to allow users to change 
85176* the default values of configuration properties. Users should not 
85177* modify the key string, unless they are overriding the Calendar implementation
85178* </p>
85179*
85180* @property YAHOO.widget.CalendarGroup.DEFAULT_CONFIG
85181* @static
85182* @type Object An object with key/value pairs, the key being the 
85183* uppercase configuration property name and the value being an objec 
85184* literal with a key string property, and a value property, specifying the 
85185* default value of the property 
85186*/
85187
85188/**
85189* The set of default Config property keys and values for the CalendarGroup
85190* @property YAHOO.widget.CalendarGroup._DEFAULT_CONFIG
85191* @deprecated Made public. See the public DEFAULT_CONFIG property for details
85192* @private
85193* @static
85194* @type Object
85195*/
85196CalendarGroup.DEFAULT_CONFIG = CalendarGroup._DEFAULT_CONFIG = Calendar.DEFAULT_CONFIG;
85197CalendarGroup.DEFAULT_CONFIG.PAGES = {key:"pages", value:2};
85198
85199var DEF_CFG = CalendarGroup.DEFAULT_CONFIG;
85200
85201CalendarGroup.prototype = {
85202
85203    /**
85204    * Initializes the calendar group. All subclasses must call this method in order for the
85205    * group to be initialized properly.
85206    * @method init
85207    * @param {String} id optional The id of the table element that will represent the CalendarGroup widget. As of 2.4.0, this argument is optional.
85208    * @param {String | HTMLElement} container The id of the container div element that will wrap the CalendarGroup table, or a reference to a DIV element which exists in the document.
85209    * @param {Object} config optional The configuration object containing the initial configuration values for the CalendarGroup.
85210    */
85211    init : function(id, container, config) {
85212
85213        // Normalize 2.4.0, pre 2.4.0 args
85214        var nArgs = this._parseArgs(arguments);
85215
85216        id = nArgs.id;
85217        container = nArgs.container;
85218        config = nArgs.config;
85219
85220        this.oDomContainer = Dom.get(container);
85221        if (!this.oDomContainer) { this.logger.log("Container not found in document.", "error"); }
85222
85223        if (!this.oDomContainer.id) {
85224            this.oDomContainer.id = Dom.generateId();
85225        }
85226        if (!id) {
85227            id = this.oDomContainer.id + "_t";
85228        }
85229
85230        /**
85231        * The unique id associated with the CalendarGroup
85232        * @property id
85233        * @type String
85234        */
85235        this.id = id;
85236
85237        /**
85238        * The unique id associated with the CalendarGroup container
85239        * @property containerId
85240        * @type String
85241        */
85242        this.containerId = this.oDomContainer.id;
85243
85244        this.logger = new YAHOO.widget.LogWriter("CalendarGroup " + this.id);
85245        this.initEvents();
85246        this.initStyles();
85247
85248        /**
85249        * The collection of Calendar pages contained within the CalendarGroup
85250        * @property pages
85251        * @type YAHOO.widget.Calendar[]
85252        */
85253        this.pages = [];
85254
85255        Dom.addClass(this.oDomContainer, CalendarGroup.CSS_CONTAINER);
85256        Dom.addClass(this.oDomContainer, CalendarGroup.CSS_MULTI_UP);
85257
85258        /**
85259        * The Config object used to hold the configuration variables for the CalendarGroup
85260        * @property cfg
85261        * @type YAHOO.util.Config
85262        */
85263        this.cfg = new YAHOO.util.Config(this);
85264
85265        /**
85266        * The local object which contains the CalendarGroup's options
85267        * @property Options
85268        * @type Object
85269        */
85270        this.Options = {};
85271
85272        /**
85273        * The local object which contains the CalendarGroup's locale settings
85274        * @property Locale
85275        * @type Object
85276        */
85277        this.Locale = {};
85278
85279        this.setupConfig();
85280
85281        if (config) {
85282            this.cfg.applyConfig(config, true);
85283        }
85284
85285        this.cfg.fireQueue();
85286
85287        this.logger.log("Initialized " + this.pages.length + "-page CalendarGroup", "info");
85288    },
85289
85290    setupConfig : function() {
85291
85292        var cfg = this.cfg;
85293
85294        /**
85295        * The number of pages to include in the CalendarGroup. This value can only be set once, in the CalendarGroup's constructor arguments.
85296        * @config pages
85297        * @type Number
85298        * @default 2
85299        */
85300        cfg.addProperty(DEF_CFG.PAGES.key, { value:DEF_CFG.PAGES.value, validator:cfg.checkNumber, handler:this.configPages } );
85301
85302        /**
85303        * The positive or negative year offset from the Gregorian calendar year (assuming a January 1st rollover) to 
85304        * be used when displaying or parsing dates.  NOTE: All JS Date objects returned by methods, or expected as input by
85305        * methods will always represent the Gregorian year, in order to maintain date/month/week values.
85306        *
85307        * @config year_offset
85308        * @type Number
85309        * @default 0
85310        */
85311        cfg.addProperty(DEF_CFG.YEAR_OFFSET.key, { value:DEF_CFG.YEAR_OFFSET.value, handler: this.delegateConfig, supercedes:DEF_CFG.YEAR_OFFSET.supercedes, suppressEvent:true } );
85312
85313        /**
85314        * The date to use to represent "Today".
85315        *
85316        * @config today
85317        * @type Date
85318        * @default Today's date
85319        */
85320        cfg.addProperty(DEF_CFG.TODAY.key, { value: new Date(DEF_CFG.TODAY.value.getTime()), supercedes:DEF_CFG.TODAY.supercedes, handler: this.configToday, suppressEvent:false } );
85321
85322        /**
85323        * The month/year representing the current visible Calendar date (mm/yyyy)
85324        * @config pagedate
85325        * @type String | Date
85326        * @default Today's date
85327        */
85328        cfg.addProperty(DEF_CFG.PAGEDATE.key, { value: DEF_CFG.PAGEDATE.value || new Date(DEF_CFG.TODAY.value.getTime()), handler:this.configPageDate } );
85329
85330        /**
85331        * The date or range of dates representing the current Calendar selection
85332        *
85333        * @config selected
85334        * @type String
85335        * @default []
85336        */
85337        cfg.addProperty(DEF_CFG.SELECTED.key, { value:[], handler:this.configSelected } );
85338
85339        /**
85340        * The title to display above the CalendarGroup's month header. The title is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
85341        * @config title
85342        * @type HTML
85343        * @default ""
85344        */
85345        cfg.addProperty(DEF_CFG.TITLE.key, { value:DEF_CFG.TITLE.value, handler:this.configTitle } );
85346
85347        /**
85348        * Whether or not a close button should be displayed for this CalendarGroup
85349        * @config close
85350        * @type Boolean
85351        * @default false
85352        */
85353        cfg.addProperty(DEF_CFG.CLOSE.key, { value:DEF_CFG.CLOSE.value, handler:this.configClose } );
85354
85355        /**
85356        * Whether or not an iframe shim should be placed under the Calendar to prevent select boxes from bleeding through in Internet Explorer 6 and below.
85357        * This property is enabled by default for IE6 and below. It is disabled by default for other browsers for performance reasons, but can be 
85358        * enabled if required.
85359        * 
85360        * @config iframe
85361        * @type Boolean
85362        * @default true for IE6 and below, false for all other browsers
85363        */
85364        cfg.addProperty(DEF_CFG.IFRAME.key, { value:DEF_CFG.IFRAME.value, handler:this.configIframe, validator:cfg.checkBoolean } );
85365
85366        /**
85367        * The minimum selectable date in the current Calendar (mm/dd/yyyy)
85368        * @config mindate
85369        * @type String | Date
85370        * @default null
85371        */
85372        cfg.addProperty(DEF_CFG.MINDATE.key, { value:DEF_CFG.MINDATE.value, handler:this.delegateConfig } );
85373
85374        /**
85375        * The maximum selectable date in the current Calendar (mm/dd/yyyy)
85376        * @config maxdate
85377        * @type String | Date
85378        * @default null
85379        */
85380        cfg.addProperty(DEF_CFG.MAXDATE.key, { value:DEF_CFG.MAXDATE.value, handler:this.delegateConfig  } );
85381
85382        /**
85383        * True if the Calendar should allow multiple selections. False by default.
85384        * @config MULTI_SELECT
85385        * @type Boolean
85386        * @default false
85387        */
85388        cfg.addProperty(DEF_CFG.MULTI_SELECT.key, { value:DEF_CFG.MULTI_SELECT.value, handler:this.delegateConfig, validator:cfg.checkBoolean } );
85389
85390        /**
85391        * True if the Calendar should allow selection of out-of-month dates. False by default.
85392        * @config OOM_SELECT
85393        * @type Boolean
85394        * @default false
85395        */
85396        cfg.addProperty(DEF_CFG.OOM_SELECT.key, { value:DEF_CFG.OOM_SELECT.value, handler:this.delegateConfig, validator:cfg.checkBoolean } );
85397
85398        /**
85399        * The weekday the week begins on. Default is 0 (Sunday).
85400        * @config START_WEEKDAY
85401        * @type number
85402        * @default 0
85403        */ 
85404        cfg.addProperty(DEF_CFG.START_WEEKDAY.key, { value:DEF_CFG.START_WEEKDAY.value, handler:this.delegateConfig, validator:cfg.checkNumber  } );
85405        
85406        /**
85407        * True if the Calendar should show weekday labels. True by default.
85408        * @config SHOW_WEEKDAYS
85409        * @type Boolean
85410        * @default true
85411        */ 
85412        cfg.addProperty(DEF_CFG.SHOW_WEEKDAYS.key, { value:DEF_CFG.SHOW_WEEKDAYS.value, handler:this.delegateConfig, validator:cfg.checkBoolean } );
85413        
85414        /**
85415        * True if the Calendar should show week row headers. False by default.
85416        * @config SHOW_WEEK_HEADER
85417        * @type Boolean
85418        * @default false
85419        */ 
85420        cfg.addProperty(DEF_CFG.SHOW_WEEK_HEADER.key,{ value:DEF_CFG.SHOW_WEEK_HEADER.value, handler:this.delegateConfig, validator:cfg.checkBoolean } );
85421        
85422        /**
85423        * True if the Calendar should show week row footers. False by default.
85424        * @config SHOW_WEEK_FOOTER
85425        * @type Boolean
85426        * @default false
85427        */
85428        cfg.addProperty(DEF_CFG.SHOW_WEEK_FOOTER.key,{ value:DEF_CFG.SHOW_WEEK_FOOTER.value, handler:this.delegateConfig, validator:cfg.checkBoolean } );
85429        
85430        /**
85431        * True if the Calendar should suppress weeks that are not a part of the current month. False by default.
85432        * @config HIDE_BLANK_WEEKS
85433        * @type Boolean
85434        * @default false
85435        */  
85436        cfg.addProperty(DEF_CFG.HIDE_BLANK_WEEKS.key,{ value:DEF_CFG.HIDE_BLANK_WEEKS.value, handler:this.delegateConfig, validator:cfg.checkBoolean } );
85437
85438        /**
85439        * The image URL that should be used for the left navigation arrow. The image URL is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
85440        * @config NAV_ARROW_LEFT
85441        * @type String
85442        * @deprecated You can customize the image by overriding the default CSS class for the left arrow - "calnavleft"
85443        * @default null
85444        */  
85445        cfg.addProperty(DEF_CFG.NAV_ARROW_LEFT.key, { value:DEF_CFG.NAV_ARROW_LEFT.value, handler:this.delegateConfig } );
85446
85447        /**
85448        * The image URL that should be used for the right navigation arrow. The image URL is inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
85449        * @config NAV_ARROW_RIGHT
85450        * @type String
85451        * @deprecated You can customize the image by overriding the default CSS class for the right arrow - "calnavright"
85452        * @default null
85453        */  
85454        cfg.addProperty(DEF_CFG.NAV_ARROW_RIGHT.key, { value:DEF_CFG.NAV_ARROW_RIGHT.value, handler:this.delegateConfig } );
85455    
85456        // Locale properties
85457        
85458        /**
85459        * The short month labels for the current locale. The month labels are inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
85460        * @config MONTHS_SHORT
85461        * @type HTML[]
85462        * @default ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
85463        */
85464        cfg.addProperty(DEF_CFG.MONTHS_SHORT.key, { value:DEF_CFG.MONTHS_SHORT.value, handler:this.delegateConfig } );
85465        
85466        /**
85467        * The long month labels for the current locale. The month labels are inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
85468        * @config MONTHS_LONG
85469        * @type HTML[]
85470        * @default ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "
85470November", "December"
85471        */ 
85472        cfg.addProperty(DEF_CFG.MONTHS_LONG.key,  { value:DEF_CFG.MONTHS_LONG.value, handler:this.delegateConfig } );
85473        
85474        /**
85475        * The 1-character weekday labels for the current locale. The weekday labels are inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
85476        * @config WEEKDAYS_1CHAR
85477        * @type HTML[]
85478        * @default ["S", "M", "T", "W", "T", "F", "S"]
85479        */ 
85480        cfg.addProperty(DEF_CFG.WEEKDAYS_1CHAR.key, { value:DEF_CFG.WEEKDAYS_1CHAR.value, handler:this.delegateConfig } );
85481        
85482        /**
85483        * The short weekday labels for the current locale. The weekday labels are inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
85484        * @config WEEKDAYS_SHORT
85485        * @type HTML[]
85486        * @default ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
85487        */ 
85488        cfg.addProperty(DEF_CFG.WEEKDAYS_SHORT.key, { value:DEF_CFG.WEEKDAYS_SHORT.value, handler:this.delegateConfig } );
85489        
85490        /**
85491        * The medium weekday labels for the current locale. The weekday labels are inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
85492        * @config WEEKDAYS_MEDIUM
85493        * @type HTML[]
85494        * @default ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
85495        */ 
85496        cfg.addProperty(DEF_CFG.WEEKDAYS_MEDIUM.key, { value:DEF_CFG.WEEKDAYS_MEDIUM.value, handler:this.delegateConfig } );
85497        
85498        /**
85499        * The long weekday labels for the current locale. The weekday labels are inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source.
85500        * @config WEEKDAYS_LONG
85501        * @type HTML[]
85502        * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
85503        */ 
85504        cfg.addProperty(DEF_CFG.WEEKDAYS_LONG.key, { value:DEF_CFG.WEEKDAYS_LONG.value, handler:this.delegateConfig } );
85505    
85506        /**
85507        * The setting that determines which length of month labels should be used. Possible values are "short" and "long".
85508        * @config LOCALE_MONTHS
85509        * @type String
85510        * @default "long"
85511        */
85512        cfg.addProperty(DEF_CFG.LOCALE_MONTHS.key, { value:DEF_CFG.LOCALE_MONTHS.value, handler:this.delegateConfig } );
85513    
85514        /**
85515        * The setting that determines which length of weekday labels should be used. Possible values are "1char", "short", "medium", and "long".
85516        * @config LOCALE_WEEKDAYS
85517        * @type String
85518        * @default "short"
85519        */ 
85520        cfg.addProperty(DEF_CFG.LOCALE_WEEKDAYS.key, { value:DEF_CFG.LOCALE_WEEKDAYS.value, handler:this.delegateConfig } );
85521    
85522        /**
85523        * The value used to delimit individual dates in a date string passed to various Calendar functions.
85524        * @config DATE_DELIMITER
85525        * @type String
85526        * @default ","
85527        */
85528        cfg.addProperty(DEF_CFG.DATE_DELIMITER.key,  { value:DEF_CFG.DATE_DELIMITER.value, handler:this.delegateConfig } );
85529    
85530        /**
85531        * The value used to delimit date fields in a date string passed to various Calendar functions.
85532        * @config DATE_FIELD_DELIMITER
85533        * @type String
85534        * @default "/"
85535        */ 
85536        cfg.addProperty(DEF_CFG.DATE_FIELD_DELIMITER.key,{ value:DEF_CFG.DATE_FIELD_DELIMITER.value, handler:this.delegateConfig } );
85537    
85538        /**
85539        * The value used to delimit date ranges in a date string passed to various Calendar functions.
85540        * @config DATE_RANGE_DELIMITER
85541        * @type String
85542        * @default "-"
85543        */
85544        cfg.addProperty(DEF_CFG.DATE_RANGE_DELIMITER.key,{ value:DEF_CFG.DATE_RANGE_DELIMITER.value, handler:this.delegateConfig } );
85545    
85546        /**
85547        * The position of the month in a month/year date string
85548        * @config MY_MONTH_POSITION
85549        * @type Number
85550        * @default 1
85551        */
85552        cfg.addProperty(DEF_CFG.MY_MONTH_POSITION.key, { value:DEF_CFG.MY_MONTH_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
85553        
85554        /**
85555        * The position of the year in a month/year date string
85556        * @config MY_YEAR_POSITION
85557        * @type Number
85558        * @default 2
85559        */ 
85560        cfg.addProperty(DEF_CFG.MY_YEAR_POSITION.key, { value:DEF_CFG.MY_YEAR_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
85561        
85562        /**
85563        * The position of the month in a month/day date string
85564        * @config MD_MONTH_POSITION
85565        * @type Number
85566        * @default 1
85567        */ 
85568        cfg.addProperty(DEF_CFG.MD_MONTH_POSITION.key, { value:DEF_CFG.MD_MONTH_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
85569        
85570        /**
85571        * The position of the day in a month/year date string
85572        * @config MD_DAY_POSITION
85573        * @type Number
85574        * @default 2
85575        */ 
85576        cfg.addProperty(DEF_CFG.MD_DAY_POSITION.key,  { value:DEF_CFG.MD_DAY_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
85577        
85578        /**
85579        * The position of the month in a month/day/year date string
85580        * @config MDY_MONTH_POSITION
85581        * @type Number
85582        * @default 1
85583        */ 
85584        cfg.addProperty(DEF_CFG.MDY_MONTH_POSITION.key, { value:DEF_CFG.MDY_MONTH_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
85585        
85586        /**
85587        * The position of the day in a month/day/year date string
85588        * @config MDY_DAY_POSITION
85589        * @type Number
85590        * @default 2
85591        */ 
85592        cfg.addProperty(DEF_CFG.MDY_DAY_POSITION.key, { value:DEF_CFG.MDY_DAY_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
85593        
85594        /**
85595        * The position of the year in a month/day/year date string
85596        * @config MDY_YEAR_POSITION
85597        * @type Number
85598        * @default 3
85599        */ 
85600        cfg.addProperty(DEF_CFG.MDY_YEAR_POSITION.key, { value:DEF_CFG.MDY_YEAR_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
85601    
85602        /**
85603        * The position of the month in the month year label string used as the Calendar header
85604        * @config MY_LABEL_MONTH_POSITION
85605        * @type Number
85606        * @default 1
85607        */
85608        cfg.addProperty(DEF_CFG.MY_LABEL_MONTH_POSITION.key, { value:DEF_CFG.MY_LABEL_MONTH_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
85609    
85610        /**
85611        * The position of the year in the month year label string used as the Calendar header
85612        * @config MY_LABEL_YEAR_POSITION
85613        * @type Number
85614        * @default 2
85615        */
85616        cfg.addProperty(DEF_CFG.MY_LABEL_YEAR_POSITION.key, { value:DEF_CFG.MY_LABEL_YEAR_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
85617
85618        /**
85619        * The suffix used after the month when rendering the Calendar header
85620        * @config MY_LABEL_MONTH_SUFFIX
85621        * @type String
85622        * @default " "
85623        */
85624        cfg.addProperty(DEF_CFG.MY_LABEL_MONTH_SUFFIX.key, { value:DEF_CFG.MY_LABEL_MONTH_SUFFIX.value, handler:this.delegateConfig } );
85625        
85626        /**
85627        * The suffix used after the year when rendering the Calendar header
85628        * @config MY_LABEL_YEAR_SUFFIX
85629        * @type String
85630        * @default ""
85631        */
85632        cfg.addProperty(DEF_CFG.MY_LABEL_YEAR_SUFFIX.key, { value:DEF_CFG.MY_LABEL_YEAR_SUFFIX.value, handler:this.delegateConfig } );
85633
85634        /**
85635        * Configuration for the Month/Year CalendarNavigator UI which allows the user to jump directly to a 
85636        * specific Month/Year without having to scroll sequentially through months.
85637        * <p>
85638        * Setting this property to null (default value) or false, will disable the CalendarNavigator UI.
85639        * </p>
85640        * <p>
85641        * Setting this property to true will enable the CalendarNavigatior UI with the default CalendarNavigator configuration values.
85642        * </p>
85643        * <p>
85644        * This property can also be set to an object literal containing configuration properties for the CalendarNavigator UI.
85645        * The configuration object expects the the following case-sensitive properties, with the "strings" property being a nested object.
85646        * Any properties which are not provided will use the default values (defined in the CalendarNavigator class).
85647        * </p>
85648        * <dl>
85649        * <dt>strings</dt>
85650        * <dd><em>Object</em> :  An object with the properties shown below, defining the string labels to use in the Navigator's UI. The strings are inserted into the DOM as HTML, and should be escaped by the implementor if coming from an external source. 
85651        *     <dl>
85652        *         <dt>month</dt><dd><em>HTML</em> : The markup to use for the month label. Defaults to "Month".</dd>
85653        *         <dt>year</dt><dd><em>HTML</em> : The markup to use for the year label. Defaults to "Year".</dd>
85654        *         <dt>submit</dt><dd><em>HTML</em> : The markup to use for the submit button label. Defaults to "Okay".</dd>
85655        *         <dt>cancel</dt><dd><em>HTML</em> : The markup to use for the cancel button label. Defaults to "Cancel".</dd>
85656        *         <dt>invalidYear</dt><dd><em>HTML</em> : The markup to use for invalid year values. Defaults to "Year needs to be a number".</dd>
85657        *     </dl>
85658        * </dd>
85659        * <dt>monthFormat</dt><dd><em>String</em> : The month format to use. Either YAHOO.widget.Calendar.LONG, or YAHOO.widget.Calendar.SHORT. Defaults to YAHOO.widget.Calendar.LONG</dd>
85660        * <dt>initialFocus</dt><dd><em>String</em> : Either "year" or "month" specifying which input control should get initial focus. Defaults to "year"</dd>
85661        * </dl>
85662        * <p>E.g.</p>
85663        * <pre>
85664        * var navConfig = {
85665        *   strings: {
85666        *    month:"Calendar Month",
85667        *    year:"Calendar Year",
85668        *    submit: "Submit",
85669        *    cancel: "Cancel",
85670        *    invalidYear: "Please enter a valid year"
85671        *   },
85672        *   monthFormat: YAHOO.widget.Calendar.SHORT,
85673        *   initialFocus: "month"
85674        * }
85675        * </pre>
85676        * @config navigator
85677        * @type {Object|Boolean}
85678        * @default null
85679        */
85680        cfg.addProperty(DEF_CFG.NAV.key, { value:DEF_CFG.NAV.value, handler:this.configNavigator } );
85681
85682        /**
85683         * The map of UI strings which the CalendarGroup UI uses.
85684         *
85685         * @config strings
85686         * @type {Object}
85687         * @default An object with the properties shown below:
85688         *     <dl>
85689         *         <dt>previousMonth</dt><dd><em>HTML</em> : The markup to use for the "Previous Month" navigation label. Defaults to "Previous Month". The string is added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.</dd>
85690         *         <dt>nextMonth</dt><dd><em>HTML</em> : The markup to use for the "Next Month" navigation UI. Defaults to "Next Month". The string is added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.</dd>
85691         *         <dt>close</dt><dd><em>HTML</em> : The markup to use for the close button label. Defaults to "Close". The string is added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.</dd>
85692         *     </dl>
85693         */
85694        cfg.addProperty(DEF_CFG.STRINGS.key, { 
85695            value:DEF_CFG.STRINGS.value, 
85696            handler:this.configStrings, 
85697            validator: function(val) {
85698                return Lang.isObject(val);
85699            },
85700            supercedes: DEF_CFG.STRINGS.supercedes
85701        });
85702    },
85703
85704    /**
85705    * Initializes CalendarGroup's built-in CustomEvents
85706    * @method initEvents
85707    */
85708    initEvents : function() {
85709
85710        var me = this,
85711            strEvent = "Event",
85712            CE = YAHOO.util.CustomEvent;
85713
85714        /**
85715        * Proxy subscriber to subscribe to the CalendarGroup's child Calendars' CustomEvents
85716        * @method sub
85717        * @private
85718        * @param {Function} fn The function to subscribe to this CustomEvent
85719        * @param {Object} obj The CustomEvent's scope object
85720        * @param {Boolean} bOverride Whether or not to apply scope correction
85721        */
85722        var sub = function(fn, obj, bOverride) {
85723            for (var p=0;p<me.pages.length;++p) {
85724                var cal = me.pages[p];
85725                cal[this.type + strEvent].subscribe(fn, obj, bOverride);
85726            }
85727        };
85728
85729        /**
85730        * Proxy unsubscriber to unsubscribe from the CalendarGroup's child Calendars' CustomEvents
85731        * @method unsub
85732        * @private
85733        * @param {Function} fn The function to subscribe to this CustomEvent
85734        * @param {Object} obj The CustomEvent's scope object
85735        */
85736        var unsub = function(fn, obj) {
85737            for (var p=0;p<me.pages.length;++p) {
85738                var cal = me.pages[p];
85739                cal[this.type + strEvent].unsubscribe(fn, obj);
85740            }
85741        };
85742
85743        var defEvents = Calendar._EVENT_TYPES;
85744
85745        /**
85746        * Fired before a date selection is made
85747        * @event beforeSelectEvent
85748        */
85749        me.beforeSelectEvent = new CE(defEvents.BEFORE_SELECT);
85750        me.beforeSelectEvent.subscribe = sub; me.beforeSelectEvent.unsubscribe = unsub;
85751
85752        /**
85753        * Fired when a date selection is made
85754        * @event selectEvent
85755        * @param {Array} Array of Date field arrays in the format [YYYY, MM, DD].
85756        */
85757        me.selectEvent = new CE(defEvents.SELECT); 
85758        me.selectEvent.subscribe = sub; me.selectEvent.unsubscribe = unsub;
85759
85760        /**
85761        * Fired before a date or set of dates is deselected
85762        * @event beforeDeselectEvent
85763        */
85764        me.beforeDeselectEvent = new CE(defEvents.BEFORE_DESELECT); 
85765        me.beforeDeselectEvent.subscribe = sub; me.beforeDeselectEvent.unsubscribe = unsub;
85766
85767        /**
85768        * Fired when a date or set of dates has been deselected
85769        * @event deselectEvent
85770        * @param {Array} Array of Date field arrays in the format [YYYY, MM, DD].
85771        */
85772        me.deselectEvent = new CE(defEvents.DESELECT); 
85773        me.deselectEvent.subscribe = sub; me.deselectEvent.unsubscribe = unsub;
85774        
85775        /**
85776        * Fired when the Calendar page is changed
85777        * @event changePageEvent
85778        */
85779        me.changePageEvent = new CE(defEvents.CHANGE_PAGE); 
85780        me.changePageEvent.subscribe = sub; me.changePageEvent.unsubscribe = unsub;
85781
85782        /**
85783        * Fired before the Calendar is rendered
85784        * @event beforeRenderEvent
85785        */
85786        me.beforeRenderEvent = new CE(defEvents.BEFORE_RENDER);
85787        me.beforeRenderEvent.subscribe = sub; me.beforeRenderEvent.unsubscribe = unsub;
85788    
85789        /**
85790        * Fired when the Calendar is rendered
85791        * @event renderEvent
85792        */
85793        me.renderEvent = new CE(defEvents.RENDER);
85794        me.renderEvent.subscribe = sub; me.renderEvent.unsubscribe = unsub;
85795    
85796        /**
85797        * Fired when the Calendar is reset
85798        * @event resetEvent
85799        */
85800        me.resetEvent = new CE(defEvents.RESET); 
85801        me.resetEvent.subscribe = sub; me.resetEvent.unsubscribe = unsub;
85802    
85803        /**
85804        * Fired when the Calendar is cleared
85805        * @event clearEvent
85806        */
85807        me.clearEvent = new CE(defEvents.CLEAR);
85808        me.clearEvent.subscribe = sub; me.clearEvent.unsubscribe = unsub;
85809
85810        /**
85811        * Fired just before the CalendarGroup is to be shown
85812        * @event beforeShowEvent
85813        */
85814        me.beforeShowEvent = new CE(defEvents.BEFORE_SHOW);
85815    
85816        /**
85817        * Fired after the CalendarGroup is shown
85818        * @event showEvent
85819        */
85820        me.showEvent = new CE(defEvents.SHOW);
85821    
85822        /**
85823        * Fired just before the CalendarGroup is to be hidden
85824        * @event beforeHideEvent
85825        */
85826        me.beforeHideEvent = new CE(defEvents.BEFORE_HIDE);
85827    
85828        /**
85829        * Fired after the CalendarGroup is hidden
85830        * @event hideEvent
85831        */
85832        me.hideEvent = new CE(defEvents.HIDE);
85833
85834        /**
85835        * Fired just before the CalendarNavigator is to be shown
85836        * @event beforeShowNavEvent
85837        */
85838        me.beforeShowNavEvent = new CE(defEvents.BEFORE_SHOW_NAV);
85839    
85840        /**
85841        * Fired after the CalendarNavigator is shown
85842        * @event showNavEvent
85843        */
85844        me.showNavEvent = new CE(defEvents.SHOW_NAV);
85845    
85846        /**
85847        * Fired just before the CalendarNavigator is to be hidden
85848        * @event beforeHideNavEvent
85849        */
85850        me.beforeHideNavEvent = new CE(defEvents.BEFORE_HIDE_NAV);
85851
85852        /**
85853        * Fired after the CalendarNavigator is hidden
85854        * @event hideNavEvent
85855        */
85856        me.hideNavEvent = new CE(defEvents.HIDE_NAV);
85857
85858        /**
85859        * Fired just before the CalendarNavigator is to be rendered
85860        * @event beforeRenderNavEvent
85861        */
85862        me.beforeRenderNavEvent = new CE(defEvents.BEFORE_RENDER_NAV);
85863
85864        /**
85865        * Fired after the CalendarNavigator is rendered
85866        * @event renderNavEvent
85867        */
85868        me.renderNavEvent = new CE(defEvents.RENDER_NAV);
85869
85870        /**
85871        * Fired just before the CalendarGroup is to be destroyed
85872        * @event beforeDestroyEvent
85873        */
85874        me.beforeDestroyEvent = new CE(defEvents.BEFORE_DESTROY);
85875
85876        /**
85877        * Fired after the CalendarGroup is destroyed. This event should be used
85878        * for notification only. When this event is fired, important CalendarGroup instance
85879        * properties, dom references and event listeners have already been 
85880        * removed/dereferenced, and hence the CalendarGroup instance is not in a usable 
85881        * state.
85882        *
85883        * @event destroyEvent
85884        */
85885        me.destroyEvent = new CE(defEvents.DESTROY);
85886    },
85887    
85888    /**
85889    * The default Config handler for the "pages" property
85890    * @method configPages
85891    * @param {String} type The CustomEvent type (usually the property name)
85892    * @param {Object[]} args The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
85893    * @param {Object} obj The scope object. For configuration handlers, this will usually equal the owner.
85894    */
85895    configPages : function(type, args, obj) {
85896        var pageCount = args[0],
85897            cfgPageDate = DEF_CFG.PAGEDATE.key,
85898            sep = "_",
85899            caldate,
85900            firstPageDate = null,
85901            groupCalClass = "groupcal",
85902            firstClass = "first-of-type",
85903            lastClass = "last-of-type";
85904
85905        for (var p=0;p<pageCount;++p) {
85906            var calId = this.id + sep + p,
85907                calContainerId = this.containerId + sep + p,
85908                childConfig = this.cfg.getConfig();
85909
85910            childConfig.close = false;
85911            childConfig.title = false;
85912            childConfig.navigator = null;
85913
85914            if (p > 0) {
85915                caldate = new Date(firstPageDate);
85916                this._setMonthOnDate(caldate, caldate.getMonth() + p);
85917                childConfig.pageDate = caldate;
85918            }
85919
85920            var cal = this.constructChild(calId, calContainerId, childConfig);
85921
85922            Dom.removeClass(cal.oDomContainer, this.Style.CSS_SINGLE);
85923            Dom.addClass(cal.oDomContainer, groupCalClass);
85924
85925            if (p===0) {
85926                firstPageDate = cal.cfg.getProperty(cfgPageDate);
85927                Dom.addClass(cal.oDomContainer, firstClass);
85928            }
85929    
85930            if (p==(pageCount-1)) {
85931                Dom.addClass(cal.oDomContainer, lastClass);
85932            }
85933    
85934            cal.parent = this;
85935            cal.index = p; 
85936    
85937            this.pages[this.pages.length] = cal;
85938        }
85939    },
85940    
85941    /**
85942    * The default Config handler for the "pagedate" property
85943    * @method configPageDate
85944    * @param {String} type The CustomEvent type (usually the property name)
85945    * @param {Object[]} args The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
85946    * @param {Object} obj The scope object. For configuration handlers, this will usually equal the owner.
85947    */
85948    configPageDate : function(type, args, obj) {
85949        var val = args[0],
85950            firstPageDate;
85951
85952        var cfgPageDate = DEF_CFG.PAGEDATE.key;
85953        
85954        for (var p=0;p<this.pages.length;++p) {
85955            var cal = this.pages[p];
85956            if (p === 0) {
85957                firstPageDate = cal._parsePageDate(val);
85958                cal.cfg.setProperty(cfgPageDate, firstPageDate);
85959            } else {
85960                var pageDate = new Date(firstPageDate);
85961                this._setMonthOnDate(pageDate, pageDate.getMonth() + p);
85962                cal.cfg.setProperty(cfgPageDate, pageDate);
85963            }
85964        }
85965    },
85966    
85967    /**
85968    * The default Config handler for the CalendarGroup "selected" property
85969    * @method configSelected
85970    * @param {String} type The CustomEvent type (usually the property name)
85971    * @param {Object[]} args The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
85972    * @param {Object} obj The scope object. For configuration handlers, this will usually equal the owner.
85973    */
85974    configSelected : function(type, args, obj) {
85975        var cfgSelected = DEF_CFG.SELECTED.key;
85976        this.delegateConfig(type, args, obj);
85977        var selected = (this.pages.length > 0) ? this.pages[0].cfg.getProperty(cfgSelected) : []; 
85978        this.cfg.setProperty(cfgSelected, selected, true);
85979    },
85980
85981    
85982    /**
85983    * Delegates a configuration property to the CustomEvents associated with the CalendarGroup's children
85984    * @method delegateConfig
85985    * @param {String} type The CustomEvent type (usually the property name)
85986    * @param {Object[]} args The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
85987    * @param {Object} obj The scope object. For configuration handlers, this will usually equal the owner.
85988    */
85989    delegateConfig : function(type, args, obj) {
85990        var val = args[0];
85991        var cal;
85992    
85993        for (var p=0;p<this.pages.length;p++) {
85994            cal = this.pages[p];
85995            cal.cfg.setProperty(type, val);
85996        }
85997    },
85998
85999    /**
86000    * Adds a function to all child Calendars within this CalendarGroup.
86001    * @method setChildFunction
86002    * @param {String}  fnName  The name of the function
86003    * @param {Function}  fn   The function to apply to each Calendar page object
86004    */
86005    setChildFunction : function(fnName, fn) {
86006        var pageCount = this.cfg.getProperty(DEF_CFG.PAGES.key);
86007    
86008        for (var p=0;p<pageCount;++p) {
86009            this.pages[p][fnName] = fn;
86010        }
86011    },
86012
86013    /**
86014    * Calls a function within all child Calendars within this CalendarGroup.
86015    * @method callChildFunction
86016    * @param {String}  fnName  The name of the function
86017    * @param {Array}  args  The arguments to pass to the function
86018    */
86019    callChildFunction : function(fnName, args) {
86020        var pageCount = this.cfg.getProperty(DEF_CFG.PAGES.key);
86021
86022        for (var p=0;p<pageCount;++p) {
86023            var page = this.pages[p];
86024            if (page[fnName]) {
86025                var fn = page[fnName];
86026                fn.call(page, args);
86027            }
86028        } 
86029    },
86030
86031    /**
86032    * Constructs a child calendar. This method can be overridden if a subclassed version of the default
86033    * calendar is to be used.
86034    * @method constructChild
86035    * @param {String} id   The id of the table element that will represent the calendar widget
86036    * @param {String} containerId The id of the container div element that will wrap the calendar table
86037    * @param {Object} config  The configuration object containing the Calendar's arguments
86038    * @return {YAHOO.widget.Calendar} The YAHOO.widget.Calendar instance that is constructed
86039    */
86040    constructChild : function(id,containerId,config) {
86041        var container = document.getElementById(containerId);
86042        if (! container) {
86043            container = document.createElement("div");
86044            container.id = containerId;
86045            this.oDomContainer.appendChild(container);
86046        }
86047        return new Calendar(id,containerId,config);
86048    },
86049    
86050    /**
86051    * Sets the calendar group's month explicitly. This month will be set into the first
86052    * page of the multi-page calendar, and all other months will be iterated appropriately.
86053    * @method setMonth
86054    * @param {Number} month  The numeric month, from 0 (January) to 11 (December)
86055    */
86056    setMonth : function(month) {
86057        month = parseInt(month, 10);
86058        var currYear;
86059
86060        var cfgPageDate = DEF_CFG.PAGEDATE.key;
86061
86062        for (var p=0; p<this.pages.length; ++p) {
86063            var cal = this.pages[p];
86064            var pageDate = cal.cfg.getProperty(cfgPageDate);
86065            if (p === 0) {
86066                currYear = pageDate.getFullYear();
86067            } else {
86068                pageDate.setFullYear(currYear);
86069            }
86070            this._setMonthOnDate(pageDate, month+p); 
86071            cal.cfg.setProperty(cfgPageDate, pageDate);
86072        }
86073    },
86074
86075    /**
86076    * Sets the calendar group's year explicitly. This year will be set into the first
86077    * page of the multi-page calendar, and all other months will be iterated appropriately.
86078    * @method setYear
86079    * @param {Number} year  The numeric 4-digit year
86080    */
86081    setYear : function(year) {
86082    
86083        var cfgPageDate = DEF_CFG.PAGEDATE.key;
86084    
86085        year = parseInt(year, 10);
86086        for (var p=0;p<this.pages.length;++p) {
86087            var cal = this.pages[p];
86088            var pageDate = cal.cfg.getProperty(cfgPageDate);
86089    
86090            if ((pageDate.getMonth()+1) == 1 && p>0) {
86091                year+=1;
86092            }
86093            cal.setYear(year);
86094        }
86095    },
86096
86097    /**
86098    * Calls the render function of all child calendars within the group.
86099    * @method render
86100    */
86101    render : function() {
86102        this.renderHeader();
86103        for (var p=0;p<this.pages.length;++p) {
86104            var cal = this.pages[p];
86105            cal.render();
86106        }
86107        this.renderFooter();
86108    },
86109
86110    /**
86111    * Selects a date or a collection of dates on the current calendar. This method, by default,
86112    * does not call the render method explicitly. Once selection has completed, render must be 
86113    * called for the changes to be reflected visually.
86114    * @method select
86115    * @param    {String/Date/Date[]}    date    The date string of dates to select in the current calendar. Valid formats are
86116    *                               individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
86117    *                               Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
86118    *                               This method can also take a JavaScript Date object or an array of Date objects.
86119    * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
86120    */
86121    select : function(date) {
86122        for (var p=0;p<this.pages.length;++p) {
86123            var cal = this.pages[p];
86124            cal.select(date);
86125        }
86126        return this.getSelectedDates();
86127    },
86128
86129    /**
86130    * Selects dates in the CalendarGroup based on the cell index provided. This method is used to select cells without having to do a full render. The selected style is applied to the cells directly.
86131    * The value of the MULTI_SELECT Configuration attribute will determine the set of dates which get selected. 
86132    * <ul>
86133    *    <li>If MULTI_SELECT is false, selectCell will select the cell at the specified index for only the last displayed Calendar page.</li>
86134    *    <li>If MULTI_SELECT is true, selectCell will select the cell at the specified index, on each displayed Calendar page.</li>
86135    * </ul>
86136    * @method selectCell
86137    * @param {Number} cellIndex The index of the cell to be selected. 
86138    * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
86139    */
86140    selectCell : function(cellIndex) {
86141        for (var p=0;p<this.pages.length;++p) {
86142            var cal = this.pages[p];
86143            cal.selectCell(cellIndex);
86144        }
86145        return this.getSelectedDates();
86146    },
86147    
86148    /**
86149    * Deselects a date or a collection of dates on the current calendar. This method, by default,
86150    * does not call the render method explicitly. Once deselection has completed, render must be 
86151    * called for the changes to be reflected visually.
86152    * @method deselect
86153    * @param {String/Date/Date[]} date The date string of dates to deselect in the current calendar. Valid formats are
86154    *        individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
86155    *        Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
86156    *        This method can also take a JavaScript Date object or an array of Date objects. 
86157    * @return {Date[]}   Array of JavaScript Date objects representing all individual dates that are currently selected.
86158    */
86159    deselect : function(date) {
86160        for (var p=0;p<this.pages.length;++p) {
86161            var cal = this.pages[p];
86162            cal.deselect(date);
86163        }
86164        return this.getSelectedDates();
86165    },
86166    
86167    /**
86168    * Deselects all dates on the current calendar.
86169    * @method deselectAll
86170    * @return {Date[]}  Array of JavaScript Date objects representing all individual dates that are currently selected.
86171    *      Assuming that this function executes properly, the return value should be an empty array.
86172    *      However, the empty array is returned for the sake of being able to check the selection status
86173    *      of the calendar.
86174    */
86175    deselectAll : function() {
86176        for (var p=0;p<this.pages.length;++p) {
86177            var cal = this.pages[p];
86178            cal.deselectAll();
86179        }
86180        return this.getSelectedDates();
86181    },
86182
86183    /**
86184    * Deselects dates in the CalendarGroup based on the cell index provided. This method is used to select cells without having to do a full render. The selected style is applied to the cells directly.
86185    * deselectCell will deselect the cell at the specified index on each displayed Calendar page.
86186    *
86187    * @method deselectCell
86188    * @param {Number} cellIndex The index of the cell to deselect. 
86189    * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
86190    */
86191    deselectCell : function(cellIndex) {
86192        for (var p=0;p<this.pages.length;++p) {
86193            var cal = this.pages[p];
86194            cal.deselectCell(cellIndex);
86195        }
86196        return this.getSelectedDates();
86197    },
86198
86199    /**
86200    * Resets the calendar widget to the originally selected month and year, and 
86201    * sets the calendar to the initial selection(s).
86202    * @method reset
86203    */
86204    reset : function() {
86205        for (var p=0;p<this.pages.length;++p) {
86206            var cal = this.pages[p];
86207            cal.reset();
86208        }
86209    },
86210
86211    /**
86212    * Clears the selected dates in the current calendar widget and sets the calendar
86213    * to the current month and year.
86214    * @method clear
86215    */
86216    clear : function() {
86217        for (var p=0;p<this.pages.length;++p) {
86218            var cal = this.pages[p];
86219            cal.clear();
86220        }
86221
86222        this.cfg.setProperty(DEF_CFG.SELECTED.key, []);
86223        this.cfg.setProperty(DEF_CFG.PAGEDATE.key, new Date(this.pages[0].today.getTime()));
86224        this.render();
86225    },
86226
86227    /**
86228    * Navigates to the next month page in the calendar widget.
86229    * @method nextMonth
86230    */
86231    nextMonth : function() {
86232        for (var p=0;p<this.pages.length;++p) {
86233            var cal = this.pages[p];
86234            cal.nextMonth();
86235        }
86236    },
86237    
86238    /**
86239    * Navigates to the previous month page in the calendar widget.
86240    * @method previousMonth
86241    */
86242    previousMonth : function() {
86243        for (var p=this.pages.length-1;p>=0;--p) {
86244            var cal = this.pages[p];
86245            cal.previousMonth();
86246        }
86247    },
86248    
86249    /**
86250    * Navigates to the next year in the currently selected month in the calendar widget.
86251    * @method nextYear
86252    */
86253    nextYear : function() {
86254        for (var p=0;p<this.pages.length;++p) {
86255            var cal = this.pages[p];
86256            cal.nextYear();
86257        }
86258    },
86259
86260    /**
86261    * Navigates to the previous year in the currently selected month in the calendar widget.
86262    * @method previousYear
86263    */
86264    previousYear : function() {
86265        for (var p=0;p<this.pages.length;++p) {
86266            var cal = this.pages[p];
86267            cal.previousYear();
86268        }
86269    },
86270
86271    /**
86272    * Gets the list of currently selected dates from the calendar.
86273    * @return   An array of currently selected JavaScript Date objects.
86274    * @type Date[]
86275    */
86276    getSelectedDates : function() { 
86277        var returnDates = [];
86278        var selected = this.cfg.getProperty(DEF_CFG.SELECTED.key);
86279        for (var d=0;d<selected.length;++d) {
86280            var dateArray = selected[d];
86281
86282            var date = DateMath.getDate(dateArray[0],dateArray[1]-1,dateArray[2]);
86283            returnDates.push(date);
86284        }
86285
86286        returnDates.sort( function(a,b) { return a-b; } );
86287        return returnDates;
86288    },
86289
86290    /**
86291    * Adds a renderer to the render stack. The function reference passed to this method will be executed
86292    * when a date cell matches the conditions specified in the date string for this renderer.
86293    * 
86294    * <p>NOTE: The contents of the cell set by the renderer will be added to the DOM as HTML. The custom renderer implementation should 
86295    * escape markup used to set the cell contents, if coming from an external source.<p>
86296    * @method addRenderer
86297    * @param {String} sDates  A date string to associate with the specified renderer. Valid formats
86298    *         include date (12/24/2005), month/day (12/24), and range (12/1/2004-1/1/2005)
86299    * @param {Function} fnRender The function executed to render cells that match the render rules for this renderer.
86300    */
86301    addRenderer : function(sDates, fnRender) {
86302        for (var p=0;p<this.pages.length;++p) {
86303            var cal = this.pages[p];
86304            cal.addRenderer(sDates, fnRender);
86305        }
86306    },
86307
86308    /**
86309    * Adds a month renderer to the render stack. The function reference passed to this method will be executed
86310    * when a date cell matches the month passed to this method
86311    * 
86312    * <p>NOTE: The contents of the cell set by the renderer will be added to the DOM as HTML. The custom renderer implementation should 
86313    * escape markup used to set the cell contents, if coming from an external source.<p>
86314    * @method addMonthRenderer
86315    * @param {Number} month  The month (1-12) to associate with this renderer
86316    * @param {Function} fnRender The function executed to render cells that match the render rules for this renderer.
86317    */
86318    addMonthRenderer : function(month, fnRender) {
86319        for (var p=0;p<this.pages.length;++p) {
86320            var cal = this.pages[p];
86321            cal.addMonthRenderer(month, fnRender);
86322        }
86323    },
86324
86325    /**
86326    * Adds a weekday renderer to the render stack. The function reference passed to this method will be executed
86327    * when a date cell matches the weekday passed to this method.
86328    *
86329    * <p>NOTE: The contents of the cell set by the renderer will be added to the DOM as HTML. The custom renderer implementation should 
86330    * escape HTML used to set the cell contents, if coming from an external source.<p>
86331    *
86332    * @method addWeekdayRenderer
86333    * @param {Number} weekday  The weekday (Sunday = 1, Monday = 2 ... Saturday = 7) to associate with this renderer
86334    * @param {Function} fnRender The function executed to render cells that match the render rules for this renderer.
86335    */
86336    addWeekdayRenderer : function(weekday, fnRender) {
86337        for (var p=0;p<this.pages.length;++p) {
86338            var cal = this.pages[p];
86339            cal.addWeekdayRenderer(weekday, fnRender);
86340        }
86341    },
86342
86343    /**
86344     * Removes all custom renderers added to the CalendarGroup through the addRenderer, addMonthRenderer and 
86345     * addWeekRenderer methods. CalendarGroup's render method needs to be called to after removing renderers 
86346     * to see the changes applied.
86347     * 
86348     * @method removeRenderers
86349     */
86350    removeRenderers : function() {
86351        this.callChildFunction("removeRenderers");
86352    },
86353
86354    /**
86355    * Renders the header for the CalendarGroup.
86356    * @method renderHeader
86357    */
86358    renderHeader : function() {
86359        // EMPTY DEFAULT IMPL
86360    },
86361
86362    /**
86363    * Renders a footer for the 2-up calendar container. By default, this method is
86364    * unimplemented.
86365    * @method renderFooter
86366    */
86367    renderFooter : function() {
86368        // EMPTY DEFAULT IMPL
86369    },
86370
86371    /**
86372    * Adds the designated number of months to the current calendar month, and sets the current
86373    * calendar page date to the new month.
86374    * @method addMonths
86375    * @param {Number} count The number of months to add to the current calendar
86376    */
86377    addMonths : function(count) {
86378        this.callChildFunction("addMonths", count);
86379    },
86380    
86381    /**
86382    * Subtracts the designated number of months from the current calendar month, and sets the current
86383    * calendar page date to the new month.
86384    * @method subtractMonths
86385    * @param {Number} count The number of months to subtract from the current calendar
86386    */
86387    subtractMonths : function(count) {
86388        this.callChildFunction("subtractMonths", count);
86389    },
86390
86391    /**
86392    * Adds the designated number of years to the current calendar, and sets the current
86393    * calendar page date to the new month.
86394    * @method addYears
86395    * @param {Number} count The number of years to add to the current calendar
86396    */
86397    addYears : function(count) {
86398        this.callChildFunction("addYears", count);
86399    },
86400
86401    /**
86402    * Subtcats the designated number of years from the current calendar, and sets the current
86403    * calendar page date to the new month.
86404    * @method subtractYears
86405    * @param {Number} count The number of years to subtract from the current calendar
86406    */
86407    subtractYears : function(count) {
86408        this.callChildFunction("subtractYears", count);
86409    },
86410
86411    /**
86412     * Returns the Calendar page instance which has a pagedate (month/year) matching the given date. 
86413     * Returns null if no match is found.
86414     * 
86415     * @method getCalendarPage
86416     * @param {Date} date The JavaScript Date object for which a Calendar page is to be found.
86417     * @return {Calendar} The Calendar page instance representing the month to which the date 
86418     * belongs.
86419     */
86420    getCalendarPage : function(date) {
86421        var cal = null;
86422        if (date) {
86423            var y = date.getFullYear(),
86424                m = date.getMonth();
86425
86426            var pages = this.pages;
86427            for (var i = 0; i < pages.length; ++i) {
86428                var pageDate = pages[i].cfg.getProperty("pagedate");
86429                if (pageDate.getFullYear() === y && pageDate.getMonth() === m) {
86430                    cal = pages[i];
86431                    break;
86432                }
86433            }
86434        }
86435        return cal;
86436    },
86437
86438    /**
86439    * Sets the month on a Date object, taking into account year rollover if the month is less than 0 or greater than 11.
86440    * The Date object passed in is modified. It should be cloned before passing it into this method if the original value needs to be maintained
86441    * @method _setMonthOnDate
86442    * @private
86443    * @param {Date} date The Date object on which to set the month index
86444    * @param {Number} iMonth The month index to set
86445    */
86446    _setMonthOnDate : function(date, iMonth) {
86447        // Bug in Safari 1.3, 2.0 (WebKit build < 420), Date.setMonth does not work consistently if iMonth is not 0-11
86448        if (YAHOO.env.ua.webkit && YAHOO.env.ua.webkit < 420 && (iMonth < 0 || iMonth > 11)) {
86449            var newDate = DateMath.add(date, DateMath.MONTH, iMonth-date.getMonth());
86450            date.setTime(newDate.getTime());
86451        } else {
86452            date.setMonth(iMonth);
86453        }
86454    },
86455    
86456    /**
86457     * Fixes the width of the CalendarGroup container element, to account for miswrapped floats
86458     * @method _fixWidth
86459     * @private
86460     */
86461    _fixWidth : function() {
86462        var w = 0;
86463        for (var p=0;p<this.pages.length;++p) {
86464            var cal = this.pages[p];
86465            w += cal.oDomContainer.offsetWidth;
86466        }
86467        if (w > 0) {
86468            this.oDomContainer.style.width = w + "px";
86469        }
86470    },
86471    
86472    /**
86473    * Returns a string representation of the object.
86474    * @method toString
86475    * @return {String} A string representation of the CalendarGroup object.
86476    */
86477    toString : function() {
86478        return "CalendarGroup " + this.id;
86479    },
86480
86481    /**
86482     * Destroys the CalendarGroup instance. The method will remove references
86483     * to HTML elements, remove any event listeners added by the CalendarGroup.
86484     * 
86485     * It will also destroy the Config and CalendarNavigator instances created by the 
86486     * CalendarGroup and the individual Calendar instances created for each page.
86487     *
86488     * @method destroy
86489     */
86490    destroy : function() {
86491
86492        if (this.beforeDestroyEvent.fire()) {
86493
86494            var cal = this;
86495    
86496            // Child objects
86497            if (cal.navigator) {
86498                cal.navigator.destroy();
86499            }
86500    
86501            if (cal.cfg) {
86502                cal.cfg.destroy();
86503            }
86504    
86505            // DOM event listeners
86506            Event.purgeElement(cal.oDomContainer, true);
86507    
86508            // Generated markup/DOM - Not removing the container DIV since we didn't create it.
86509            Dom.removeClass(cal.oDomContainer, CalendarGroup.CSS_CONTAINER);
86510            Dom.removeClass(cal.oDomContainer, CalendarGroup.CSS_MULTI_UP);
86511            
86512            for (var i = 0, l = cal.pages.length; i < l; i++) {
86513                cal.pages[i].destroy();
86514                cal.pages[i] = null;
86515            }
86516    
86517            cal.oDomContainer.innerHTML = "";
86518    
86519            // JS-to-DOM references
86520            cal.oDomContainer = null;
86521    
86522            this.destroyEvent.fire();
86523        }
86524    }
86525};
86526
86527/**
86528* CSS class representing the container for the calendar
86529* @property YAHOO.widget.CalendarGroup.CSS_CONTAINER
86530* @static
86531* @final
86532* @type String
86533*/
86534CalendarGroup.CSS_CONTAINER = "yui-calcontainer";
86535
86536/**
86537* CSS class representing the container for the calendar
86538* @property YAHOO.widget.CalendarGroup.CSS_MULTI_UP
86539* @static
86540* @final
86541* @type String
86542*/
86543CalendarGroup.CSS_MULTI_UP = "multi";
86544
86545/**
86546* CSS class representing the title for the 2-up calendar
86547* @property YAHOO.widget.CalendarGroup.CSS_2UPTITLE
86548* @static
86549* @final
86550* @type String
86551*/
86552CalendarGroup.CSS_2UPTITLE = "title";
86553
86554/**
86555* CSS class representing the close icon for the 2-up calendar
86556* @property YAHOO.widget.CalendarGroup.CSS_2UPCLOSE
86557* @static
86558* @final
86559* @deprecated Along with Calendar.IMG_ROOT and NAV_ARROW_LEFT, NAV_ARROW_RIGHT configuration properties.
86560*     Calendar's <a href="YAHOO.widget.Calendar.html#Style.CSS_CLOSE">Style.CSS_CLOSE</a> property now represents the CSS class used to render the close icon
86561* @type String
86562*/
86563CalendarGroup.CSS_2UPCLOSE = "close-icon";
86564
86565YAHOO.lang.augmentProto(CalendarGroup, Calendar, "buildDayLabel",
86566                                                 "buildMonthLabel",
86567                                                 "renderOutOfBoundsDate",
86568                                                 "renderRowHeader",
86569                                                 "renderRowFooter",
86570                                                 "renderCellDefault",
86571                                                 "styleCellDefault",
86572                                                 "renderCellStyleHighlight1",
86573                                                 "renderCellStyleHighlight2",
86574                                                 "renderCellStyleHighlight3",
86575                                                 "renderCellStyleHighlight4",
86576                                                 "renderCellStyleToday",
86577                                                 "renderCellStyleSelected",
86578                                                 "renderCellNotThisMonth",
86579                                                 "styleCellNotThisMonth",
86580                                                 "renderBodyCellRestricted",
86581                                                 "initStyles",
86582                                                 "configTitle",
86583                                                 "configClose",
86584                                                 "configIframe",
86585                                                 "configStrings",
86586                                                 "configToday",
86587                                                 "configNavigator",
86588                                                 "createTitleBar",
86589                                                 "createCloseButton",
86590                                                 "removeTitleBar",
86591                                                 "removeCloseButton",
86592                                                 "hide",
86593                                                 "show",
86594                                                 "toDate",
86595                                                 "_toDate",
86596                                                 "_parseArgs",
86597                                                 "browser");
86598
86599YAHOO.widget.CalGrp = CalendarGroup;
86600YAHOO.widget.CalendarGroup = CalendarGroup;
86601
86602/**
86603* @class YAHOO.widget.Calendar2up
86604* @extends YAHOO.widget.CalendarGroup
86605* @deprecated The old Calendar2up class is no longer necessary, since CalendarGroup renders in a 2up view by default.
86606*/
86607YAHOO.widget.Calendar2up = function(id, containerId, config) {
86608    this.init(id, containerId, config);
86609};
86610
86611YAHOO.extend(YAHOO.widget.Calendar2up, CalendarGroup);
86612
86613/**
86614* @deprecated The old Calendar2up class is no longer necessary, since CalendarGroup renders in a 2up view by default.
86615*/
86616YAHOO.widget.Cal2up = YAHOO.widget.Calendar2up;
86617
86618})();
86619/**
86620 * The CalendarNavigator is used along with a Calendar/CalendarGroup to 
86621 * provide a Month/Year popup navigation control, allowing the user to navigate 
86622 * to a specific month/year in the Calendar/CalendarGroup without having to 
86623 * scroll through months sequentially
86624 *
86625 * @namespace YAHOO.widget
86626 * @class CalendarNavigator
86627 * @constructor
86628 * @param {Calendar|CalendarGroup} cal The instance of the Calendar or CalendarGroup to which this CalendarNavigator should be attached.
86629 */
86630YAHOO.widget.CalendarNavigator = function(cal) {
86631    this.init(cal);
86632};
86633
86634(function() {
86635    // Setup static properties (inside anon fn, so that we can use shortcuts)
86636    var CN = YAHOO.widget.CalendarNavigator;
86637
86638    /**
86639     * YAHOO.widget.CalendarNavigator.CLASSES contains constants
86640     * for the class values applied to the CalendarNaviatgator's 
86641     * DOM elements
86642     * @property YAHOO.widget.CalendarNavigator.CLASSES
86643     * @type Object
86644     * @static
86645     */
86646    CN.CLASSES = {
86647        /**
86648         * Class applied to the Calendar Navigator's bounding box
86649         * @property YAHOO.widget.CalendarNavigator.CLASSES.NAV
86650         * @type String
86651         * @static
86652         */
86653        NAV :"yui-cal-nav",
86654        /**
86655         * Class applied to the Calendar/CalendarGroup's bounding box to indicate
86656         * the Navigator is currently visible
86657         * @property YAHOO.widget.CalendarNavigator.CLASSES.NAV_VISIBLE
86658         * @type String
86659         * @static
86660         */
86661        NAV_VISIBLE: "yui-cal-nav-visible",
86662        /**
86663         * Class applied to the Navigator mask's bounding box
86664         * @property YAHOO.widget.CalendarNavigator.CLASSES.MASK
86665         * @type String
86666         * @static
86667         */
86668        MASK : "yui-cal-nav-mask",
86669        /**
86670         * Class applied to the year label/control bounding box
86671         * @property YAHOO.widget.CalendarNavigator.CLASSES.YEAR
86672         * @type String
86673         * @static
86674         */
86675        YEAR : "yui-cal-nav-y",
86676        /**
86677         * Class applied to the month label/control bounding box
86678         * @property YAHOO.widget.CalendarNavigator.CLASSES.MONTH
86679         * @type String
86680         * @static
86681         */
86682        MONTH : "yui-cal-nav-m",
86683        /**
86684         * Class applied to the submit/cancel button's bounding box
86685         * @property YAHOO.widget.CalendarNavigator.CLASSES.BUTTONS
86686         * @type String
86687         * @static
86688         */
86689        BUTTONS : "yui-cal-nav-b",
86690        /**
86691         * Class applied to buttons wrapping element
86692         * @property YAHOO.widget.CalendarNavigator.CLASSES.BUTTON
86693         * @type String
86694         * @static
86695         */
86696        BUTTON : "yui-cal-nav-btn",
86697        /**
86698         * Class applied to the validation error area's bounding box
86699         * @property YAHOO.widget.CalendarNavigator.CLASSES.ERROR
86700         * @type String
86701         * @static
86702         */
86703        ERROR : "yui-cal-nav-e",
86704        /**
86705         * Class applied to the year input control
86706         * @property YAHOO.widget.CalendarNavigator.CLASSES.YEAR_CTRL
86707         * @type String
86708         * @static
86709         */
86710        YEAR_CTRL : "yui-cal-nav-yc",
86711        /**
86712         * Class applied to the month input control
86713         * @property YAHOO.widget.CalendarNavigator.CLASSES.MONTH_CTRL
86714         * @type String
86715         * @static
86716         */
86717        MONTH_CTRL : "yui-cal-nav-mc",
86718        /**
86719         * Class applied to controls with invalid data (e.g. a year input field with invalid an year)
86720         * @property YAHOO.widget.CalendarNavigator.CLASSES.INVALID
86721         * @type String
86722         * @static
86723         */
86724        INVALID : "yui-invalid",
86725        /**
86726         * Class applied to default controls
86727         * @property YAHOO.widget.CalendarNavigator.CLASSES.DEFAULT
86728         * @type String
86729         * @static
86730         */
86731        DEFAULT : "yui-default"
86732    };
86733
86734    /**
86735     * Object literal containing the default configuration values for the CalendarNavigator
86736     * The configuration object is expected to follow the format below, with the properties being
86737     * case sensitive.
86738     * <dl>
86739     * <dt>strings</dt>
86740     * <dd><em>Object</em> :  An object with the properties shown below, defining the string labels to use in the Navigator's UI
86741     *     <dl>
86742     *         <dt>month</dt><dd><em>HTML</em> : The markup to use for the month label. Defaults to "Month".</dd>
86743     *         <dt>year</dt><dd><em>HTML</em> : The markup to use for the year label. Defaults to "Year".</dd>
86744     *         <dt>submit</dt><dd><em>HTML</em> : The markup to use for the submit button label. Defaults to "Okay".</dd>
86745     *         <dt>cancel</dt><dd><em>HTML</em> : The markup to use for the cancel button label. Defaults to "Cancel".</dd>
86746     *         <dt>invalidYear</dt><dd><em>HTML</em> : The markup to use for invalid year values. Defaults to "Year needs to be a number".</dd>
86747     *     </dl>
86748     * </dd>
86749     * <dt>monthFormat</dt><dd><em>String</em> : The month format to use. Either YAHOO.widget.Calendar.LONG, or YAHOO.widget.Calendar.SHORT. Defaults to YAHOO.widget.Calendar.LONG</dd>
86750     * <dt>initialFocus</dt><dd><em>String</em> : Either "year" or "month" specifying which input control should get initial focus. Defaults to "year"</dd>
86751     * </dl>
86752     * @property DEFAULT_CONFIG
86753     * @type Object
86754     * @static
86755     */
86756    CN.DEFAULT_CONFIG = {
86757        strings : {
86758            month: "Month",
86759            year: "Year",
86760            submit: "Okay",
86761            cancel: "Cancel",
86762            invalidYear : "Year needs to be a number"
86763        },
86764        monthFormat: YAHOO.widget.Calendar.LONG,
86765        initialFocus: "year"
86766    };
86767
86768    /**
86769     * Object literal containing the default configuration values for the CalendarNavigator
86770     * @property _DEFAULT_CFG
86771     * @protected
86772     * @deprecated Made public. See the public DEFAULT_CONFIG property
86773     * @type Object
86774     * @static
86775     */
86776    CN._DEFAULT_CFG = CN.DEFAULT_CONFIG;
86777
86778
86779    /**
86780     * The suffix added to the Calendar/CalendarGroup's ID, to generate
86781     * a unique ID for the Navigator and it's bounding box.
86782     * @property YAHOO.widget.CalendarNavigator.ID_SUFFIX
86783     * @static
86784     * @type String
86785     * @final
86786     */
86787    CN.ID_SUFFIX = "_nav";
86788    /**
86789     * The suffix added to the Navigator's ID, to generate
86790     * a unique ID for the month control.
86791     * @property YAHOO.widget.CalendarNavigator.MONTH_SUFFIX
86792     * @static
86793     * @type String 
86794     * @final
86795     */
86796    CN.MONTH_SUFFIX = "_month";
86797    /**
86798     * The suffix added to the Navigator's ID, to generate
86799     * a unique ID for the year control.
86800     * @property YAHOO.widget.CalendarNavigator.YEAR_SUFFIX
86801     * @static
86802     * @type String
86803     * @final
86804     */
86805    CN.YEAR_SUFFIX = "_year";
86806    /**
86807     * The suffix added to the Navigator's ID, to generate
86808     * a unique ID for the error bounding box.
86809     * @property YAHOO.widget.CalendarNavigator.ERROR_SUFFIX
86810     * @static
86811     * @type String
86812     * @final
86813     */
86814    CN.ERROR_SUFFIX = "_error";
86815    /**
86816     * The suffix added to the Navigator's ID, to generate
86817     * a unique ID for the "Cancel" button.
86818     * @property YAHOO.widget.CalendarNavigator.CANCEL_SUFFIX
86819     * @static
86820     * @type String
86821     * @final
86822     */
86823    CN.CANCEL_SUFFIX = "_cancel";
86824    /**
86825     * The suffix added to the Navigator's ID, to generate
86826     * a unique ID for the "Submit" button.
86827     * @property YAHOO.widget.CalendarNavigator.SUBMIT_SUFFIX
86828     * @static
86829     * @type String
86830     * @final
86831     */
86832    CN.SUBMIT_SUFFIX = "_submit";
86833
86834    /**
86835     * The number of digits to which the year input control is to be limited.
86836     * @property YAHOO.widget.CalendarNavigator.YR_MAX_DIGITS
86837     * @static
86838     * @type Number
86839     */
86840    CN.YR_MAX_DIGITS = 4;
86841
86842    /**
86843     * The amount by which to increment the current year value,
86844     * when the arrow up/down key is pressed on the year control
86845     * @property YAHOO.widget.CalendarNavigator.YR_MINOR_INC
86846     * @static
86847     * @type Number
86848     */
86849    CN.YR_MINOR_INC = 1;
86850
86851    /**
86852     * The amount by which to increment the current year value,
86853     * when the page up/down key is pressed on the year control
86854     * @property YAHOO.widget.CalendarNavigator.YR_MAJOR_INC
86855     * @static
86856     * @type Number
86857     */
86858    CN.YR_MAJOR_INC = 10;
86859
86860    /**
86861     * Artificial delay (in ms) between the time the Navigator is hidden
86862     * and the Calendar/CalendarGroup state is updated. Allows the user
86863     * the see the Calendar/CalendarGroup page changing. If set to 0
86864     * the Calendar/CalendarGroup page will be updated instantly
86865     * @property YAHOO.widget.CalendarNavigator.UPDATE_DELAY
86866     * @static
86867     * @type Number
86868     */
86869    CN.UPDATE_DELAY = 50;
86870
86871    /**
86872     * Regular expression used to validate the year input
86873     * @property YAHOO.widget.CalendarNavigator.YR_PATTERN
86874     * @static
86875     * @type RegExp
86876     */
86877    CN.YR_PATTERN = /^\d+$/;
86878    /**
86879     * Regular expression used to trim strings
86880     * @property YAHOO.widget.CalendarNavigator.TRIM
86881     * @static
86882     * @type RegExp
86883     */
86884    CN.TRIM = /^\s*(.*?)\s*$/;
86885})();
86886
86887YAHOO.widget.CalendarNavigator.prototype = {
86888
86889    /**
86890     * The unique ID for this CalendarNavigator instance
86891     * @property id
86892     * @type String
86893     */
86894    id : null,
86895
86896    /**
86897     * The Calendar/CalendarGroup instance to which the navigator belongs
86898     * @property cal
86899     * @type {Calendar|CalendarGroup}
86900     */
86901    cal : null,
86902
86903    /**
86904     * Reference to the HTMLElement used to render the navigator's bounding box
86905     * @property navEl
86906     * @type HTMLElement
86907     */
86908    navEl : null,
86909
86910    /**
86911     * Reference to the HTMLElement used to render the navigator's mask
86912     * @property maskEl
86913     * @type HTMLElement
86914     */
86915    maskEl : null,
86916
86917    /**
86918     * Reference to the HTMLElement used to input the year
86919     * @property yearEl
86920     * @type HTMLElement
86921     */
86922    yearEl : null,
86923
86924    /**
86925     * Reference to the HTMLElement used to input the month
86926     * @property monthEl
86927     * @type HTMLElement
86928     */
86929    monthEl : null,
86930
86931    /**
86932     * Reference to the HTMLElement used to display validation errors
86933     * @property errorEl
86934     * @type HTMLElement
86935     */
86936    errorEl : null,
86937
86938    /**
86939     * Reference to the HTMLElement used to update the Calendar/Calendar group
86940     * with the month/year values
86941     * @property submitEl
86942     * @type HTMLElement
86943     */
86944    submitEl : null,
86945    
86946    /**
86947     * Reference to the HTMLElement used to hide the navigator without updating the 
86948     * Calendar/Calendar group
86949     * @property cancelEl
86950     * @type HTMLElement
86951     */
86952    cancelEl : null,
86953
86954    /** 
86955     * Reference to the first focusable control in the navigator (by default monthEl)
86956     * @property firstCtrl
86957     * @type HTMLElement
86958     */
86959    firstCtrl : null,
86960    
86961    /** 
86962     * Reference to the last focusable control in the navigator (by default cancelEl)
86963     * @property lastCtrl
86964     * @type HTMLElement
86965     */
86966    lastCtrl : null,
86967
86968    /**
86969     * The document containing the Calendar/Calendar group instance
86970     * @protected
86971     * @property _doc
86972     * @type HTMLDocument
86973     */
86974    _doc : null,
86975
86976    /**
86977     * Internal state property for the current year displayed in the navigator
86978     * @protected
86979     * @property _year
86980     * @type Number
86981     */
86982    _year: null,
86983    
86984    /**
86985     * Internal state property for the current month index displayed in the navigator
86986     * @protected
86987     * @property _month
86988     * @type Number
86989     */
86990    _month: 0,
86991
86992    /**
86993     * Private internal state property which indicates whether or not the 
86994     * Navigator has been rendered.
86995     * @private
86996     * @property __rendered
86997     * @type Boolean
86998     */
86999    __rendered: false,
87000
87001    /**
87002     * Init lifecycle method called as part of construction
87003     * 
87004     * @method init
87005     * @param {Calendar} cal The instance of the Calendar or CalendarGroup to which this CalendarNavigator should be attached
87006     */
87007    init : function(cal) {
87008        var calBox = cal.oDomContainer;
87009
87010        this.cal = cal;
87011        this.id = calBox.id + YAHOO.widget.CalendarNavigator.ID_SUFFIX;
87012        this._doc = calBox.ownerDocument;
87013
87014        /**
87015         * Private flag, to identify IE Quirks
87016         * @private
87017         * @property __isIEQuirks
87018         */
87019        var ie = YAHOO.env.ua.ie;
87020        this.__isIEQuirks = (ie && ((ie <= 6) || (this._doc.compatMode == "BackCompat")));
87021    },
87022
87023    /**
87024     * Displays the navigator and mask, updating the input controls to reflect the 
87025     * currently set month and year. The show method will invoke the render method
87026     * if the navigator has not been renderered already, allowing for lazy rendering
87027     * of the control.
87028     * 
87029     * The show method will fire the Calendar/CalendarGroup's beforeShowNav and showNav events
87030     * 
87031     * @method show
87032     */
87033    show : function() {
87034        var CLASSES = YAHOO.widget.CalendarNavigator.CLASSES;
87035
87036        if (this.cal.beforeShowNavEvent.fire()) {
87037            if (!this.__rendered) {
87038                this.render();
87039            }
87040            this.clearErrors();
87041
87042            this._updateMonthUI();
87043            this._updateYearUI();
87044            this._show(this.navEl, true);
87045
87046            this.setInitialFocus();
87047            this.showMask();
87048
87049            YAHOO.util.Dom.addClass(this.cal.oDomContainer, CLASSES.NAV_VISIBLE);
87050            this.cal.showNavEvent.fire();
87051        }
87052    },
87053
87054    /**
87055     * Hides the navigator and mask
87056     * 
87057     * The show method will fire the Calendar/CalendarGroup's beforeHideNav event and hideNav events
87058     * @method hide
87059     */
87060    hide : function() {
87061        var CLASSES = YAHOO.widget.CalendarNavigator.CLASSES;
87062
87063        if (this.cal.beforeHideNavEvent.fire()) {
87064            this._show(this.navEl, false);
87065            this.hideMask();
87066            YAHOO.util.Dom.removeClass(this.cal.oDomContainer, CLASSES.NAV_VISIBLE);
87067            this.cal.hideNavEvent.fire();
87068        }
87069    },
87070    
87071
87072    /**
87073     * Displays the navigator's mask element
87074     * 
87075     * @method showMask
87076     */
87077    showMask : function() {
87078        this._show(this.maskEl, true);
87079        if (this.__isIEQuirks) {
87080            this._syncMask();
87081        }
87082    },
87083
87084    /**
87085     * Hides the navigator's mask element
87086     * 
87087     * @method hideMask
87088     */
87089    hideMask : function() {
87090        this._show(this.maskEl, false);
87091    },
87092
87093    /**
87094     * Returns the current month set on the navigator
87095     * 
87096     * Note: This may not be the month set in the UI, if 
87097     * the UI contains an invalid value.
87098     * 
87099     * @method getMonth
87100     * @return {Number} The Navigator's current month index
87101     */
87102    getMonth: function() {
87103        return this._month;
87104    },
87105
87106    /**
87107     * Returns the current year set on the navigator
87108     * 
87109     * Note: This may not be the year set in the UI, if 
87110     * the UI contains an invalid value.
87111     * 
87112     * @method getYear
87113     * @return {Number} The Navigator's current year value
87114     */
87115    getYear: function() {
87116        return this._year;
87117    },
87118
87119    /**
87120     * Sets the current month on the Navigator, and updates the UI
87121     * 
87122     * @method setMonth
87123     * @param {Number} nMonth The month index, from 0 (Jan) through 11 (Dec).
87124     */
87125    setMonth : function(nMonth) {
87126        if (nMonth >= 0 && nMonth < 12) {
87127            this._month = nMonth;
87128        }
87129        this._updateMonthUI();
87130    },
87131
87132    /**
87133     * Sets the current year on the Navigator, and updates the UI. If the 
87134     * provided year is invalid, it will not be set.
87135     * 
87136     * @method setYear
87137     * @param {Number} nYear The full year value to set the Navigator to.
87138     */
87139    setYear : function(nYear) {
87140        var yrPattern = YAHOO.widget.CalendarNavigator.YR_PATTERN;
87141        if (YAHOO.lang.isNumber(nYear) && yrPattern.test(nYear+"")) {
87142            this._year = nYear;
87143        }
87144        this._updateYearUI();
87145    },
87146
87147    /**
87148     * Renders the HTML for the navigator, adding it to the 
87149     * document and attaches event listeners if it has not 
87150     * already been rendered.
87151     * 
87152     * @method render
87153     */
87154    render: function() {
87155        this.cal.beforeRenderNavEvent.fire();
87156        if (!this.__rendered) {
87157            this.createNav();
87158            this.createMask();
87159            this.applyListeners();
87160            this.__rendered = true;
87161        }
87162        this.cal.renderNavEvent.fire();
87163    },
87164
87165    /**
87166     * Creates the navigator's containing HTMLElement, it's contents, and appends 
87167     * the containg element to the Calendar/CalendarGroup's container.
87168     * 
87169     * @method createNav
87170     */
87171    createNav : function() {
87172        var NAV = YAHOO.widget.CalendarNavigator;
87173        var doc = this._doc;
87174
87175        var d = doc.createElement("div");
87176        d.className = NAV.CLASSES.NAV;
87177
87178        var htmlBuf = this.renderNavContents([]);
87179
87180        d.innerHTML = htmlBuf.join('');
87181        this.cal.oDomContainer.appendChild(d);
87182
87183        this.navEl = d;
87184
87185        this.yearEl = doc.getElementById(this.id + NAV.YEAR_SUFFIX);
87186        this.monthEl = doc.getElementById(this.id + NAV.MONTH_SUFFIX);
87187        this.errorEl = doc.getElementById(this.id + NAV.ERROR_SUFFIX);
87188        this.submitEl = doc.getElementById(this.id + NAV.SUBMIT_SUFFIX);
87189        this.cancelEl = doc.getElementById(this.id + NAV.CANCEL_SUFFIX);
87190
87191        if (YAHOO.env.ua.gecko && this.yearEl && this.yearEl.type == "text") {
87192            // Avoid XUL error on focus, select [ https://bugzilla.mozilla.org/show_bug.cgi?id=236791, 
87193            // supposedly fixed in 1.8.1, but there are reports of it still being around for methods other than blur ]
87194            this.yearEl.setAttribute("autocomplete", "off");
87195        }
87196
87197        this._setFirstLastElements();
87198    },
87199
87200    /**
87201     * Creates the Mask HTMLElement and appends it to the Calendar/CalendarGroups
87202     * container.
87203     * 
87204     * @method createMask
87205     */
87206    createMask : function() {
87207        var C = YAHOO.widget.CalendarNavigator.CLASSES;
87208
87209        var d = this._doc.createElement("div");
87210        d.className = C.MASK;
87211
87212        this.cal.oDomContainer.appendChild(d);
87213        this.maskEl = d;
87214    },
87215
87216    /**
87217     * Used to set the width/height of the mask in pixels to match the Calendar Container.
87218     * Currently only used for IE6 or IE in quirks mode. The other A-Grade browser are handled using CSS (width/height 100%).
87219     * <p>
87220     * The method is also registered as an HTMLElement resize listener on the Calendars container element.
87221     * </p>
87222     * @protected
87223     * @method _syncMask
87224     */
87225    _syncMask : function() {
87226        var c = this.cal.oDomContainer;
87227        if (c && this.maskEl) {
87228            var r = YAHOO.util.Dom.getRegion(c);
87229            YAHOO.util.Dom.setStyle(this.maskEl, "width", r.right - r.left + "px");
87230            YAHOO.util.Dom.setStyle(this.maskEl, "height", r.bottom - r.top + "px");
87231        }
87232    },
87233
87234    /**
87235     * Renders the contents of the navigator. NOTE: The contents of the array passed into this method are added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.
87236     * 
87237     * @method renderNavContents
87238     * 
87239     * @param {HTML[]} html The HTML buffer to append the HTML to.
87240     * @return {HTML[]} A reference to the buffer passed in.
87241     */
87242    renderNavContents : function(html) {
87243        var NAV = YAHOO.widget.CalendarNavigator,
87244            C = NAV.CLASSES,
87245            h = html; // just to use a shorter name
87246
87247        h[h.length] = '<div class="' + C.MONTH + '">';
87248        this.renderMonth(h);
87249        h[h.length] = '</div>';
87250        h[h.length] = '<div class="' + C.YEAR + '">';
87251        this.renderYear(h);
87252        h[h.length] = '</div>';
87253        h[h.length] = '<div class="' + C.BUTTONS + '">';
87254        this.renderButtons(h);
87255        h[h.length] = '</div>';
87256        h[h.length] = '<div class="' + C.ERROR + '" id="' + this.id + NAV.ERROR_SUFFIX + '"></div>';
87257
87258        return h;
87259    },
87260
87261    /**
87262     * Renders the month label and control for the navigator. NOTE: The contents of the array passed into this method are added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.
87263     * 
87264     * @method renderNavContents
87265     * @param {HTML[]} html The HTML buffer to append the HTML to.
87266     * @return {HTML[]} A reference to the buffer passed in.
87267     */
87268    renderMonth : function(html) {
87269        var NAV = YAHOO.widget.CalendarNavigator,
87270            C = NAV.CLASSES;
87271
87272        var id = this.id + NAV.MONTH_SUFFIX,
87273            mf = this.__getCfg("monthFormat"),
87274            months = this.cal.cfg.getProperty((mf == YAHOO.widget.Calendar.SHORT) ? "MONTHS_SHORT" : "MONTHS_LONG"),
87275            h = html;
87276
87277        if (months && months.length > 0) {
87278            h[h.length] = '<label for="' + id + '">';
87279            h[h.length] = this.__getCfg("month", true);
87280            h[h.length] = '</label>';
87281            h[h.length] = '<select name="' + id + '" id="' + id + '" class="' + C.MONTH_CTRL + '">';
87282            for (var i = 0; i < months.length; i++) {
87283                h[h.length] = '<option value="' + i + '">';
87284                h[h.length] = months[i];
87285                h[h.length] = '</option>';
87286            }
87287            h[h.length] = '</select>';
87288        }
87289        return h;
87290    },
87291
87292    /**
87293     * Renders the year label and control for the navigator. NOTE: The contents of the array passed into this method are added to the DOM as HTML, and should be escaped by the implementor if coming from an external source. 
87294     * 
87295     * @method renderYear
87296     * @param {Array} html The HTML buffer to append the HTML to.
87297     * @return {Array} A reference to the buffer passed in.
87298     */
87299    renderYear : function(html) {
87300        var NAV = YAHOO.widget.CalendarNavigator,
87301            C = NAV.CLASSES;
87302
87303        var id = this.id + NAV.YEAR_SUFFIX,
87304            size = NAV.YR_MAX_DIGITS,
87305            h = html;
87306
87307        h[h.length] = '<label for="' + id + '">';
87308        h[h.length] = this.__getCfg("year", true);
87309        h[h.length] = '</label>';
87310        h[h.length] = '<input type="text" name="' + id + '" id="' + id + '" class="' + C.YEAR_CTRL + '" maxlength="' + size + '"/>';
87311        return h;
87312    },
87313
87314    /**
87315     * Renders the submit/cancel buttons for the navigator. NOTE: The contents of the array passed into this method are added to the DOM as HTML, and should be escaped by the implementor if coming from an external source.
87316     * 
87317     * @method renderButtons
87318     * @param {Array} html The HTML buffer to append the HTML to.
87319     * @return {Array} A reference to the buffer passed in.
87320     */
87321    renderButtons : function(html) {
87322        var C = YAHOO.widget.CalendarNavigator.CLASSES;
87323        var h = html;
87324
87325        h[h.length] = '<span class="' + C.BUTTON + ' ' + C.DEFAULT + '">';
87326        h[h.length] = '<button type="button" id="' + this.id + '_submit' + '">';
87327        h[h.length] = this.__getCfg("submit", true);
87328        h[h.length] = '</button>';
87329        h[h.length] = '</span>';
87330        h[h.length] = '<span class="' + C.BUTTON +'">';
87331        h[h.length] = '<button type="button" id="' + this.id + '_cancel' + '">';
87332        h[h.length] = this.__getCfg("cancel", true);
87333        h[h.length] = '</button>';
87334        h[h.length] = '</span>';
87335
87336        return h;
87337    },
87338
87339    /**
87340     * Attaches DOM event listeners to the rendered elements
87341     * <p>
87342     * The method will call applyKeyListeners, to setup keyboard specific 
87343     * listeners
87344     * </p>
87345     * @method applyListeners
87346     */
87347    applyListeners : function() {
87348        var E = YAHOO.util.Event;
87349
87350        function yearUpdateHandler() {
87351            if (this.validate()) {
87352                this.setYear(this._getYearFromUI());
87353            }
87354        }
87355
87356        function monthUpdateHandler() {
87357            this.setMonth(this._getMonthFromUI());
87358        }
87359
87360        E.on(this.submitEl, "click", this.submit, this, true);
87361        E.on(this.cancelEl, "click", this.cancel, this, true);
87362        E.on(this.yearEl, "blur", yearUpdateHandler, this, true);
87363        E.on(this.monthEl, "change", monthUpdateHandler, this, true);
87364
87365        if (this.__isIEQuirks) {
87366            YAHOO.util.Event.on(this.cal.oDomContainer, "resize", this._syncMask, this, true);
87367        }
87368
87369        this.applyKeyListeners();
87370    },
87371
87372    /**
87373     * Removes/purges DOM event listeners from the rendered elements
87374     * 
87375     * @method purgeListeners
87376     */
87377    purgeListeners : function() {
87378        var E = YAHOO.util.Event;
87379        E.removeListener(this.submitEl, "click", this.submit);
87380        E.removeListener(this.cancelEl, "click", this.cancel);
87381        E.removeListener(this.yearEl, "blur");
87382        E.removeListener(this.monthEl, "change");
87383        if (this.__isIEQuirks) {
87384            E.removeListener(this.cal.oDomContainer, "resize", this._syncMask);
87385        }
87386
87387        this.purgeKeyListeners();
87388    },
87389
87390    /**
87391     * Attaches DOM listeners for keyboard support. 
87392     * Tab/Shift-Tab looping, Enter Key Submit on Year element,
87393     * Up/Down/PgUp/PgDown year increment on Year element
87394     * <p>
87395     * NOTE: MacOSX Safari 2.x doesn't let you tab to buttons and 
87396     * MacOSX Gecko does not let you tab to buttons or select controls,
87397     * so for these browsers, Tab/Shift-Tab looping is limited to the 
87398     * elements which can be reached using the tab key.
87399     * </p>
87400     * @method applyKeyListeners
87401     */
87402    applyKeyListeners : function() {
87403        var E = YAHOO.util.Event,
87404            ua = YAHOO.env.ua;
87405
87406        // IE/Safari 3.1 doesn't fire keypress for arrow/pg keys (non-char keys)
87407        var arrowEvt = (ua.ie || ua.webkit) ? "keydown" : "keypress";
87408
87409        // - IE/Safari 3.1 doesn't fire keypress for non-char keys
87410        // - Opera doesn't allow us to cancel keydown or keypress for tab, but 
87411        //   changes focus successfully on keydown (keypress is too late to change focus - opera's already moved on).
87412        var tabEvt = (ua.ie || ua.opera || ua.webkit) ? "keydown" : "keypress";
87413
87414        // Everyone likes keypress for Enter (char keys) - whoo hoo!
87415        E.on(this.yearEl, "keypress", this._handleEnterKey, this, true);
87416
87417        E.on(this.yearEl, arrowEvt, this._handleDirectionKeys, this, true);
87418        E.on(this.lastCtrl, tabEvt, this._handleTabKey, this, true);
87419        E.on(this.firstCtrl, tabEvt, this._handleShiftTabKey, this, true);
87420    },
87421
87422    /**
87423     * Removes/purges DOM listeners for keyboard support
87424     *
87425     * @method purgeKeyListeners
87426     */
87427    purgeKeyListeners : function() {
87428        var E = YAHOO.util.Event,
87429            ua = YAHOO.env.ua;
87430
87431        var arrowEvt = (ua.ie || ua.webkit) ? "keydown" : "keypress";
87432        var tabEvt = (ua.ie || ua.opera || ua.webkit) ? "keydown" : "keypress";
87433
87434        E.removeListener(this.yearEl, "keypress", this._handleEnterKey);
87435        E.removeListener(this.yearEl, arrowEvt, this._handleDirectionKeys);
87436        E.removeListener(this.lastCtrl, tabEvt, this._handleTabKey);
87437        E.removeListener(this.firstCtrl, tabEvt, this._handleShiftTabKey);
87438    },
87439
87440    /**
87441     * Updates the Calendar/CalendarGroup's pagedate with the currently set month and year if valid.
87442     * <p>
87443     * If the currently set month/year is invalid, a validation error will be displayed and the 
87444     * Calendar/CalendarGroup's pagedate will not be updated.
87445     * </p>
87446     * @method submit
87447     */
87448    submit : function() {
87449        if (this.validate()) {
87450            this.hide();
87451
87452            this.setMonth(this._getMonthFromUI());
87453            this.setYear(this._getYearFromUI());
87454
87455            var cal = this.cal;
87456
87457            // Artificial delay, just to help the user see something changed
87458            var delay = YAHOO.widget.CalendarNavigator.UPDATE_DELAY;
87459            if (delay > 0) {
87460                var nav = this;
87461                window.setTimeout(function(){ nav._update(cal); }, delay);
87462            } else {
87463                this._update(cal);
87464            }
87465        }
87466    },
87467
87468    /**
87469     * Updates the Calendar rendered state, based on the state of the CalendarNavigator
87470     * @method _update
87471     * @param cal The Calendar instance to update
87472     * @protected
87473     */
87474    _update : function(cal) {
87475        var date = YAHOO.widget.DateMath.getDate(this.getYear() - cal.cfg.getProperty("YEAR_OFFSET"), this.getMonth(), 1);
87476        cal.cfg.setProperty("pagedate", date);
87477        cal.render();
87478    },
87479
87480    /**
87481     * Hides the navigator and mask, without updating the Calendar/CalendarGroup's state
87482     * 
87483     * @method cancel
87484     */
87485    cancel : function() {
87486        this.hide();
87487    },
87488
87489    /**
87490     * Validates the current state of the UI controls
87491     * 
87492     * @method validate
87493     * @return {Boolean} true, if the current UI state contains valid values, false if not
87494     */
87495    validate : function() {
87496        if (this._getYearFromUI() !== null) {
87497            this.clearErrors();
87498            return true;
87499        } else {
87500            this.setYearError();
87501            this.setError(this.__getCfg("invalidYear", true));
87502            return false;
87503        }
87504    },
87505
87506    /**
87507     * Displays an error message in the Navigator's error panel.
87508     * 
87509     * @method setError
87510     * @param {HTML} msg The markup for the error message to display. NOTE: The msg passed into this method is added to the DOM as HTML, and should be escaped by the implementor if coming from an external source. 
87511     */
87512    setError : function(msg) {
87513        if (this.errorEl) {
87514            this.errorEl.innerHTML = msg;
87515            this._show(this.errorEl, true);
87516        }
87517    },
87518
87519    /**
87520     * Clears the navigator's error message and hides the error panel
87521     * @method clearError 
87522     */
87523    clearError : function() {
87524        if (this.errorEl) {
87525            this.errorEl.innerHTML = "";
87526            this._show(this.errorEl, false);
87527        }
87528    },
87529
87530    /**
87531     * Displays the validation error UI for the year control
87532     * @method setYearError
87533     */
87534    setYearError : function() {
87535        YAHOO.util.Dom.addClass(this.yearEl, YAHOO.widget.CalendarNavigator.CLASSES.INVALID);
87536    },
87537
87538    /**
87539     * Removes the validation error UI for the year control
87540     * @method clearYearError
87541     */
87542    clearYearError : function() {
87543        YAHOO.util.Dom.removeClass(this.yearEl, YAHOO.widget.CalendarNavigator.CLASSES.INVALID);
87544    },
87545
87546    /**
87547     * Clears all validation and error messages in the UI
87548     * @method clearErrors
87549     */
87550    clearErrors : function() {
87551        this.clearError();
87552        this.clearYearError();
87553    },
87554
87555    /**
87556     * Sets the initial focus, based on the configured value
87557     * @method setInitialFocus
87558     */
87559    setInitialFocus : function() {
87560        var el = this.submitEl,
87561            f = this.__getCfg("initialFocus");
87562
87563        if (f && f.toLowerCase) {
87564            f = f.toLowerCase();
87565            if (f == "year") {
87566                el = this.yearEl;
87567                try {
87568                    this.yearEl.select();
87569                } catch (selErr) {
87570                    // Ignore;
87571                }
87572            } else if (f == "month") {
87573                el = this.monthEl;
87574            }
87575        }
87576
87577        if (el && YAHOO.lang.isFunction(el.focus)) {
87578            try {
87579                el.focus();
87580            } catch (focusErr) {
87581                // TODO: Fall back if focus fails?
87582            }
87583        }
87584    },
87585
87586    /**
87587     * Removes all renderered HTML elements for the Navigator from
87588     * the DOM, purges event listeners and clears (nulls) any property
87589     * references to HTML references
87590     * @method erase
87591     */
87592    erase : function() {
87593        if (this.__rendered) {
87594            this.purgeListeners();
87595
87596            // Clear out innerHTML references
87597            this.yearEl = null;
87598            this.monthEl = null;
87599            this.errorEl = null;
87600            this.submitEl = null;
87601            this.cancelEl = null;
87602            this.firstCtrl = null;
87603            this.lastCtrl = null;
87604            if (this.navEl) {
87605                this.navEl.innerHTML = "";
87606            }
87607
87608            var p = this.navEl.parentNode;
87609            if (p) {
87610                p.removeChild(this.navEl);
87611            }
87612            this.navEl = null;
87613
87614            var pm = this.maskEl.parentNode;
87615            if (pm) {
87616                pm.removeChild(this.maskEl);
87617            }
87618            this.maskEl = null;
87619            this.__rendered = false;
87620        }
87621    },
87622
87623    /**
87624     * Destroys the Navigator object and any HTML references
87625     * @method destroy
87626     */
87627    destroy : function() {
87628        this.erase();
87629        this._doc = null;
87630        this.cal = null;
87631        this.id = null;
87632    },
87633
87634    /**
87635     * Protected implementation to handle how UI elements are 
87636     * hidden/shown.
87637     *
87638     * @method _show
87639     * @protected
87640     */
87641    _show : function(el, bShow) {
87642        if (el) {
87643            YAHOO.util.Dom.setStyle(el, "display", (bShow) ? "block" : "none");
87644        }
87645    },
87646
87647    /**
87648     * Returns the month value (index), from the month UI element
87649     * @protected
87650     * @method _getMonthFromUI
87651     * @return {Number} The month index, or 0 if a UI element for the month
87652     * is not found
87653     */
87654    _getMonthFromUI : function() {
87655        if (this.monthEl) {
87656            return this.monthEl.selectedIndex;
87657        } else {
87658            return 0; // Default to Jan
87659        }
87660    },
87661
87662    /**
87663     * Returns the year value, from the Navitator's year UI element
87664     * @protected
87665     * @method _getYearFromUI
87666     * @return {Number} The year value set in the UI, if valid. null is returned if 
87667     * the UI does not contain a valid year value.
87668     */
87669    _getYearFromUI : function() {
87670        var NAV = YAHOO.widget.CalendarNavigator;
87671
87672        var yr = null;
87673        if (this.yearEl) {
87674            var value = this.yearEl.value;
87675            value = value.replace(NAV.TRIM, "$1");
87676
87677            if (NAV.YR_PATTERN.test(value)) {
87678                yr = parseInt(value, 10);
87679            }
87680        }
87681        return yr;
87682    },
87683
87684    /**
87685     * Updates the Navigator's year UI, based on the year value set on the Navigator object
87686     * @protected
87687     * @method _updateYearUI
87688     */
87689    _updateYearUI : function() {
87690        if (this.yearEl && this._year !== null) {
87691            this.yearEl.value = this._year;
87692        }
87693    },
87694
87695    /**
87696     * Updates the Navigator's month UI, based on the month value set on the Navigator object
87697     * @protected
87698     * @method _updateMonthUI
87699     */
87700    _updateMonthUI : function() {
87701        if (this.monthEl) {
87702            this.monthEl.selectedIndex = this._month;
87703        }
87704    },
87705
87706    /**
87707     * Sets up references to the first and last focusable element in the Navigator's UI
87708     * in terms of tab order (Naviagator's firstEl and lastEl properties). The references
87709     * are used to control modality by looping around from the first to the last control
87710     * and visa versa for tab/shift-tab navigation.
87711     * <p>
87712     * See <a href="#applyKeyListeners">applyKeyListeners</a>
87713     * </p>
87714     * @protected
87715     * @method _setFirstLastElements
87716     */
87717    _setFirstLastElements : function() {
87718        this.firstCtrl = this.monthEl;
87719        this.lastCtrl = this.cancelEl;
87720
87721        // Special handling for MacOSX.
87722        // - Safari 2.x can't focus on buttons
87723        // - Gecko can't focus on select boxes or buttons
87724        if (this.__isMac) {
87725            if (YAHOO.env.ua.webkit && YAHOO.env.ua.webkit < 420){
87726                this.firstCtrl = this.monthEl;
87727                this.lastCtrl = this.yearEl;
87728            }
87729            if (YAHOO.env.ua.gecko) {
87730                this.firstCtrl = this.yearEl;
87731                this.lastCtrl = this.yearEl;
87732            }
87733        }
87734    },
87735
87736    /**
87737     * Default Keyboard event handler to capture Enter 
87738     * on the Navigator's year control (yearEl)
87739     * 
87740     * @method _handleEnterKey
87741     * @protected
87742     * @param {Event} e The DOM event being handled
87743     */
87744    _handleEnterKey : function(e) {
87745        var KEYS = YAHOO.util.KeyListener.KEY;
87746
87747        if (YAHOO.util.Event.getCharCode(e) == KEYS.ENTER) {
87748            YAHOO.util.Event.preventDefault(e);
87749            this.submit();
87750        }
87751    },
87752
87753    /**
87754     * Default Keyboard event handler to capture up/down/pgup/pgdown
87755     * on the Navigator's year control (yearEl).
87756     * 
87757     * @method _handleDirectionKeys
87758     * @protected
87759     * @param {Event} e The DOM event being handled
87760     */
87761    _handleDirectionKeys : function(e) {
87762        var E = YAHOO.util.Event,
87763            KEYS = YAHOO.util.KeyListener.KEY,
87764            NAV = YAHOO.widget.CalendarNavigator;
87765
87766        var value = (this.yearEl.value) ? parseInt(this.yearEl.value, 10) : null;
87767        if (isFinite(value)) {
87768            var dir = false;
87769            switch(E.getCharCode(e)) {
87770                case KEYS.UP:
87771                    this.yearEl.value = value + NAV.YR_MINOR_INC;
87772                    dir = true;
87773                    break;
87774                case KEYS.DOWN:
87775                    this.yearEl.value = Math.max(value - NAV.YR_MINOR_INC, 0);
87776                    dir = true;
87777                    break;
87778                case KEYS.PAGE_UP:
87779                    this.yearEl.value = value + NAV.YR_MAJOR_INC;
87780                    dir = true;
87781                    break;
87782                case KEYS.PAGE_DOWN:
87783                    this.yearEl.value = Math.max(value - NAV.YR_MAJOR_INC, 0);
87784                    dir = true;
87785                    break;
87786                default:
87787                    break;
87788            }
87789            if (dir) {
87790                E.preventDefault(e);
87791                try {
87792                    this.yearEl.select();
87793                } catch(err) {
87794                    // Ignore
87795                }
87796            }
87797        }
87798    },
87799
87800    /**
87801     * Default Keyboard event handler to capture Tab 
87802     * on the last control (lastCtrl) in the Navigator.
87803     * 
87804     * @method _handleTabKey
87805     * @protected
87806     * @param {Event} e The DOM event being handled
87807     */
87808    _handleTabKey : function(e) {
87809        var E = YAHOO.util.Event,
87810            KEYS = YAHOO.util.KeyListener.KEY;
87811
87812        if (E.getCharCode(e) == KEYS.TAB && !e.shiftKey) {
87813            try {
87814                E.preventDefault(e);
87815                this.firstCtrl.focus();
87816            } catch (err) {
87817                // Ignore - mainly for focus edge cases
87818            }
87819        }
87820    },
87821
87822    /**
87823     * Default Keyboard event handler to capture Shift-Tab 
87824     * on the first control (firstCtrl) in the Navigator.
87825     * 
87826     * @method _handleShiftTabKey
87827     * @protected
87828     * @param {Event} e The DOM event being handled
87829     */
87830    _handleShiftTabKey : function(e) {
87831        var E = YAHOO.util.Event,
87832            KEYS = YAHOO.util.KeyListener.KEY;
87833
87834        if (e.shiftKey && E.getCharCode(e) == KEYS.TAB) {
87835            try {
87836                E.preventDefault(e);
87837                this.lastCtrl.focus();
87838            } catch (err) {
87839                // Ignore - mainly for focus edge cases
87840            }
87841        }
87842    },
87843
87844    /**
87845     * Retrieve Navigator configuration values from 
87846     * the parent Calendar/CalendarGroup's config value.
87847     * <p>
87848     * If it has not been set in the user provided configuration, the method will 
87849     * return the default value of the configuration property, as set in DEFAULT_CONFIG
87850     * </p>
87851     * @private
87852     * @method __getCfg
87853     * @param {String} Case sensitive property name.
87854     * @param {Boolean} true, if the property is a string property, false if not.
87855     * @return The value of the configuration property
87856     */
87857    __getCfg : function(prop, bIsStr) {
87858        var DEF_CFG = YAHOO.widget.CalendarNavigator.DEFAULT_CONFIG;
87859        var cfg = this.cal.cfg.getProperty("navigator");
87860
87861        if (bIsStr) {
87862            return (cfg !== true && cfg.strings && cfg.strings[prop]) ? cfg.strings[prop] : DEF_CFG.strings[prop];
87863        } else {
87864            return (cfg !== true && cfg[prop]) ? cfg[prop] : DEF_CFG[prop];
87865        }
87866    },
87867
87868    /**
87869     * Private flag, to identify MacOS
87870     * @private
87871     * @property __isMac
87872     */
87873    __isMac : (navigator.userAgent.toLowerCase().indexOf("macintosh") != -1)
87874
87875};
87876YAHOO.register("calendar", YAHOO.widget.Calendar, {version: "2.9.0", build: "2800"});
87877/*
87878Copyright (c) 2011, Yahoo! Inc. All rights reserved.
87879Code licensed under the BSD License:
87880http://developer.yahoo.com/yui/license.html
87881version: 2.9.0
87882*/
87883/**
87884 * @description <p>Makes an element resizable</p>
87885 * @namespace YAHOO.util
87886 * @requires yahoo, dom, dragdrop, element, event
87887 * @optional animation
87888 * @module resize
87889 */
87890(function() {
87891var D = YAHOO.util.Dom,
87892    Event = YAHOO.util.Event,
87893    Lang = YAHOO.lang;
87894
87895    /**
87896     * @constructor
87897     * @class Resize
87898     * @extends YAHOO.util.Element
87899     * @description <p>Makes an element resizable</p>
87900     * @param {String/HTMLElement} el The element to make resizable.
87901     * @param {Object} attrs Object liternal containing configuration parameters.
87902    */
87903
87904    var Resize = function(el, config) {
87905        YAHOO.log('Creating Resize Object', 'info', 'Resize');
87906        var oConfig = {
87907            element: el,
87908            attributes: config || {}
87909        };
87910
87911        Resize.superclass.constructor.call(this, oConfig.element, oConfig.attributes);    
87912    };
87913
87914    /**
87915    * @private
87916    * @static
87917    * @property _instances
87918    * @description Internal hash table for all resize instances
87919    * @type Object
87920    */ 
87921    Resize._instances = {};
87922    /**
87923    * @static
87924    * @method getResizeById 
87925    * @description Get's a resize object by the HTML id of the element associated with the Resize object.
87926    * @return {Object} The Resize Object
87927    */ 
87928    Resize.getResizeById = function(id) {
87929        if (Resize._instances[id]) {
87930            return Resize._instances[id];
87931        }
87932        YAHOO.log('No Instance Found', 'error', 'Resize');
87933        return false;
87934    };
87935
87936    YAHOO.extend(Resize, YAHOO.util.Element, {
87937        /**
87938        * @private
87939        * @property CSS_RESIZE
87940        * @description Base CSS class name
87941        * @type String
87942        */ 
87943        CSS_RESIZE: 'yui-resize',
87944        /**
87945        * @private
87946        * @property CSS_DRAG
87947        * @description Class name added when dragging is enabled
87948        * @type String
87949        */ 
87950        CSS_DRAG: 'yui-draggable',
87951        /**
87952        * @private
87953        * @property CSS_HOVER
87954        * @description Class name used for hover only handles
87955        * @type String
87956        */ 
87957        CSS_HOVER: 'yui-resize-hover',
87958        /**
87959        * @private
87960        * @property CSS_PROXY
87961        * @description Class name given to the proxy element
87962        * @type String
87963        */ 
87964        CSS_PROXY: 'yui-resize-proxy',
87965        /**
87966        * @private
87967        * @property CSS_WRAP
87968        * @description Class name given to the wrap element
87969        * @type String
87970        */ 
87971        CSS_WRAP: 'yui-resize-wrap',
87972        /**
87973        * @private
87974        * @property CSS_KNOB
87975        * @description Class name used to make the knob style handles
87976        * @type String
87977        */ 
87978        CSS_KNOB: 'yui-resize-knob',
87979        /**
87980        * @private
87981        * @property CSS_HIDDEN
87982        * @description Class name given to the wrap element to make all handles hidden
87983        * @type String
87984        */ 
87985        CSS_HIDDEN: 'yui-resize-hidden',
87986        /**
87987        * @private
87988        * @property CSS_HANDLE
87989        * @description Class name given to all handles, used as a base for single handle names as well.. Handle "t" will get this.CSS_HANDLE + '-t' as well as this.CSS_HANDLE
87990        * @type String
87991        */ 
87992        CSS_HANDLE: 'yui-resize-handle',
87993        /**
87994        * @private
87995        * @property CSS_STATUS
87996        * @description Class name given to the status element
87997        * @type String
87998        */ 
87999        CSS_STATUS: 'yui-resize-status',
88000        /**
88001        * @private
88002        * @property CSS_GHOST
88003        * @description Class name given to the wrap element when the ghost property is active
88004        * @type String
88005        */ 
88006        CSS_GHOST: 'yui-resize-ghost',
88007        /**
88008        * @private
88009        * @property CSS_RESIZING
88010        * @description Class name given to the wrap element when a resize action is taking place.
88011        * @type String
88012        */ 
88013        CSS_RESIZING: 'yui-resize-resizing',
88014        /**
88015        * @private
88016        * @property _resizeEvent
88017        * @description The mouse event used to resize with
88018        * @type Event
88019        */ 
88020        _resizeEvent: null,
88021        /**
88022        * @private
88023        * @property dd
88024        * @description The <a href="YAHOO.util.DragDrop.html">YAHOO.util.DragDrop</a> instance used if draggable is true
88025        * @type Object
88026        */ 
88027        dd: null,
88028        /** 
88029        * @private
88030        * @property browser
88031        * @description A copy of the YAHOO.env.ua property
88032        * @type Object
88033        */
88034        browser: YAHOO.env.ua,
88035        /** 
88036        * @private
88037        * @property _locked
88038        * @description A flag to show if the resize is locked
88039        * @type Boolean
88040        */
88041        _locked: null,
88042        /** 
88043        * @private
88044        * @property _positioned
88045        * @description A flag to show if the element is absolutely positioned
88046        * @type Boolean
88047        */
88048        _positioned: null,
88049        /** 
88050        * @private
88051        * @property _dds
88052        * @description An Object containing references to all of the <a href="YAHOO.util.DragDrop.html">YAHOO.util.DragDrop</a> instances used for the resize handles
88053        * @type Object
88054        */
88055        _dds: null,
88056        /** 
88057        * @private
88058        * @property _wrap
88059        * @description The HTML reference of the element wrapper
88060        * @type HTMLElement
88061        */
88062        _wrap: null,
88063        /** 
88064        * @private
88065        * @property _proxy
88066        * @description The HTML reference of the element proxy
88067        * @type HTMLElement
88068        */
88069        _proxy: null,
88070        /** 
88071        * @private
88072        * @property _handles
88073        * @description An object containing references to all of the resize handles.
88074        * @type Object
88075        */
88076        _handles: null,
88077        /** 
88078        * @private
88079        * @property _currentHandle
88080        * @description The string identifier of the currently active handle. e.g. 'r', 'br', 'tl'
88081        * @type String
88082        */
88083        _currentHandle: null,
88084        /** 
88085        * @private
88086        * @property _currentDD
88087        * @description A link to the currently active DD object
88088        * @type Object
88089        */
88090        _currentDD: null,
88091        /** 
88092        * @private
88093        * @property _cache
88094        * @description An lookup table containing key information for the element being resized. e.g. height, width, x position, y position, etc..
88095        * @type Object
88096        */
88097        _cache: null,
88098        /** 
88099        * @private
88100        * @property _active
88101        * @description Flag to show if the resize is active. Used for events.
88102        * @type Boolean
88103        */
88104        _active: null,
88105        /** 
88106        * @private
88107        * @method _createProxy
88108        * @description Creates the proxy element if the proxy config is true
88109        */
88110        _createProxy: function() {
88111            if (this.get('proxy')) {
88112                YAHOO.log('Creating the Proxy Element', 'info', 'Resize');
88113                this._proxy = document.createElement('div');
88114                this._proxy.className = this.CSS_PROXY;
88115                this._proxy.style.height = this.get('element').clientHeight + 'px';
88116                this._proxy.style.width = this.get('element').clientWidth + 'px';
88117                this._wrap.parentNode.appendChild(this._proxy);
88118            } else {
88119                YAHOO.log('No proxy element, turn off animate config option', 'info', 'Resize');
88120                this.set('animate', false);
88121            }
88122        },
88123        /** 
88124        * @private
88125        * @method _createWrap
88126        * @description Creates the wrap element if the wrap config is true. It will auto wrap the following element types: img, textarea, input, iframe, select
88127        */
88128        _createWrap: function() {
88129            YAHOO.log('Create the wrap element', 'info', 'Resize');
88130            this._positioned = false;
88131            //Force wrap for elements that can't have children 
88132            if (this.get('wrap') === false) {
88133                switch (this.get('element').tagName.toLowerCase()) {
88134                    case 'img':
88135                    case 'textarea':
88136                    case 'input':
88137                    case 'iframe':
88138                    case 'select':
88139                        YAHOO.log('Auto-wrapping the element (' + this.get('element').tagName.toLowerCase() + ')', 'warn', 'Resize');
88140                        this.set('wrap', true);
88141                        break;
88142                }
88143            }
88144            if (this.get('wrap') === true) {
88145                YAHOO.log('Creating the wrap element', 'info', 'Resize');
88146                this._wrap = document.createElement('div');
88147                this._wrap.id = this.get('element').id + '_wrap';
88148                this._wrap.className = this.CSS_WRAP;
88149                if (this.get('element').tagName.toLowerCase() == 'textarea') {
88150                    D.addClass(this._wrap, 'yui-resize-textarea');
88151                }
88152                D.setStyle(this._wrap, 'width', this.get('width') + 'px');
88153                D.setStyle(this._wrap, 'height', this.get('height') + 'px');
88154                D.setStyle(this._wrap, 'z-index', this.getStyle('z-index'));
88155                this.setStyle('z-index', 0);
88156                var pos = D.getStyle(this.get('element'), 'position');
88157                D.setStyle(this._wrap, 'position', ((pos == 'static') ? 'relative' : pos));
88158                D.setStyle(this._wrap, 'top', D.getStyle(this.get('element'), 'top'));
88159                D.setStyle(this._wrap, 'left', D.getStyle(this.get('element'), 'left'));
88160                if (D.getStyle(this.get('element'), 'position') == 'absolute') {
88161                    this._positioned = true;
88162                    YAHOO.log('The element is positioned absolute', 'info', 'Resize');
88163                    D.setStyle(this.get('element'), 'position', 'relative');
88164                    D.setStyle(this.get('element'), 'top', '0');
88165                    D.setStyle(this.get('element'), 'left', '0');
88166                }
88167                var par = this.get('element').parentNode;
88168                par.replaceChild(this._wrap, this.get('element'));
88169                this._wrap.appendChild(this.get('element'));
88170            } else {
88171                this._wrap = this.get('element');
88172                if (D.getStyle(this._wrap, 'position') == 'absolute') {
88173                    this._positioned = true;
88174                }
88175            }
88176            if (this.get('draggable')) {
88177                this._setupDragDrop();
88178            }
88179            if (this.get('hover')) {
88180                D.addClass(this._wrap, this.CSS_HOVER);
88181            }
88182            if (this.get('knobHandles')) {
88183                D.addClass(this._wrap, this.CSS_KNOB);
88184            }
88185            if (this.get('hiddenHandles')) {
88186                D.addClass(this._wrap, this.CSS_HIDDEN);
88187            }
88188            D.addClass(this._wrap, this.CSS_RESIZE);
88189        },
88190        /** 
88191        * @private
88192        * @method _setupDragDrop
88193        * @description Setup the <a href="YAHOO.util.DragDrop.html">YAHOO.util.DragDrop</a> instance on the element
88194        */
88195        _setupDragDrop: function() {
88196            YAHOO.log('Setting up the dragdrop instance on the element', 'info', 'Resize');
88197            D.addClass(this._wrap, this.CSS_DRAG);
88198            this.dd = new YAHOO.util.DD(this._wrap, this.get('id') + '-resize', { dragOnly: true, useShim: this.get('useShim') });
88199            this.dd.on('dragEvent', function() {
88200                this.fireEvent('dragEvent', arguments);
88201            }, this, true);
88202        },
88203        /** 
88204        * @private
88205        * @method _createHandles
88206        * @description Creates the handles as specified in the config
88207        */
88208        _createHandles: function() {
88209            YAHOO.log('Creating the handles', 'info', 'Resize');
88210            this._handles = {};
88211            this._dds = {};
88212            var h = this.get('handles');
88213            for (var i = 0; i < h.length; i++) {
88214                YAHOO.log('Creating handle position: ' + h[i], 'info', 'Resize');
88215                this._handles[h[i]] = document.createElement('div');
88216                this._handles[h[i]].id = D.generateId(this._handles[h[i]]);
88217                this._handles[h[i]].className = this.CSS_HANDLE + ' ' + this.CSS_HANDLE + '-' + h[i];
88218                var k = document.createElement('div');
88219                k.className = this.CSS_HANDLE + '-inner-' + h[i];
88220                this._handles[h[i]].appendChild(k);
88221                this._wrap.appendChild(this._handles[h[i]]);
88222                Event.on(this._handles[h[i]], 'mouseover', this._handleMouseOver, this, true);
88223                Event.on(this._handles[h[i]], 'mouseout', this._handleMouseOut, this, true);
88224                this._dds[h[i]] = new YAHOO.util.DragDrop(this._handles[h[i]], this.get('id') + '-handle-' + h, { useShim: this.get('useShim') });
88225                this._dds[h[i]].setPadding(15, 15, 15, 15);
88226                this._dds[h[i]].on('startDragEvent', this._handleStartDrag, this._dds[h[i]], this);
88227                this._dds[h[i]].on('mouseDownEvent', this._handleMouseDown, this._dds[h[i]], this);
88228            }
88229            YAHOO.log('Creating the Status box', 'info', 'Resize');
88230            this._status = document.createElement('span');
88231            this._status.className = this.CSS_STATUS;
88232            document.body.insertBefore(this._status, document.body.firstChild);
88233        },
88234        /** 
88235        * @private
88236        * @method _ieSelectFix
88237        * @description The function we use as the onselectstart handler when we start a drag in Internet Explorer
88238        */
88239        _ieSelectFix: function() {
88240            return false;
88241        },
88242        /** 
88243        * @private
88244        * @property _ieSelectBack
88245        * @description We will hold a copy of the current "onselectstart" method on this property, and reset it after we are done using it.
88246        */
88247        _ieSelectBack: null,
88248        /** 
88249        * @private
88250        * @method _setAutoRatio
88251        * @param {Event} ev A mouse event.
88252        * @description This method checks to see if the "autoRatio" config is set. If it is, we will check to see if the "Shift Key" is pressed. If so, we will set the config ratio to true.
88253        */
88254        _setAutoRatio: function(ev) {
88255            if (this.get('autoRatio')) {
88256                YAHOO.log('Setting up AutoRatio', 'info', 'Resize');
88257                if (ev && ev.shiftKey) {
88258                    //Shift Pressed
88259                    YAHOO.log('Shift key presses, turning on ratio', 'info', 'Resize');
88260                    this.set('ratio', true);
88261                } else {
88262                    YAHOO.log('Resetting ratio back to default', 'info', 'Resize');
88263                    this.set('ratio', this._configs.ratio._initialConfig.value);
88264                }
88265            }
88266        },
88267        /** 
88268        * @private
88269        * @method _handleMouseDown
88270        * @param {Event} ev A mouse event.
88271        * @description This method preps the autoRatio on MouseDown.
88272        */
88273        _handleMouseDown: function(ev) {
88274            if (this._locked) {
88275                YAHOO.log('Resize Locked', 'info', 'Resize');
88276                return false;
88277            }
88278            if (D.getStyle(this._wrap, 'position') == 'absolute') {
88279                this._positioned = true;
88280            }
88281            if (ev) {
88282                this._setAutoRatio(ev);
88283            }
88284            if (this.browser.ie) {
88285                this._ieSelectBack = document.body.onselectstart;
88286                document.body.onselectstart = this._ieSelectFix;
88287            }
88288        },
88289        /** 
88290        * @private
88291        * @method _handleMouseOver
88292        * @param {Event} ev A mouse event.
88293        * @description Adds CSS class names to the handles
88294        */
88295        _handleMouseOver: function(ev) {
88296            if (this._locked) {
88297                YAHOO.log('Resize Locked', 'info', 'Resize');
88298                return false;
88299            }
88300            D.removeClass(this._wrap, this.CSS_RESIZE);
88301
88302            if (this.get('hover')) {
88303                D.removeClass(this._wrap, this.CSS_HOVER);
88304            }
88305            var tar = Event.getTarget(ev);
88306            if (!D.hasClass(tar, this.CSS_HANDLE)) {
88307                tar = tar.parentNode;
88308            }
88309            if (D.hasClass(tar, this.CSS_HANDLE) && !this._active) {
88310                D.addClass(tar, this.CSS_HANDLE + '-active');
88311                for (var i in this._handles) {
88312                    if (Lang.hasOwnProperty(this._handles, i)) {
88313                        if (this._handles[i] == tar) {
88314                            D.addClass(tar, this.CSS_HANDLE + '-' + i + '-active');
88315                            break;
88316                        }
88317                    }
88318                }
88319            }
88320
88321            D.addClass(this._wrap, this.CSS_RESIZE);
88322        },
88323        /** 
88324        * @private
88325        * @method _handleMouseOut
88326        * @param {Event} ev A mouse event.
88327        * @description Removes CSS class names to the handles
88328        */
88329        _handleMouseOut: function(ev) {
88330            D.removeClass(this._wrap, this.CSS_RESIZE);
88331            if (this.get('hover') && !this._active) {
88332                D.addClass(this._wrap, this.CSS_HOVER);
88333            }
88334            var tar = Event.getTarget(ev);
88335            if (!D.hasClass(tar, this.CSS_HANDLE)) {
88336                tar = tar.parentNode;
88337            }
88338            if (D.hasClass(tar, this.CSS_HANDLE) && !this._active) {
88339                D.removeClass(tar, this.CSS_HANDLE + '-active');
88340                for (var i in this._handles) {
88341                    if (Lang.hasOwnProperty(this._handles, i)) {
88342                        if (this._handles[i] == tar) {
88343                            D.removeClass(tar, this.CSS_HANDLE + '-' + i + '-active');
88344                            break;
88345                        }
88346                    }
88347                }
88348            }
88349            D.addClass(this._wrap, this.CSS_RESIZE);
88350        },
88351        /** 
88352        * @private
88353        * @method _handleStartDrag
88354        * @param {Object} args The args passed from the CustomEvent.
88355        * @param {Object} dd The <a href="YAHOO.util.DragDrop.html">YAHOO.util.DragDrop</a> object we are working with.
88356        * @description Resizes the proxy, sets up the <a href="YAHOO.util.DragDrop.html">YAHOO.util.DragDrop</a> handlers, updates the status div and preps the cache
88357        */
88358        _handleStartDrag: function(args, dd) {
88359            YAHOO.log('startDrag', 'info', 'Resize');
88360            var tar = dd.getDragEl();
88361            if (D.hasClass(tar, this.CSS_HANDLE)) {
88362                if (D.getStyle(this._wrap, 'position') == 'absolute') {
88363                    this._positioned = true;
88364                }
88365                this._active = true;
88366                this._currentDD = dd;
88367                if (this._proxy) {
88368                    YAHOO.log('Activate proxy element', 'info', 'Resize');
88369                    this._proxy.style.visibility = 'visible';
88370                    this._proxy.style.zIndex = '1000';
88371                    this._proxy.style.height = this.get('element').clientHeight + 'px';
88372                    this._proxy.style.width = this.get('element').clientWidth + 'px';
88373                }
88374
88375                for (var i in this._handles) {
88376                    if (Lang.hasOwnProperty(this._handles, i)) {
88377                        if (this._handles[i] == tar) {
88378                            this._currentHandle = i;
88379                            var handle = '_handle_for_' + i;
88380                            D.addClass(tar, this.CSS_HANDLE + '-' + i + '-active');
88381                            dd.on('dragEvent', this[handle], this, true);
88382                            dd.on('mouseUpEvent', this._handleMouseUp, this, true);
88383                            YAHOO.log('Adding DragEvents to: ' + i, 'info', 'Resize');
88384                            break;
88385                        }
88386                    }
88387                }
88388
88389
88390                D.addClass(tar, this.CSS_HANDLE + '-active');
88391
88392                if (this.get('proxy')) {
88393                    YAHOO.log('Posiiton Proxy Element', 'info', 'Resize');
88394                    var xy = D.getXY(this.get('element'));
88395                    D.setXY(this._proxy, xy);
88396                    if (this.get('ghost')) {
88397                        YAHOO.log('Add Ghost Class', 'info', 'Resize');
88398                        this.addClass(this.CSS_GHOST);
88399                    }
88400                }
88401                D.addClass(this._wrap, this.CSS_RESIZING);
88402                this._setCache();
88403                this._updateStatus(this._cache.height, this._cache.width, this._cache.top, this._cache.left);
88404                YAHOO.log('Firing startResize Event', 'info', 'Resize');
88405                this.fireEvent('startResize', { type: 'startresize', target: this});
88406            }
88407        },
88408        /** 
88409        * @private
88410        * @method _setCache
88411        * @description Sets up the this._cache hash table.
88412        */
88413        _setCache: function() {
88414            YAHOO.log('Setting up property cache', 'info', 'Resize');
88415            this._cache.xy = D.getXY(this._wrap);
88416            D.setXY(this._wrap, this._cache.xy);
88417            this._cache.height = this.get('clientHeight');
88418            this._cache.width = this.get('clientWidth');
88419            this._cache.start.height = this._cache.height;
88420            this._cache.start.width = this._cache.width;
88421            this._cache.start.top = this._cache.xy[1];
88422            this._cache.start.left = this._cache.xy[0];
88423            this._cache.top = this._cache.xy[1];
88424            this._cache.left = this._cache.xy[0];
88425            this.set('height', this._cache.height, true);
88426            this.set('width', this._cache.width, true);
88427        },
88428        /** 
88429        * @private
88430        * @method _handleMouseUp
88431        * @param {Event} ev A mouse event.
88432        * @description Cleans up listeners, hides proxy element and removes class names.
88433        */
88434        _handleMouseUp: function(ev) {
88435            this._active = false;
88436
88437            var handle = '_handle_for_' + this._currentHandle;
88438            this._currentDD.unsubscribe('dragEvent', this[handle], this, true);
88439            this._currentDD.unsubscribe('mouseUpEvent', this._handleMouseUp, this, true);
88440
88441            if (this._proxy) {
88442                YAHOO.log('Hide Proxy Element', 'info', 'Resize');
88443                this._proxy.style.visibility = 'hidden';
88444                this._proxy.style.zIndex = '-1';
88445                if (this.get('setSize')) {
88446                    YAHOO.log('Setting Size', 'info', 'Resize');
88447                    this.resize(ev, this._cache.height, this._cache.width, this._cache.top, this._cache.left, true);
88448                } else {
88449                    YAHOO.log('Firing Resize Event', 'info', 'Resize');
88450                    this.fireEvent('resize', { ev: 'resize', target: this, height: this._cache.height, width: this._cache.width, top: this._cache.top, left: this._cache.left });
88451                }
88452
88453                if (this.get('ghost')) {
88454                    YAHOO.log('Removing Ghost Class', 'info', 'Resize');
88455                    this.removeClass(this.CSS_GHOST);
88456                }
88457            }
88458
88459            if (this.get('hover')) {
88460                D.addClass(this._wrap, this.CSS_HOVER);
88461            }
88462            if (this._status) {
88463                D.setStyle(this._status, 'display', 'none');
88464            }
88465            if (this.browser.ie) {
88466                YAHOO.log('Resetting IE onselectstart function', 'info', 'Resize');
88467                document.body.onselectstart = this._ieSelectBack;
88468            }
88469
88470            if (this.browser.ie) {
88471                D.removeClass(this._wrap, this.CSS_RESIZE);
88472            }
88473
88474            for (var i in this._handles) {
88475                if (Lang.hasOwnProperty(this._handles, i)) {
88476                    D.removeClass(this._handles[i], this.CSS_HANDLE + '-active');
88477                }
88478            }
88479            if (this.get('hover') && !this._active) {
88480                D.addClass(this._wrap, this.CSS_HOVER);
88481            }
88482            D.removeClass(this._wrap, this.CSS_RESIZING);
88483
88484            D.removeClass(this._handles[this._currentHandle], this.CSS_HANDLE + '-' + this._currentHandle + '-active');
88485            D.removeClass(this._handles[this._currentHandle], this.CSS_HANDLE + '-active');
88486
88487            if (this.browser.ie) {
88488                D.addClass(this._wrap, this.CSS_RESIZE);
88489            }
88490
88491            this._resizeEvent = null;
88492            this._currentHandle = null;
88493            
88494            if (!this.get('animate')) {
88495                this.set('height', this._cache.height, true);
88496                this.set('width', this._cache.width, true);
88497            }
88498
88499            YAHOO.log('Firing endResize Event', 'info', 'Resize');
88500            this.fireEvent('endResize', { ev: 'endResize', target: this, height: this._cache.height, width: this._cache.width, top: this._cache.top, left: this._cache.left });
88501        },
88502        /** 
88503        * @private
88504        * @method _setRatio
88505        * @param {Number} h The height offset.
88506        * @param {Number} w The with offset.
88507        * @param {Number} t The top offset.
88508        * @param {Number} l The left offset.
88509        * @description Using the Height, Width, Top & Left, it recalcuates them based on the original element size.
88510        * @return {Array} The new Height, Width, Top & Left settings
88511        */
88512        _setRatio: function(h, w, t, l) {
88513            YAHOO.log('Setting Ratio', 'info', 'Resize');
88514            var oh = h, ow = w;
88515            if (this.get('ratio')) {
88516                var orgH = this._cache.height,
88517                    orgW = this._cache.width,
88518                    nh = parseInt(this.get('height'), 10),
88519                    nw = parseInt(this.get('width'), 10),
88520                    maxH = this.get('maxHeight'),
88521                    minH = this.get('minHeight'),
88522                    maxW = this.get('maxWidth'),
88523                    minW = this.get('minWidth');
88524
88525                switch (this._currentHandle) {
88526                    case 'l':
88527                        h = nh * (w / nw);
88528                        h = Math.min(Math.max(minH, h), maxH);                        
88529                        w = nw * (h / nh);
88530                        t = (this._cache.start.top - (-((nh - h) / 2)));
88531                        l = (this._cache.start.left - (-((nw - w))));
88532                        break;
88533                    case 'r':
88534                        h = nh * (w / nw);
88535                        h = Math.min(Math.max(minH, h), maxH);                        
88536                        w = nw * (h / nh);
88537                        t = (this._cache.start.top - (-((nh - h) / 2)));
88538                        break;
88539                    case 't':
88540                        w = nw * (h / nh);
88541                        h = nh * (w / nw);
88542                        l = (this._cache.start.left - (-((nw - w) / 2)));
88543                        t = (this._cache.start.top - (-((nh - h))));
88544                        break;
88545                    case 'b':
88546                        w = nw * (h / nh);
88547                        h = nh * (w / nw);
88548                        l = (this._cache.start.left - (-((nw - w) / 2)));
88549                        break;
88550                    case 'bl':
88551                        h = nh * (w / nw);
88552                        w = nw * (h / nh);
88553                        l = (this._cache.start.left - (-((nw - w))));
88554                        break;
88555                    case 'br':
88556                        h = nh * (w / nw);
88557                        w = nw * (h / nh);
88558                        break;
88559                    case 'tl':
88560                        h = nh * (w / nw);
88561                        w = nw * (h / nh);
88562                        l = (this._cache.start.left - (-((nw - w))));
88563                        t = (this._cache.start.top - (-((nh - h))));
88564                        break;
88565                    case 'tr':
88566                        h = nh * (w / nw);
88567                        w = nw * (h / nh);
88568                        l = (this._cache.start.left);
88569                        t = (this._cache.start.top - (-((nh - h))));
88570                        break;
88571                }
88572                oh = this._checkHeight(h);
88573                ow = this._checkWidth(w);
88574                if ((oh != h) || (ow != w)) {
88575                    t = 0;
88576                    l = 0;
88577                    if (oh != h) {
88578                        ow = this._cache.width;
88579                    }
88580                    if (ow != w) {
88581                        oh = this._cache.height;
88582                    }
88583                }
88584            }
88585            return [oh, ow, t, l];
88586        },
88587        /** 
88588        * @private
88589        * @method _updateStatus
88590        * @param {Number} h The new height setting.
88591        * @param {Number} w The new width setting.
88592        * @param {Number} t The new top setting.
88593        * @param {Number} l The new left setting.
88594        * @description Using the Height, Width, Top & Left, it updates the status element with the elements sizes.
88595        */
88596        _updateStatus: function(h, w, t, l) {
88597            if (this._resizeEvent && (!Lang.isString(this._resizeEvent))) {
88598                YAHOO.log('Updating Status Box', 'info', 'Resize');
88599                h = ((h === 0) ? this._cache.start.height : h);
88600                w = ((w === 0) ? this._cache.start.width : w);
88601                var h1 = parseInt(this.get('height'), 10),
88602                    w1 = parseInt(this.get('width'), 10);
88603                
88604                if (isNaN(h1)) {
88605                    h1 = parseInt(h, 10);
88606                }
88607                if (isNaN(w1)) {
88608                    w1 = parseInt(w, 10);
88609                }
88610                var diffH = (parseInt(h, 10) - h1);
88611                var diffW = (parseInt(w, 10) - w1);
88612                this._cache.offsetHeight = diffH;
88613                this._cache.offsetWidth = diffW;
88614                if (this.get('status')) {
88615                    YAHOO.log('Showing Status Box', 'info', 'Resize');
88616                    D.setStyle(this._status, 'display', 'inline');
88617                    //This will cause IE8 to crash if the status box is hidden..
88618                    this._status.innerHTML = '<strong>' + parseInt(h, 10) + ' x ' + parseInt(w, 10) + '</strong><em>' + ((diffH > 0) ? '+' : '') + diffH + ' x ' + ((diffW > 0) ? '+' : '') + diffW + '</em>';
88619                    D.setXY(this._status, [Event.getPageX(this._resizeEvent) + 12, Event.getPageY(this._resizeEvent) + 12]);
88620                }
88621            }
88622        },
88623        /** 
88624        * @method lock
88625        * @description Lock the resize so it can't be resized
88626        * @param {Boolean} dd If the draggable config is set, lock it too
88627        * @return {<a href="YAHOO.util.Resize.html">YAHOO.util.Resize</a>} The Resize instance
88628        */
88629        lock: function(dd) {
88630            this._locked = true;
88631            if (dd && this.dd) {
88632                D.removeClass(this._wrap, 'yui-draggable');
88633                this.dd.lock();
88634            }
88635            return this;
88636        },
88637        /** 
88638        * @method unlock
88639        * @description Unlock the resize so it can be resized
88640        * @param {Boolean} dd If the draggable config is set, unlock it too
88641        * @return {<a href="YAHOO.util.Resize.html">YAHOO.util.Resize</a>} The Resize instance
88642        */
88643        unlock: function(dd) {
88644            this._locked = false;
88645            if (dd && this.dd) {
88646                D.addClass(this._wrap, 'yui-draggable');
88647                this.dd.unlock();
88648            }
88649            return this;
88650        },
88651        /** 
88652        * @method isLocked
88653        * @description Check the locked status of the resize instance
88654        * @return {Boolean}
88655        */
88656        isLocked: function() {
88657            return this._locked;
88658        },
88659        /** 
88660        * @method reset
88661        * @description Resets the element to is start state.
88662        * @return {<a href="YAHOO.util.Resize.html">YAHOO.util.Resize</a>} The Resize instance
88663        */
88664        reset: function() {
88665            YAHOO.log('Resetting to cached sizes and position', 'info', 'Resize');
88666            this.resize(null, this._cache.start.height, this._cache.start.width, this._cache.start.top, this._cache.start.left, true);
88667            return this;
88668        },
88669        /** 
88670        * @private
88671        * @method resize
88672        * @param {Event} ev The mouse event.
88673        * @param {Number} h The new height setting.
88674        * @param {Number} w The new width setting.
88675        * @param {Number} t The new top setting.
88676        * @param {Number} l The new left setting.
88677        * @param {Boolean} force Resize the element (used for proxy resize).
88678        * @param {Boolean} silent Don't fire the beforeResize Event.
88679        * @description Resizes the element, wrapper or proxy based on the data from the handlers.
88680        * @return {<a href="YAHOO.util.Resize.html">YAHOO.util.Resize</a>} The Resize instance
88681        */
88682        resize: function(ev, h, w, t, l, force, silent) {
88683            if (this._locked) {
88684                YAHOO.log('Resize Locked', 'info', 'Resize');
88685                return false;
88686            }
88687            YAHOO.log('Resize: ' + h + ',' + w + ',' + t + ',' + l, 'info', 'Resize');
88688            this._resizeEvent = ev;
88689            var el = this._wrap, anim = this.get('animate'), set = true;
88690            if (this._proxy && !force) {
88691                el = this._proxy;
88692                anim = false;
88693            }
88694            this._setAutoRatio(ev);
88695            if (this._positioned) {
88696                if (this._proxy) {
88697                    t = this._cache.top - t;
88698                    l = this._cache.left - l;
88699                }
88700            }
88701            
88702            
88703            var ratio = this._setRatio(h, w, t, l);
88704            h = parseInt(ratio[0], 10);
88705            w = parseInt(ratio[1], 10);
88706            t = parseInt(ratio[2], 10);
88707            l = parseInt(ratio[3], 10);
88708            
88709            if (t == 0) {
88710                //No Offset, get from cache
88711                t = D.getY(el);
88712            }
88713            if (l == 0) {
88714                //No Offset, get from cache
88715                l = D.getX(el);
88716            }
88717
88718            
88719
88720            if (this._positioned) {
88721                if (this._proxy && force) {
88722                    if (!anim) {
88723                        el.style.top = this._proxy.style.top;
88724                        el.style.left = this._proxy.style.left;
88725                    } else {
88726                        t = this._proxy.style.top;
88727                        l = this._proxy.style.left;
88728                    }
88729                } else {
88730                    if (!this.get('ratio') && !this._proxy) {
88731                        t = this._cache.top + -(t);
88732                        l = this._cache.left + -(l);
88733                    }
88734                    if (t) {
88735                        if (this.get('minY')) {
88736                            if (t < this.get('minY')) {
88737                                t = this.get('minY');
88738                            }
88739                        }
88740                        if (this.get('maxY')) {
88741                            if (t > this.get('maxY')) {
88742                                t = this.get('maxY');
88743                            }
88744                        }
88745                    }
88746                    if (l) {
88747                        if (this.get('minX')) {
88748                            if (l < this.get('minX')) {
88749                                l = this.get('minX');
88750                            }
88751                        }
88752                        if (this.get('maxX')) {
88753                            if ((l + w) > this.get('maxX')) {
88754                                l = (this.get('maxX') - w);
88755                            }
88756                        }
88757                    }
88758                }
88759            }
88760            if (!silent) {
88761                YAHOO.log('beforeResize', 'info', 'Resize');
88762                var beforeReturn = this.fireEvent('beforeResize', { ev: 'beforeResize', target: this, height: h, width: w, top: t, left: l });
88763                if (beforeReturn === false) {
88764                    YAHOO.log('Resized cancelled because befireResize returned false', 'info', 'Resize');
88765                    return false;
88766                }
88767            }
88768
88769            this._updateStatus(h, w, t, l);
88770
88771
88772            if (this._positioned) {
88773                if (this._proxy && force) {
88774                    //Do nothing
88775                } else {
88776                    if (t) {
88777                        D.setY(el, t);
88778                        this._cache.top = t;
88779                    }
88780                    if (l) {
88781                        D.setX(el, l);
88782                        this._cache.left = l;
88783                    }
88784                }
88785            }
88786            if (h) {
88787                if (!anim) {
88788                    set = true;
88789                    if (this._proxy && force) {
88790                        if (!this.get('setSize')) {
88791                            set = false;
88792                        }
88793                    }
88794                    if (set) {
88795                        el.style.height = h + 'px';
88796                    }
88797                    if ((this._proxy && force) || !this._proxy) {
88798                        if (this._wrap != this.get('element')) {
88799                            this.get('element').style.height = h + 'px';
88800                        }
88801                    }
88802                }
88803                this._cache.height = h;
88804            }
88805            if (w) {
88806                this._cache.width = w;
88807                if (!anim) {
88808                    set = true;
88809                    if (this._proxy && force) {
88810                        if (!this.get('setSize')) {
88811                            set = false;
88812                        }
88813                    }
88814                    if (set) {
88815                        el.style.width = w + 'px';
88816                    }
88817                    if ((this._proxy && force) || !this._proxy) {
88818                        if (this._wrap != this.get('element')) {
88819                            this.get('element').style.width = w + 'px';
88820                        }
88821                    }
88822                }
88823            }
88824            if (anim) {
88825                if (YAHOO.util.Anim) {
88826                    var _anim = new YAHOO.util.Anim(el, {
88827                        height: {
88828                            to: this._cache.height
88829                        },
88830                        width: {
88831                            to: this._cache.width
88832                        }
88833                    }, this.get('animateDuration'), this.get('animateEasing'));
88834                    if (this._positioned) {
88835                        if (t) {
88836                            _anim.attributes.top = {
88837                                to: parseInt(t, 10)
88838                            };
88839                        }
88840                        if (l) {
88841                            _anim.attributes.left = {
88842                                to: parseInt(l, 10)
88843                            };
88844                        }
88845                    }
88846
88847                    if (this._wrap != this.get('element')) {
88848                        _anim.onTween.subscribe(function() {
88849                            this.get('element').style.height = el.style.height;
88850                            this.get('element').style.width = el.style.width;
88851                        }, this, true);
88852                    }
88853
88854                    _anim.onComplete.subscribe(function() {
88855                        YAHOO.log('Animation onComplete fired', 'info', 'Resize');
88856                        this.set('height', h);
88857                        this.set('width', w);
88858                        this.fireEvent('resize', { ev: 'resize', target: this, height: h, width: w, top: t, left: l });
88859                    }, this, true);
88860                    _anim.animate();
88861
88862                }
88863            } else {
88864                if (this._proxy && !force) {
88865                    YAHOO.log('proxyResize', 'info', 'Resize');
88866                    this.fireEvent('proxyResize', { ev: 'proxyresize', target: this, height: h, width: w, top: t, left: l });
88867                } else {
88868                    YAHOO.log('resize', 'info', 'Resize');
88869                    this.fireEvent('resize', { ev: 'resize', target: this, height: h, width: w, top: t, left: l });
88870                }
88871            }
88872            return this;
88873        },
88874        /** 
88875        * @private
88876        * @method _handle_for_br
88877        * @param {Object} args The arguments from the CustomEvent.
88878        * @description Handles the sizes for the Bottom Right handle.
88879        */
88880        _handle_for_br: function(args) {
88881            YAHOO.log('Handle BR', 'info', 'Resize');
88882            var newW = this._setWidth(args.e);
88883            var newH = this._setHeight(args.e);
88884            this.resize(args.e, newH, newW, 0, 0);
88885        },
88886        /** 
88887        * @private
88888        * @method _handle_for_bl
88889        * @param {Object} args The arguments from the CustomEvent.
88890        * @description Handles the sizes for the Bottom Left handle.
88891        */
88892        _handle_for_bl: function(args) {
88893            YAHOO.log('Handle BL', 'info', 'Resize');
88894            var newW = this._setWidth(args.e, true);
88895            var newH = this._setHeight(args.e);
88896            var l = (newW - this._cache.width);
88897            this.resize(args.e, newH, newW, 0, l);
88898        },
88899        /** 
88900        * @private
88901        * @method _handle_for_tl
88902        * @param {Object} args The arguments from the CustomEvent.
88903        * @description Handles the sizes for the Top Left handle.
88904        */
88905        _handle_for_tl: function(args) {
88906            YAHOO.log('Handle TL', 'info', 'Resize');
88907            var newW = this._setWidth(args.e, true);
88908            var newH = this._setHeight(args.e, true);
88909            var t = (newH - this._cache.height);
88910            var l = (newW - this._cache.width);
88911            this.resize(args.e, newH, newW, t, l);
88912        },
88913        /** 
88914        * @private
88915        * @method _handle_for_tr
88916        * @param {Object} args The arguments from the CustomEvent.
88917        * @description Handles the sizes for the Top Right handle.
88918        */
88919        _handle_for_tr: function(args) {
88920            YAHOO.log('Handle TR', 'info', 'Resize');
88921            var newW = this._setWidth(args.e);
88922            var newH = this._setHeight(args.e, true);
88923            var t = (newH - this._cache.height);
88924            this.resize(args.e, newH, newW, t, 0);
88925        },
88926        /** 
88927        * @private
88928        * @method _handle_for_r
88929        * @param {Object} args The arguments from the CustomEvent.
88930        * @description Handles the sizes for the Right handle.
88931        */
88932        _handle_for_r: function(args) {
88933            YAHOO.log('Handle R', 'info', 'Resize');
88934            this._dds.r.setYConstraint(0,0);
88935            var newW = this._setWidth(args.e);
88936            this.resize(args.e, 0, newW, 0, 0);
88937        },
88938        /** 
88939        * @private
88940        * @method _handle_for_l
88941        * @param {Object} args The arguments from the CustomEvent.
88942        * @description Handles the sizes for the Left handle.
88943        */
88944        _handle_for_l: function(args) {
88945            YAHOO.log('Handle L', 'info', 'Resize');
88946            this._dds.l.setYConstraint(0,0);
88947            var newW = this._setWidth(args.e, true);
88948            var l = (newW - this._cache.width);
88949            this.resize(args.e, 0, newW, 0, l);
88950        },
88951        /** 
88952        * @private
88953        * @method _handle_for_b
88954        * @param {Object} args The arguments from the CustomEvent.
88955        * @description Handles the sizes for the Bottom handle.
88956        */
88957        _handle_for_b: function(args) {
88958            YAHOO.log('Handle B', 'info', 'Resize');
88959            this._dds.b.setXConstraint(0,0);
88960            var newH = this._setHeight(args.e);
88961            this.resize(args.e, newH, 0, 0, 0);
88962        },
88963        /** 
88964        * @private
88965        * @method _handle_for_t
88966        * @param {Object} args The arguments from the CustomEvent.
88967        * @description Handles the sizes for the Top handle.
88968        */
88969        _handle_for_t: function(args) {
88970            YAHOO.log('Handle T', 'info', 'Resize');
88971            this._dds.t.setXConstraint(0,0);
88972            var newH = this._setHeight(args.e, true);
88973            var t = (newH - this._cache.height);
88974            this.resize(args.e, newH, 0, t, 0);
88975        },
88976        /** 
88977        * @private
88978        * @method _setWidth
88979        * @param {Event} ev The mouse event.
88980        * @param {Boolean} flip Argument to determine the direction of the movement.
88981        * @description Calculates the width based on the mouse event.
88982        * @return {Number} The new value
88983        */
88984        _setWidth: function(ev, flip) {
88985            YAHOO.log('Set width based on Event', 'info', 'Resize');
88986            var xy = this._cache.xy[0],
88987                w = this._cache.width,
88988                x = Event.getPageX(ev),
88989                nw = (x - xy);
88990
88991                if (flip) {
88992                    nw = (xy - x) + parseInt(this.get('width'), 10);
88993                }
88994                
88995                nw = this._snapTick(nw, this.get('xTicks'));
88996                nw = this._checkWidth(nw);
88997            return nw;
88998        },
88999        /** 
89000        * @private
89001        * @method _checkWidth
89002        * @param {Number} w The width to check.
89003        * @description Checks the value passed against the maxWidth and minWidth.
89004        * @return {Number} the new value
89005        */
89006        _checkWidth: function(w) {
89007            YAHOO.log('Checking the min/max width', 'info', 'Resize');
89008            if (this.get('minWidth')) {
89009                if (w <= this.get('minWidth')) {
89010                    YAHOO.log('Using minWidth', 'info', 'Resize');
89011                    w = this.get('minWidth');
89012                }
89013            }
89014            if (this.get('maxWidth')) {
89015                if (w >= this.get('maxWidth')) {
89016                    YAHOO.log('Using Max Width', 'info', 'Resize');
89017                    w = this.get('maxWidth');
89018                }
89019            }
89020            return w;
89021        },
89022        /** 
89023        * @private
89024        * @method _checkHeight
89025        * @param {Number} h The height to check.
89026        * @description Checks the value passed against the maxHeight and minHeight.
89027        * @return {Number} The new value
89028        */
89029        _checkHeight: function(h) {
89030            YAHOO.log('Checking the min/max height', 'info', 'Resize');
89031            if (this.get('minHeight')) {
89032                if (h <= this.get('minHeight')) {
89033                    YAHOO.log('Using minHeight', 'info', 'Resize');
89034                    h = this.get('minHeight');
89035                }
89036            }
89037            if (this.get('maxHeight')) {
89038                if (h >= this.get('maxHeight')) {
89039                    YAHOO.log('using maxHeight', 'info', 'Resize');
89040                    h = this.get('maxHeight');
89041                }
89042            }
89043            return h;
89044        },
89045        /** 
89046        * @private
89047        * @method _setHeight
89048        * @param {Event} ev The mouse event.
89049        * @param {Boolean} flip Argument to determine the direction of the movement.
89050        * @description Calculated the height based on the mouse event.
89051        * @return {Number} The new value
89052        */
89053        _setHeight: function(ev, flip) {
89054            YAHOO.log('Setting the height based on the Event', 'info', 'Resize');
89055            var xy = this._cache.xy[1],
89056                h = this._cache.height,
89057                y = Event.getPageY(ev),
89058                nh = (y - xy);
89059
89060                if (flip) {
89061                    nh = (xy - y) + parseInt(this.get('height'), 10);
89062                }
89063                nh = this._snapTick(nh, this.get('yTicks'));
89064                nh = this._checkHeight(nh);
89065                
89066            return nh;
89067        },
89068        /** 
89069        * @private
89070        * @method _snapTick
89071        * @param {Number} size The size to tick against.
89072        * @param {Number} pix The tick pixels.
89073        * @description Adjusts the number based on the ticks used.
89074        * @return {Number} the new snapped position
89075        */
89076        _snapTick: function(size, pix) {
89077            YAHOO.log('Snapping to ticks', 'info', 'Resize');
89078            if (!size || !pix) {
89079                return size;
89080            }
89081            var _s = size;
89082            var _x = size % pix;
89083            if (_x > 0) {
89084                if (_x > (pix / 2)) {
89085                    _s = size + (pix - _x);
89086                } else {
89087                    _s = size - _x;
89088                }
89089            }
89090            return _s;
89091        },
89092        /** 
89093        * @private
89094        * @method init
89095        * @description The Resize class's initialization method
89096        */        
89097        init: function(p_oElement, p_oAttributes) {
89098            YAHOO.log('init', 'info', 'Resize');
89099            this._locked = false;
89100            this._cache = {
89101                xy: [],
89102                height: 0,
89103                width: 0,
89104                top: 0,
89105                left: 0,
89106                offsetHeight: 0,
89107                offsetWidth: 0,
89108                start: {
89109                    height: 0,
89110                    width: 0,
89111                    top: 0,
89112                    left: 0
89113                }
89114            };
89115
89116            Resize.superclass.init.call(this, p_oElement, p_oAttributes);
89117
89118            this.set('setSize', this.get('setSize'));
89119
89120            if (p_oAttributes.height) {
89121                this.set('height', parseInt(p_oAttributes.height, 10));
89122            } else {
89123                var h = this.getStyle('height');
89124                if (h == 'auto') {
89125                    this.set('height', parseInt(this.get('element').offsetHeight, 10));
89126                }
89127            }
89128            if (p_oAttributes.width) {
89129                this.set('width', parseInt(p_oAttributes.width, 10));
89130            } else {
89131                var w = this.getStyle('width');
89132                if (w == 'auto') {
89133                    this.set('width', parseInt(this.get('element').offsetWidth, 10));
89134                }
89135            }
89136            
89137            var id = p_oElement;
89138            if (!Lang.isString(id)) {
89139                id = D.generateId(id);
89140            }
89141            Resize._instances[id] = this;
89142
89143            this._active = false;
89144            
89145            this._createWrap();
89146            this._createProxy();
89147            this._createHandles();
89148
89149        },
89150        /**
89151        * @method getProxyEl
89152        * @description Get the HTML reference for the proxy, returns null if no proxy.
89153        * @return {HTMLElement} The proxy element
89154        */      
89155        getProxyEl: function() {
89156            return this._proxy;
89157        },
89158        /**
89159        * @method getWrapEl
89160        * @description Get the HTML reference for the wrap element, returns the current element if not wrapped.
89161        * @return {HTMLElement} The wrap element
89162        */      
89163        getWrapEl: function() {
89164            return this._wrap;
89165        },
89166        /**
89167        * @method getStatusEl
89168        * @description Get the HTML reference for the status element.
89169        * @return {HTMLElement} The status element
89170        */      
89171        getStatusEl: function() {
89172            return this._status;
89173        },
89174        /**
89175        * @method getActiveHandleEl
89176        * @description Get the HTML reference for the currently active resize handle.
89177        * @return {HTMLElement} The handle element that is active
89178        */      
89179        getActiveHandleEl: function() {
89180            return this._handles[this._currentHandle];
89181        },
89182        /**
89183        * @method isActive
89184        * @description Returns true or false if a resize operation is currently active on the element.
89185        * @return {Boolean}
89186        */      
89187        isActive: function() {
89188            return ((this._active) ? true : false);
89189        },
89190        /**
89191        * @private
89192        * @method initAttributes
89193        * @description Initializes all of the configuration attributes used to create a resizable element.
89194        * @param {Object} attr Object literal specifying a set of 
89195        * configuration attributes used to create the utility.
89196        */      
89197        initAttributes: function(attr) {
89198            Resize.superclass.initAttributes.call(this, attr);
89199
89200            /**
89201            * @attribute useShim
89202            * @description This setting will be passed to the DragDrop instances on the resize handles and for the draggable property.
89203            * This property should be used if you want the resize handles to work over iframe and other elements.
89204            * @type Boolean
89205            */
89206            this.setAttributeConfig('useShim', {
89207                value: ((attr.useShim === true) ? true : false),
89208                validator: YAHOO.lang.isBoolean,
89209                method: function(u) {
89210                    for (var i in this._dds) {
89211                        if (Lang.hasOwnProperty(this._dds, i)) {
89212                            this._dds[i].useShim = u;
89213                        }
89214                    }
89215                    if (this.dd) {
89216                        this.dd.useShim = u;
89217                    }
89218                }
89219            });
89220            /**
89221            * @attribute setSize
89222            * @description Set the size of the resized element, if set to false the element will not be auto resized,
89223            * the resize event will contain the dimensions so the end user can resize it on their own.
89224            * This setting will only work with proxy set to true and animate set to false.
89225            * @type Boolean
89226            */
89227            this.setAttributeConfig('setSize', {
89228                value: ((attr.setSize === false) ? false : true),
89229                validator: YAHOO.lang.isBoolean
89230            });
89231
89232            /**
89233            * @attribute wrap
89234            * @description Should we wrap the element
89235            * @type Boolean
89236            */
89237            this.setAttributeConfig('wrap', {
89238                writeOnce: true,
89239                validator: YAHOO.lang.isBoolean,
89240                value: attr.wrap || false
89241            });
89242
89243            /**
89244            * @attribute handles
89245            * @description The handles to use (any combination of): 't', 'b', 'r', 'l', 'bl', 'br', 'tl', 'tr'. Defaults to: ['r', 'b', 'br'].
89246            * Can use a shortcut of All. Note: 8 way resizing should be done on an element that is absolutely positioned.
89247            * @type Array
89248            */
89249            this.setAttributeConfig('handles', {
89250                writeOnce: true,
89251                value: attr.handles || ['r', 'b', 'br'],
89252                validator: function(handles) {
89253                    if (Lang.isString(handles) && handles.toLowerCase() == 'all') {
89254                        handles = ['t', 'b', 'r', 'l', 'bl', 'br', 'tl', 'tr'];
89255                    }
89256                    if (!Lang.isArray(handles)) {
89257                        handles = handles.replace(/, /g, ',');
89258                        handles = handles.split(',');
89259                    }
89260                    this._configs.handles.value = handles;
89261                }
89262            });
89263
89264            /**
89265            * @attribute width
89266            * @description The width of the element
89267            * @type Number
89268            */
89269            this.setAttributeConfig('width', {
89270                value: attr.width || parseInt(this.getStyle('width'), 10),
89271                validator: YAHOO.lang.isNumber,
89272                method: function(width) {
89273                    width = parseInt(width, 10);
89274                    if (width > 0) {
89275                        if (this.get('setSize')) {
89276                            this.setStyle('width', width + 'px');
89277                        }
89278                        this._cache.width = width;
89279                        this._configs.width.value = width;
89280                    }
89281                }
89282            });
89283
89284            /**
89285            * @attribute height
89286            * @description The height of the element
89287            * @type Number
89288            */
89289            this.setAttributeConfig('height', {
89290                value: attr.height || parseInt(this.getStyle('height'), 10),
89291                validator: YAHOO.lang.isNumber,
89292                method: function(height) {
89293                    height = parseInt(height, 10);
89294                    if (height > 0) {
89295                        if (this.get('setSize')) {
89296                            this.setStyle('height', height + 'px');
89297                        }
89298                        this._cache.height = height;
89299                        this._configs.height.value = height;
89300                    }
89301                }
89302            });
89303
89304            /**
89305            * @attribute minWidth
89306            * @description The minimum width of the element
89307            * @type Number
89308            */
89309            this.setAttributeConfig('minWidth', {
89310                value: attr.minWidth || 15,
89311                validator: YAHOO.lang.isNumber
89312            });
89313
89314            /**
89315            * @attribute minHeight
89316            * @description The minimum height of the element
89317            * @type Number
89318            */
89319            this.setAttributeConfig('minHeight', {
89320                value: attr.minHeight || 15,
89321                validator: YAHOO.lang.isNumber
89322            });
89323
89324            /**
89325            * @attribute maxWidth
89326            * @description The maximum width of the element
89327            * @type Number
89328            */
89329            this.setAttributeConfig('maxWidth', {
89330                value: attr.maxWidth || 10000,
89331                validator: YAHOO.lang.isNumber
89332            });
89333
89334            /**
89335            * @attribute maxHeight
89336            * @description The maximum height of the element
89337            * @type Number
89338            */
89339            this.setAttributeConfig('maxHeight', {
89340                value: attr.maxHeight || 10000,
89341                validator: YAHOO.lang.isNumber
89342            });
89343
89344            /**
89345            * @attribute minY
89346            * @description The minimum y coord of the element
89347            * @type Number
89348            */
89349            this.setAttributeConfig('minY', {
89350                value: attr.minY || false
89351            });
89352
89353            /**
89354            * @attribute minX
89355            * @description The minimum x coord of the element
89356            * @type Number
89357            */
89358            this.setAttributeConfig('minX', {
89359                value: attr.minX || false
89360            });
89361            /**
89362            * @attribute maxY
89363            * @description The max y coord of the element
89364            * @type Number
89365            */
89366            this.setAttributeConfig('maxY', {
89367                value: attr.maxY || false
89368            });
89369
89370            /**
89371            * @attribute maxX
89372            * @description The max x coord of the element
89373            * @type Number
89374            */
89375            this.setAttributeConfig('maxX', {
89376                value: attr.maxX || false
89377            });
89378
89379            /**
89380            * @attribute animate
89381            * @description Should be use animation to resize the element (can only be used if we use proxy).
89382            * @type Boolean
89383            */
89384            this.setAttributeConfig('animate', {
89385                value: attr.animate || false,
89386                validator: function(value) {
89387                    var ret = true;
89388                    if (!YAHOO.util.Anim) {
89389                        ret = false;
89390                    }
89391                    return ret;
89392                }               
89393            });
89394
89395            /**
89396            * @attribute animateEasing
89397            * @description The Easing to apply to the animation.
89398            * @type Object
89399            */
89400            this.setAttributeConfig('animateEasing', {
89401                value: attr.animateEasing || function() {
89402                    var easing = false;
89403                    if (YAHOO.util.Easing && YAHOO.util.Easing.easeOut) {
89404                        easing = YAHOO.util.Easing.easeOut;
89405                    }
89406                    return easing;
89407                }()
89408            });
89409
89410            /**
89411            * @attribute animateDuration
89412            * @description The Duration to apply to the animation.
89413            * @type Number
89414            */
89415            this.setAttributeConfig('animateDuration', {
89416                value: attr.animateDuration || 0.5
89417            });
89418
89419            /**
89420            * @attribute proxy
89421            * @description Resize a proxy element instead of the real element.
89422            * @type Boolean
89423            */
89424            this.setAttributeConfig('proxy', {
89425                value: attr.proxy || false,
89426                validator: YAHOO.lang.isBoolean
89427            });
89428
89429            /**
89430            * @attribute ratio
89431            * @description Maintain the element's ratio when resizing.
89432            * @type Boolean
89433            */
89434            this.setAttributeConfig('ratio', {
89435                value: attr.ratio || false,
89436                validator: YAHOO.lang.isBoolean
89437            });
89438
89439            /**
89440            * @attribute ghost
89441            * @description Apply an opacity filter to the element being resized (only works with proxy).
89442            * @type Boolean
89443            */
89444            this.setAttributeConfig('ghost', {
89445                value: attr.ghost || false,
89446                validator: YAHOO.lang.isBoolean
89447            });
89448
89449            /**
89450            * @attribute draggable
89451            * @description A convienence method to make the element draggable
89452            * @type Boolean
89453            */
89454            this.setAttributeConfig('draggable', {
89455                value: attr.draggable || false,
89456                validator: YAHOO.lang.isBoolean,
89457                method: function(dd) {
89458                    if (dd && this._wrap && !this.dd) {
89459                        this._setupDragDrop();
89460                    } else {
89461                        if (this.dd) {
89462                            if (dd) {
89463                                //activating an old DD instance..
89464                                D.addClass(this._wrap, this.CSS_DRAG);
89465                                this.dd.DDM.regDragDrop(this.dd, "default");
89466                            } else {
89467                                D.removeClass(this._wrap, this.CSS_DRAG);
89468                                this.dd.unreg();
89469                            }
89470                        }
89471                    }
89472                }
89473            });
89474
89475            /**
89476            * @attribute hover
89477            * @description Only show the handles when they are being moused over.
89478            * @type Boolean
89479            */
89480            this.setAttributeConfig('hover', {
89481                value: attr.hover || false,
89482                validator: YAHOO.lang.isBoolean
89483            });
89484
89485            /**
89486            * @attribute hiddenHandles
89487            * @description Don't show the handles, just use the cursor to the user.
89488            * @type Boolean
89489            */
89490            this.setAttributeConfig('hiddenHandles', {
89491                value: attr.hiddenHandles || false,
89492                validator: YAHOO.lang.isBoolean
89493            });
89494
89495            /**
89496            * @attribute knobHandles
89497            * @description Use the smaller handles, instead if the full size handles.
89498            * @type Boolean
89499            */
89500            this.setAttributeConfig('knobHandles', {
89501                value: attr.knobHandles || false,
89502                validator: YAHOO.lang.isBoolean
89503            });
89504
89505            /**
89506            * @attribute xTicks
89507            * @description The number of x ticks to span the resize to.
89508            * @type Number or False
89509            */
89510            this.setAttributeConfig('xTicks', {
89511                value: attr.xTicks || false
89512            });
89513
89514            /**
89515            * @attribute yTicks
89516            * @description The number of y ticks to span the resize to.
89517            * @type Number or False
89518            */
89519            this.setAttributeConfig('yTicks', {
89520                value: attr.yTicks || false
89521            });
89522
89523            /**
89524            * @attribute status
89525            * @description Show the status (new size) of the resize.
89526            * @type Boolean
89527            */
89528            this.setAttributeConfig('status', {
89529                value: attr.status || false,
89530                validator: YAHOO.lang.isBoolean
89531            });
89532
89533            /**
89534            * @attribute autoRatio
89535            * @description Using the shift key during a resize will toggle the ratio config.
89536            * @type Boolean
89537            */
89538            this.setAttributeConfig('autoRatio', {
89539                value: attr.autoRatio || false,
89540                validator: YAHOO.lang.isBoolean
89541            });
89542
89543        },
89544        /**
89545        * @method destroy
89546        * @description Destroys the resize object and all of it's elements & listeners.
89547        */        
89548        destroy: function() {
89549            YAHOO.log('Destroying Resize', 'info', 'Resize');
89550            for (var h in this._handles) {
89551                if (Lang.hasOwnProperty(this._handles, h)) {
89552                    Event.purgeElement(this._handles[h]);
89553                    this._handles[h].parentNode.removeChild(this._handles[h]);
89554                }
89555            }
89556            if (this._proxy) {
89557                this._proxy.parentNode.removeChild(this._proxy);
89558            }
89559            if (this._status) {
89560                this._status.parentNode.removeChild(this._status);
89561            }
89562            if (this.dd) {
89563                this.dd.unreg();
89564                D.removeClass(this._wrap, this.CSS_DRAG);
89565            }
89566            if (this._wrap != this.get('element')) {
89567                this.setStyle('position', (this._positioned ? 'absolute' : 'relative'));
89568                this.setStyle('top', D.getStyle(this._wrap, 'top'));
89569                this.setStyle('left',D.getStyle(this._wrap, 'left'));
89570                this._wrap.parentNode.replaceChild(this.get('element'), this._wrap);
89571            }
89572            this.removeClass(this.CSS_RESIZE);
89573
89574            delete YAHOO.util.Resize._instances[this.get('id')];
89575            //Brutal Object Destroy
89576            for (var i in this) {
89577                if (Lang.hasOwnProperty(this, i)) {
89578                    this[i] = null;
89579                    delete this[i];
89580                }
89581            }
89582        },
89583        /**
89584        * @method toString
89585        * @description Returns a string representing the Resize Object.
89586        * @return {String}
89587        */        
89588        toString: function() {
89589            if (this.get) {
89590                return 'Resize (#' + this.get('id') + ')';
89591            }
89592            return 'Resize Utility';
89593        }
89594    });
89595
89596    YAHOO.util.Resize = Resize;
89597 
89598/**
89599* @event dragEvent
89600* @description Fires when the <a href="YAHOO.util.DragDrop.html">YAHOO.util.DragDrop</a> dragEvent is fired for the config option draggable.
89601* @type YAHOO.util.CustomEvent
89602*/
89603/**
89604* @event startResize
89605* @description Fires when a resize action is started.
89606* @type YAHOO.util.CustomEvent
89607*/
89608/**
89609* @event endResize
89610* @description Fires when the mouseUp event from the Drag Instance fires.
89611* @type YAHOO.util.CustomEvent
89612*/
89613/**
89614* @event resize
89615* @description Fires on every element resize (only fires once when used with proxy config setting).
89616* @type YAHOO.util.CustomEvent
89617*/
89618/**
89619* @event beforeResize
89620* @description Fires before every element resize after the size calculations, returning false will stop the resize.
89621* @type YAHOO.util.CustomEvent
89622*/
89623/**
89624* @event proxyResize
89625* @description Fires on every proxy resize (only fires when used with proxy config setting).
89626* @type YAHOO.util.CustomEvent
89627*/
89628
89629})();
89630
89631YAHOO.register("resize", YAHOO.util.Resize, {version: "2.9.0", build: "2800"});
89632/*
89633Copyright (c) 2011, Yahoo! Inc. All rights reserved.
89634Code licensed under the BSD License:
89635http://developer.yahoo.com/yui/license.html
89636version: 2.9.0
89637*/
89638/**
89639 * The Browser History Manager provides the ability to use the back/forward
89640 * navigation buttons in a DHTML application. It also allows a DHTML
89641 * application to be bookmarked in a specific state.
89642 *
89643 * This library requires the following static markup:
89644 *
89645 * &lt;iframe id="yui-history-iframe" src="path-to-real-asset-in-same-domain"&gt;&lt;/iframe&gt;
89646 * &lt;input id="yui-history-field" type="hidden"&gt;
89647 *
89648 * @module history
89649 * @requires yahoo,event
89650 * @namespace YAHOO.util
89651 * @title Browser History Manager
89652 */
89653
89654/**
89655 * The History class provides the ability to use the back/forward navigation
89656 * buttons in a DHTML application. It also allows a DHTML application to
89657 * be bookmarked in a specific state.
89658 *
89659 * @class History
89660 * @constructor
89661 */
89662YAHOO.util.History = (function () {
89663
89664    /**
89665     * Our hidden IFrame used to store the browsing history.
89666     *
89667     * @property _histFrame
89668     * @type HTMLIFrameElement
89669     * @default null
89670     * @private
89671     */
89672    var _histFrame = null;
89673
89674    /**
89675     * INPUT field (with type="hidden" or type="text") or TEXTAREA.
89676     * This field keeps the value of the initial state, current state
89677     * the list of all states across pages within a single browser session.
89678     *
89679     * @property _stateField
89680     * @type HTMLInputElement|HTMLTextAreaElement
89681     * @default null
89682     * @private
89683     */
89684    var _stateField = null;
89685
89686    /**
89687     * Flag used to tell whether YAHOO.util.History.initialize has been called.
89688     *
89689     * @property _initialized
89690     * @type boolean
89691     * @default false
89692     * @private
89693     */
89694    var _initialized = false;
89695
89696    /**
89697     * List of registered modules.
89698     *
89699     * @property _modules
89700     * @type array
89701     * @default []
89702     * @private
89703     */
89704    var _modules = [];
89705
89706    /**
89707     * List of fully qualified states. This is used only by Safari.
89708     *
89709     * @property _fqstates
89710     * @type array
89711     * @default []
89712     * @private
89713     */
89714    var _fqstates = [];
89715
89716    /**
89717     * location.hash is a bit buggy on Opera. I have seen instances where
89718     * navigating the history using the back/forward buttons, and hence
89719     * changing the URL, would not change location.hash. That's ok, the
89720     * implementation of an equivalent is trivial.
89721     *
89722     * @method _getHash
89723     * @return {string} The hash portion of the document's location
89724     * @private
89725     */
89726    function _getHash() {
89727        var i, href;
89728        href = self.location.href;
89729        i = href.indexOf("#");
89730        return i >= 0 ? href.substr(i + 1) : null;
89731    }
89732
89733    /**
89734     * Stores all the registered modules' initial state and current state.
89735     * On Safari, we also store all the fully qualified states visited by
89736     * the application within a single browser session. The storage takes
89737     * place in the form field specified during initialization.
89738     *
89739     * @method _storeStates
89740     * @private
89741     */
89742    function _storeStates() {
89743
89744        var moduleName, moduleObj, initialStates = [], currentStates = [];
89745
89746        for (moduleName in _modules) {
89747            if (YAHOO.lang.hasOwnProperty(_modules, moduleName)) {
89748                moduleObj = _modules[moduleName];
89749                initialStates.push(moduleName + "=" + moduleObj.initialState);
89750                currentStates.push(moduleName + "=" + moduleObj.currentState);
89751            }
89752        }
89753
89754        _stateField.value = initialStates.join("&") + "|" + currentStates.join("&");
89755    }
89756
89757    /**
89758     * Sets the new currentState attribute of all modules depending on the new
89759     * fully qualified state. Also notifies the modules which current state has
89760     * changed.
89761     *
89762     * @method _handleFQStateChange
89763     * @param {string} fqstate Fully qualified state
89764     * @private
89765     */
89766    function _handleFQStateChange(fqstate) {
89767
89768        var i, len, moduleName, moduleObj, modules, states, tokens, currentState;
89769
89770        if (!fqstate) {
89771            // Notifies all modules
89772            for (moduleName in _modules) {
89773                if (YAHOO.lang.hasOwnProperty(_modules, moduleName)) {
89774                    moduleObj = _modules[moduleName];
89775                    moduleObj.currentState = moduleObj.initialState;
89776                    moduleObj.onStateChange(_decode(moduleObj.currentState));
89777                }
89778            }
89779            return;
89780        }
89781
89782        modules = [];
89783        states = fqstate.split("&");
89784        for (i = 0, len = states.length; i < len; i++) {
89785            tokens = states[i].split("=");
89786            if (tokens.length === 2) {
89787                moduleName = tokens[0];
89788                currentState = tokens[1];
89789                modules[moduleName] = currentState;
89790            }
89791        }
89792
89793        for (moduleName in _modules) {
89794            if (YAHOO.lang.hasOwnProperty(_modules, moduleName)) {
89795                moduleObj = _modules[moduleName];
89796                currentState = modules[moduleName];
89797                if (!currentState || moduleObj.currentState !== currentState) {
89798                    moduleObj.currentState = typeof currentState === 'undefined' ? moduleObj.initialState : currentState;
89799                    moduleObj.onStateChange(_decode(moduleObj.currentState));
89800                }
89801            }
89802        }
89803    }
89804
89805    /**
89806     * Update the IFrame with our new state.
89807     *
89808     * @method _updateIFrame
89809     * @private
89810     * @return {boolean} true if successful. false otherwise.
89811     */
89812    function _updateIFrame (fqstate) {
89813
89814        var html, doc;
89815
89816        html = '<html><body><div id="state">' +
89817                    YAHOO.lang.escapeHTML(fqstate) +
89818               '</div></body></html>';
89819
89820        try {
89821            doc = _histFrame.contentWindow.document;
89822            doc.open();
89823            doc.write(html);
89824            doc.close();
89825            return true;
89826        } catch (e) {
89827            return false;
89828        }
89829    }
89830
89831    /**
89832     * Periodically checks whether our internal IFrame is ready to be used.
89833     *
89834     * @method _checkIframeLoaded
89835     * @private
89836     */
89837    function _checkIframeLoaded() {
89838
89839        var doc, elem, fqstate, hash;
89840
89841        if (!_histFrame.contentWindow || !_histFrame.contentWindow.document) {
89842            // Check again in 10 msec...
89843            setTimeout(_checkIframeLoaded, 10);
89844            return;
89845        }
89846
89847        // Start the thread that will have the responsibility to
89848        // periodically check whether a navigate operation has been
89849        // requested on the main window. This will happen when
89850        // YAHOO.util.History.navigate has been called or after
89851        // the user has hit the back/forward button.
89852
89853        doc = _histFrame.contentWindow.document;
89854        elem = doc.getElementById("state");
89855        // We must use innerText, and not innerHTML because our string contains
89856        // the "&" character (which would end up being escaped as "&amp;") and
89857        // the string comparison would fail...
89858        fqstate = elem ? elem.innerText : null;
89859
89860        hash = _getHash();
89861
89862        setInterval(function () {
89863
89864            var newfqstate, states, moduleName, moduleObj, newHash, historyLength;
89865
89866            doc = _histFrame.contentWindow.document;
89867            elem = doc.getElementById("state");
89868            // See my comment above about using innerText instead of innerHTML...
89869            newfqstate = elem ? elem.innerText : null;
89870
89871            newHash = _getHash();
89872
89873            if (newfqstate !== fqstate) {
89874
89875                fqstate = newfqstate;
89876                _handleFQStateChange(fqstate);
89877
89878                if (!fqstate) {
89879                    states = [];
89880                    for (moduleName in _modules) {
89881                        if (YAHOO.lang.hasOwnProperty(_modules, moduleName)) {
89882                            moduleObj = _modules[moduleName];
89883                            states.push(moduleName + "=" + moduleObj.initialState);
89884                        }
89885                    }
89886                    newHash = states.join("&");
89887                } else {
89888                    newHash = fqstate;
89889                }
89890
89891                // Allow the state to be bookmarked by setting the top window's
89892                // URL fragment identifier. Note that here, we are on IE, and
89893                // IE does not touch the browser history when setting the hash
89894                // (unlike all the other browsers). I used to write:
89895                //     self.location.replace( "#" + hash );
89896                // but this had a side effect when the page was not the top frame.
89897                self.location.hash = newHash;
89898                hash = newHash;
89899
89900                _storeStates();
89901
89902            } else if (newHash !== hash) {
89903
89904                // The hash has changed. The user might have clicked on a link,
89905                // or modified the URL directly, or opened the same application
89906                // bookmarked in a specific state using a bookmark. However, we
89907                // know the hash change was not caused by a hit on the back or
89908                // forward buttons, or by a call to navigate() (because it would
89909                // have been handled above) We must handle these cases, which is
89910                // why we also need to keep track of hash changes on IE!
89911
89912                // Note that IE6 has some major issues with this kind of user
89913                // interaction (the history stack gets completely messed up)
89914                // but it seems to work fine on IE7.
89915
89916                hash = newHash;
89917
89918                // Now, store a new history entry. The following will cause the
89919                // code above to execute, doing all the dirty work for us...
89920                _updateIFrame(newHash);
89921            }
89922
89923        }, 50);
89924
89925        _initialized = true;
89926        YAHOO.util.History.onLoadEvent.fire();
89927    }
89928
89929    /**
89930     * Finish up the initialization of the Browser History Manager.
89931     *
89932     * @method _initialize
89933     * @private
89934     */
89935    function _initialize() {
89936
89937        var i, len, parts, tokens, moduleName, moduleObj, initialStates, initialState, currentStates, currentState, counter, hash;
89938
89939        // Decode the content of our storage field...
89940        parts = _stateField.value.split("|");
89941
89942        if (parts.length > 1) {
89943
89944            initialStates = parts[0].split("&");
89945            for (i = 0, len = initialStates.length; i < len; i++) {
89946                tokens = initialStates[i].split("=");
89947                if (tokens.length === 2) {
89948                    moduleName = tokens[0];
89949                    initialState = tokens[1];
89950
89951                    moduleObj = YAHOO.lang.hasOwnProperty(_modules, moduleName)
89952                            && _modules[moduleName];
89953
89954                    if (moduleObj) {
89955                        moduleObj.initialState = initialState;
89956                    }
89957                }
89958            }
89959
89960            currentStates = parts[1].split("&");
89961            for (i = 0, len = currentStates.length; i < len; i++) {
89962                tokens = currentStates[i].split("=");
89963                if (tokens.length >= 2) {
89964                    moduleName = tokens[0];
89965                    currentState = tokens[1];
89966
89967                    moduleObj = YAHOO.lang.hasOwnProperty(_modules, moduleName)
89968                            && _modules[moduleName];
89969
89970                    if (moduleObj) {
89971                        moduleObj.currentState = currentState;
89972                    }
89973                }
89974            }
89975        }
89976
89977        if (parts.length > 2) {
89978            _fqstates = parts[2].split(",");
89979        }
89980
89981        if (YAHOO.env.ua.ie) {
89982
89983            if (typeof document.documentMode === "undefined" || document.documentMode < 8) {
89984
89985                // IE < 8 or IE8 in quirks mode or IE7 standards mode
89986                _checkIframeLoaded();
89987
89988            } else {
89989
89990                // IE8 in IE8 standards mode
89991                YAHOO.util.Event.on(top, "hashchange",
89992                    function () {
89993                        var hash = _getHash();
89994                        _handleFQStateChange(hash);
89995                        _storeStates();
89996                    });
89997
89998                _initialized = true;
89999                YAHOO.util.History.onLoadEvent.fire();
90000
90001            }
90002
90003        } else {
90004
90005            // Start the thread that will have the responsibility to
90006            // periodically check whether a navigate operation has been
90007            // requested on the main window. This will happen when
90008            // YAHOO.util.History.navigate has been called or after
90009            // the user has hit the back/forward button.
90010
90011            // On Gecko and Opera, we just need to watch the hash...
90012            hash = _getHash();
90013
90014            setInterval(function () {
90015
90016                var state, newHash, newCounter;
90017
90018                newHash = _getHash();
90019                if (newHash !== hash) {
90020                    hash = newHash;
90021                    _handleFQStateChange(hash);
90022                    _storeStates();
90023                }
90024
90025            }, 50);
90026
90027            _initialized = true;
90028            YAHOO.util.History.onLoadEvent.fire();
90029        }
90030    }
90031
90032    /**
90033     * Wrapper around <code>decodeURIComponent()</code> that also converts +
90034     * chars into spaces.
90035     *
90036     * @method _decode
90037     * @param {String} string string to decode
90038     * @return {String} decoded string
90039     * @private
90040     * @since 2.9.0
90041     */
90042    function _decode(string) {
90043        return decodeURIComponent(string.replace(/\+/g, ' '));
90044    }
90045
90046    /**
90047     * Wrapper around <code>encodeURIComponent()</code> that converts spaces to
90048     * + chars.
90049     *
90050     * @method _encode
90051     * @param {String} string string to encode
90052     * @return {String} encoded string
90053     * @private
90054     * @since 2.9.0
90055     */
90056    function _encode(string) {
90057        return encodeURIComponent(string).replace(/%20/g, '+');
90058    }
90059
90060    return {
90061
90062        /**
90063         * Fired when the Browser History Manager is ready. If you subscribe to
90064         * this event after the Browser History Manager has been initialized,
90065         * it will not fire. Therefore, it is recommended to use the onReady
90066         * method instead.
90067         *
90068         * @event onLoadEvent
90069         * @see onReady
90070         */
90071        onLoadEvent: new YAHOO.util.CustomEvent("onLoad"),
90072
90073        /**
90074         * Executes the supplied callback when the Browser History Manager is
90075         * ready. This will execute immediately if called after the Browser
90076         * History Manager onLoad event has fired.
90077         *
90078         * @method onReady
90079         * @param {function} fn what to execute when the Browser History Manager is ready.
90080         * @param {object} obj an optional object to be passed back as a parameter to fn.
90081         * @param {boolean|object} overrideContext If true, the obj passed in becomes fn's execution scope.
90082         * @see onLoadEvent
90083         */
90084        onReady: function (fn, obj, overrideContext) {
90085
90086            if (_initialized) {
90087
90088                setTimeout(function () {
90089                    var ctx = window;
90090                    if (overrideContext) {
90091                        if (overrideContext === true) {
90092                            ctx = obj;
90093                        } else {
90094                            ctx = overrideContext;
90095                        }
90096                    }
90097                    fn.call(ctx, "onLoad", [], obj);
90098                }, 0);
90099
90100            } else {
90101
90102                YAHOO.util.History.onLoadEvent.subscribe(fn, obj, overrideContext);
90103
90104            }
90105        },
90106
90107        /**
90108         * Registers a new module.
90109         *
90110         * @method register
90111         * @param {string} module Non-empty string uniquely identifying the
90112         *     module you wish to register.
90113         * @param {string} initialState The initial state of the specified
90114         *     module corresponding to its earliest history entry.
90115         * @param {function} onStateChange Callback called when the
90116         *     state of the specified module has changed.
90117         * @param {object} obj An arbitrary object that will be passed as a
90118         *     parameter to the handler.
90119         * @param {boolean} overrideContext If true, the obj passed in becomes the
90120         *     execution scope of the listener.
90121         */
90122        register: function (module, initialState, onStateChange, obj, overrideContext) {
90123
90124            var scope, wrappedFn;
90125
90126            if (typeof module !== "string" || YAHOO.lang.trim(module) === "" ||
90127                typeof initialState !== "string" ||
90128                typeof onStateChange !== "function") {
90129                throw new Error("Missing or invalid argument");
90130            }
90131
90132            if (YAHOO.lang.hasOwnProperty(_modules, module)) {
90133                // Here, we used to throw an exception. However, users have
90134                // complained about this behavior, so we now just return.
90135                return;
90136            }
90137
90138            // Note: A module CANNOT be registered after calling
90139            // YAHOO.util.History.initialize. Indeed, we set the initial state
90140            // of each registered module in YAHOO.util.History.initialize.
90141            // If you could register a module after initializing the Browser
90142            // History Manager, you would not read the correct state using
90143            // YAHOO.util.History.getCurrentState when coming back to the
90144            // page using the back button.
90145            if (_initialized) {
90146                throw new Error("All modules must be registered before calling YAHOO.util.History.initialize");
90147            }
90148
90149            // Make sure the strings passed in do not contain our separators "," and "|"
90150            module = _encode(module);
90151            initialState = _encode(initialState);
90152
90153            // If the user chooses to override the scope, we use the
90154            // custom object passed in as the execution scope.
90155            scope = null;
90156            if (overrideContext === true) {
90157                scope = obj;
90158            } else {
90159                scope = overrideContext;
90160            }
90161
90162            wrappedFn = function (state) {
90163                return onStateChange.call(scope, state, obj);
90164            };
90165
90166            _modules[module] = {
90167                name: module,
90168                initialState: initialState,
90169                currentState: initialState,
90170                onStateChange: wrappedFn
90171            };
90172        },
90173
90174        /**
90175         * Initializes the Browser History Manager. Call this method
90176         * from a script block located right after the opening body tag.
90177         *
90178         * @method initialize
90179         * @param {string|HTML Element} stateField <input type="hidden"> used
90180         *     to store application states. Must be in the static markup.
90181         * @param {string|HTML Element} histFrame IFrame used to store
90182         *     the history (only required on Internet Explorer)
90183         * @public
90184         */
90185        initialize: function (stateField, histFrame) {
90186
90187            if (_initialized) {
90188                // The browser history manager has already been initialized.
90189                return;
90190            }
90191
90192            if (YAHOO.env.ua.opera && typeof history.navigationMode !== "undefined") {
90193                // Disable Opera's fast back/forward navigation mode and puts
90194                // it in compatible mode. This makes anchor-based history
90195                // navigation work after the page has been navigated away
90196                // from and re-activated, at the cost of slowing down
90197                // back/forward navigation to and from that page.
90198                history.navigationMode = "compatible";
90199            }
90200
90201            if (typeof stateField === "string") {
90202                stateField = document.getElementById(stateField);
90203            }
90204
90205            if (!stateField ||
90206                stateField.tagName.toUpperCase() !== "TEXTAREA" &&
90207                (stateField.tagName.toUpperCase() !== "INPUT" ||
90208                 stateField.type !== "hidden" &&
90209                 stateField.type !== "text")) {
90210                throw new Error("Missing or invalid argument");
90211            }
90212
90213            _stateField = stateField;
90214
90215            // IE < 8 or IE8 in quirks mode or IE7 standards mode
90216            if (YAHOO.env.ua.ie && (typeof document.documentMode === "undefined" || document.documentMode < 8)) {
90217
90218                if (typeof histFrame === "string") {
90219                    histFrame = document.getElementById(histFrame);
90220                }
90221
90222                if (!histFrame || histFrame.tagName.toUpperCase() !== "IFRAME") {
90223                    throw new Error("Missing or invalid argument");
90224                }
90225
90226                _histFrame = histFrame;
90227            }
90228
90229            // Note that the event utility MUST be included inline in the page.
90230            // If it gets loaded later (which you may want to do to improve the
90231            // loading speed of your site), the onDOMReady event never fires,
90232            // and the history library never gets fully initialized.
90233            YAHOO.util.Event.onDOMReady(_initialize);
90234        },
90235
90236        /**
90237         * Call this method when you want to store a new entry in the browser's history.
90238         *
90239         * @method navigate
90240         * @param {string} module Non-empty string representing your module.
90241         * @param {string} state String representing the new state of the specified module.
90242         * @return {boolean} Indicates whether the new state was successfully added to the history.
90243         * @public
90244         */
90245        navigate: function (module, state) {
90246
90247            var states;
90248
90249            if (typeof module !== "string" || typeof state !== "string") {
90250                throw new Error("Missing or invalid argument");
90251            }
90252
90253            states = {};
90254            states[module] = state;
90255
90256            return YAHOO.util.History.multiNavigate(states);
90257        },
90258
90259        /**
90260         * Call this method when you want to store a new entry in the browser's history.
90261         *
90262         * @method multiNavigate
90263         * @param {object} states Associative array of module-state pairs to set simultaneously.
90264         * @return {boolean} Indicates whether the new state was successfully added to the history.
90265         * @public
90266         */
90267        multiNavigate: function (states) {
90268
90269            var currentStates, moduleName, moduleObj, currentState, fqstate;
90270
90271            if (typeof states !== "object") {
90272                throw new Error("Missing or invalid argument");
90273            }
90274
90275            if (!_initialized) {
90276                throw new Error("The Browser History Manager is not initialized");
90277            }
90278
90279            for (moduleName in states) {
90280                if (!YAHOO.lang.hasOwnProperty(_modules, _encode(moduleName))) {
90281                    throw new Error("The following module has not been registered: " + moduleName);
90282                }
90283            }
90284
90285            // Generate our new full state string mod1=xxx&mod2=yyy
90286            currentStates = [];
90287
90288            for (moduleName in _modules) {
90289                if (YAHOO.lang.hasOwnProperty(_modules, moduleName)) {
90290                    moduleObj = _modules[moduleName];
90291                    if (YAHOO.lang.hasOwnProperty(states, moduleName)) {
90292                        currentState = states[_decode(moduleName)];
90293                    } else {
90294                        currentState = _decode(moduleObj.currentState);
90295                    }
90296
90297                    // Make sure the strings passed in do not contain our separators "," and "|"
90298                    moduleName = _encode(moduleName);
90299                    currentState = _encode(currentState);
90300
90301                    currentStates.push(moduleName + "=" + currentState);
90302                }
90303            }
90304
90305            fqstate = currentStates.join("&");
90306
90307            if (YAHOO.env.ua.ie && (typeof document.documentMode === "undefined" || document.documentMode < 8)) {
90308
90309                return _updateIFrame(fqstate);
90310
90311            } else {
90312
90313                // Known bug: On Safari 1.x and 2.0, if you have tab browsing
90314                // enabled, Safari will show an endless loading icon in the
90315                // tab. This has apparently been fixed in recent WebKit builds.
90316                // One work around found by Dav Glass is to submit a form that
90317                // points to the same document. This indeed works on Safari 1.x
90318                // and 2.0 but creates bigger problems on WebKit. So for now,
90319                // we'll consider this an acceptable bug, and hope that Apple
90320                // comes out with their next version of Safari very soon.
90321                self.location.hash = fqstate;
90322
90323                return true;
90324            }
90325        },
90326
90327        /**
90328         * Returns the current state of the specified module.
90329         *
90330         * @method getCurrentState
90331         * @param {string} module Non-empty string representing your module.
90332         * @return {string} The current state of the specified module.
90333         * @public
90334         */
90335        getCurrentState: function (module) {
90336
90337            var moduleObj;
90338
90339            if (typeof module !== "string") {
90340                throw new Error("Missing or invalid argument");
90341            }
90342
90343            if (!_initialized) {
90344                throw new Error("The Browser History Manager is not initialized");
90345            }
90346
90347            moduleObj = YAHOO.lang.hasOwnProperty(_modules, module)
90348                    && _modules[module];
90349
90350            if (!moduleObj) {
90351                throw new Error("No such registered module: " + module);
90352            }
90353
90354            return _decode(moduleObj.currentState);
90355        },
90356
90357        /**
90358         * Returns the state of a module according to the URL fragment
90359         * identifier. This method is useful to initialize your modules
90360         * if your application was bookmarked from a particular state.
90361         *
90362         * @method getBookmarkedState
90363         * @param {string} module Non-empty string representing your module.
90364         * @return {string} The bookmarked state of the specified module.
90365         * @public
90366         */
90367        getBookmarkedState: function (module) {
90368
90369            var i, len, idx, hash, states, tokens, moduleName;
90370
90371            if (typeof module !== "string") {
90372                throw new Error("Missing or invalid argument");
90373            }
90374
90375            // Use location.href instead of location.hash which is already
90376            // URL-decoded, which creates problems if the state value
90377            // contained special characters...
90378            idx = self.location.href.indexOf("#");
90379            if (idx >= 0) {
90380                hash = self.location.href.substr(idx + 1);
90381                states = hash.split("&");
90382                for (i = 0, len = states.length; i < len; i++) {
90383                    tokens = states[i].split("=");
90384                    if (tokens.length === 2) {
90385                        moduleName = tokens[0];
90386                        if (moduleName === module) {
90387                            return _decode(tokens[1]);
90388                        }
90389                    }
90390                }
90391            }
90392
90393            return null;
90394        },
90395
90396        /**
90397         * Returns the value of the specified query string parameter.
90398         * This method is not used internally by the Browser History Manager.
90399         * However, it is provided here as a helper since many applications
90400         * using the Browser History Manager will want to read the value of
90401         * url parameters to initialize themselves.
90402         *
90403         * @method getQueryStringParameter
90404         * @param {string} paramName Name of the parameter we want to look up.
90405         * @param {string} queryString Optional URL to look at. If not specified,
90406         *     this method uses the URL in the address bar.
90407         * @return {string} The value of the specified parameter, or null.
90408         * @public
90409         */
90410        getQueryStringParameter: function (paramName, url) {
90411
90412            var i, len, idx, queryString, params, tokens;
90413
90414            url = url || self.location.href;
90415
90416            idx = url.indexOf("?");
90417            queryString = idx >= 0 ? url.substr(idx + 1) : url;
90418
90419            // Remove the hash if any
90420            idx = queryString.lastIndexOf("#");
90421            queryString = idx >= 0 ? queryString.substr(0, idx) : queryString;
90422
90423            params = queryString.split("&");
90424
90425            for (i = 0, len = params.length; i < len; i++) {
90426                tokens = params[i].split("=");
90427                if (tokens.length >= 2) {
90428                    if (tokens[0] === paramName) {
90429                        return _decode(tokens[1]);
90430                    }
90431                }
90432            }
90433
90434            return null;
90435        }
90436
90437    };
90438
90439})();
90440YAHOO.register("history", YAHOO.util.History, {version: "2.9.0", build: "2800"});
90441/**
90442 * Patch to Menu to allow empty groups to remain in the menu structure.
90443 * Required by: Sites dynamic drop-down menu.
90444 * Patches: YUI 2.6.0 to 2.9.0
90445 * Escalated: Yes, but closed as "by design"
90446 */
90447(function()
90448{
90449   var Lang = YAHOO.lang,
90450      Dom = YAHOO.util.Dom,
90451      _FIRST_OF_TYPE = "first-of-type";
90452
90453   YAHOO.widget.Menu.prototype._removeItemFromGroupByIndex = function (p_nGroupIndex, p_nItemIndex, p_keepEmptyGroup) {
90454
90455       var nGroupIndex = Lang.isNumber(p_nGroupIndex) ? p_nGroupIndex : 0,
90456           aGroup = this._getItemGroup(nGroupIndex),
90457           aArray,
90458           oItem,
90459           oUL;
90460
90461       if (aGroup) {
90462
90463           aArray = aGroup.splice(p_nItemIndex, 1);
90464           oItem = aArray[0];
90465
90466           if (oItem) {
90467
90468               // Update the index and className properties of each member
90469               this._updateItemProperties(nGroupIndex);
90470
90471               if (aGroup.length === 0 && !p_keepEmptyGroup) {
90472
90473                   // Remove the UL
90474                   oUL = this._aListElements[nGroupIndex];
90475
90476                   if (oUL && oUL.parentNode) {
90477                       oUL.parentNode.removeChild(oUL);
90478                   }
90479
90480                   // Remove the group from the array of items
90481                   this._aItemGroups.splice(nGroupIndex, 1);
90482
90483                   // Remove the UL from the array of ULs
90484                   this._aListElements.splice(nGroupIndex, 1);
90485
90486                   /*
90487                        Assign the "first-of-type" class to the new first UL
90488                        in the collection
90489                   */
90490                   oUL = this._aListElements[0];
90491
90492                   if (oUL) {
90493                       Dom.addClass(oUL, _FIRST_OF_TYPE);
90494                   }
90495               }
90496
90497               this.itemRemovedEvent.fire(oItem);
90498               this.changeContentEvent.fire();
90499           }
90500       }
90501
90502      // Return a reference to the item that was removed
90503      return oItem;
90504   };
90505
90506   YAHOO.widget.Menu.prototype._removeItemFromGroupByValue = function (p_nGroupIndex, p_oItem, p_keepEmptyGroup) {
90507
90508       var aGroup = this._getItemGroup(p_nGroupIndex),
90509           nItems,
90510           nItemIndex,
90511           returnVal,
90512           i;
90513
90514       if (aGroup) {
90515           nItems = aGroup.length;
90516           nItemIndex = -1;
90517
90518           if (nItems > 0) {
90519               i = nItems-1;
90520               do {
90521                   if (aGroup[i] == p_oItem) {
90522                       nItemIndex = i;
90523                       break;
90524                   }
90525               }
90526               while (i--);
90527
90528               if (nItemIndex > -1) {
90529                   returnVal = this._removeItemFromGroupByIndex(p_nGroupIndex, nItemIndex, p_keepEmptyGroup);
90530               }
90531           }
90532       }
90533       return returnVal;
90534   };
90535
90536   YAHOO.widget.Menu.prototype.removeItem = function (p_oObject, p_nGroupIndex, p_keepEmptyGroup) {
90537       var oItem,
90538          returnVal;
90539
90540       if (!Lang.isUndefined(p_oObject)) {
90541           if (p_oObject instanceof YAHOO.widget.MenuItem) {
90542               oItem = this._removeItemFromGroupByValue(p_nGroupIndex, p_oObject, p_keepEmptyGroup);
90543           }
90544           else if (Lang.isNumber(p_oObject)) {
90545               oItem = this._removeItemFromGroupByIndex(p_nGroupIndex, p_oObject, p_keepEmptyGroup);
90546           }
90547
90548           if (oItem) {
90549               oItem.destroy();
90550               returnVal = oItem;
90551           }
90552       }
90553
90554      return returnVal;
90555   };
90556})();
90557
90558/**
90559 * Patch to Container to prevent IE6 trying to set properties on elements that have been removed from the DOM.
90560 * This function is called via a setTimer(), so this patch fixes a race condition.
90561 * Required by: Document List "Loading Document Library..." pop-up.
90562 * Patches: YUI 2.7.0 to 2.9.0
90563 */
90564(function()
90565{
90566   /**
90567   * Adjusts the size of the shadow based on the size of the element.
90568   * @method sizeUnderlay
90569   */
90570   YAHOO.widget.Panel.prototype.sizeUnderlay = function()
90571   {
90572       var oUnderlay = this.underlay,
90573           oElement;
90574
90575       if (oUnderlay) {
90576           oElement = this.element;
90577           if (oElement) {
90578              oUnderlay.style.width = oElement.offsetWidth + "px";
90579              oUnderlay.style.height = oElement.offsetHeight + "px";
90580           }
90581       }
90582   };
90583})();
90584
90585
90586(function()
90587{
90588   /**
90589    * Drag drop support for ipad (safari) & android (default browser & chrome) making yui's drag n drop classes work out of the box.
90590    *
90591    * The trick is to:
90592    * - Stop listening for mouse events: "mousedown", "mousemove" & "mouseup"
90593    * - Start listening to touch events: "touchstart", "touchmove" & "touchend"
90594    * - Make sure all events have with pageX & pageY attributes set so they can be treated as a "mouse" event.
90595    *
90596    * Note! Assumes the following when invoked:
90597    * - the YAHOO.util.DragDropMgr && YAHOO.util.DragDrop classes have been loaded
90598    * - the YAHOO.util.DragDropMgr have been initialized
90599    */
90600   if ((YAHOO.env.ua.ipad || YAHOO.env.ua.android) && YAHOO.util.DragDropMgr && YAHOO.util.DragDrop)
90601   {
90602      var Event = YAHOO.util.Event;
90603
90604      // Fake an object that pretends to be an event so we can set it's pageX & pageY coords
90605      var createMouseEvent = function(e, preventDefault, stopPropagation)
90606      {
90607         var event = {
90608            type: e.type,
90609            target: YAHOO.util.Event.getTarget(e),
90610            pageX: e.pageX,
90611            pageY: e.pageY,
90612            which: e.which
90613         };
90614
90615         // Make sure the event can stop bubbling
90616         var orgEvent = e,
90617            pd = YAHOO.lang.isBoolean(preventDefault) ? preventDefault : true,
90618            sp = YAHOO.lang.isBoolean(stopPropagation) ? stopPropagation : true;
90619         if (e.preventDefault)
90620         {
90621            event.preventDefault = function()
90622            {
90623               if (pd)
90624               {
90625                  orgEvent.preventDefault();
90626               }
90627            }
90628         }
90629         if (e.stopPropagation)
90630         {
90631            event.stopPropagation = function()
90632            {
90633               if (sp)
90634               {
90635               orgEvent.stopPropagation();
90636               }
90637            };
90638         }
90639
90640         // Android always sets the pageY but the pageX is always 0, pick it from the targetTouches instead
90641         if (e.targetTouches && e.targetTouches.length > 0)
90642         {
90643            var touch = e.targetTouches[e.targetTouches.length - 1];
90644            event.pageX = touch.pageX;
90645            event.pageY = touch.pageY;
90646         }
90647
90648         // Add it in since the yui classes are looking at the value
90649         if (e.clientX || e.clientY)
90650         {
90651            event.clientX = e.clientX;
90652            event.clientY = e.clientY;
90653         }
90654
90655         // Add it in since the yui classes are looking at the value
90656         if (e.button)
90657         {
90658            event.button = e.button;
90659         }
90660
90661         return event;
90662      };
90663
90664      // First patch the YAHOO.uti.DragDropMgr (which is an already created singleton object without a prototype)
90665      var DDM_patch = function()
90666      {
90667         // Remove the mouse listeners that was added in DragDropMgr.onLoad
90668         Event.removeListener(document, "mouseup", this.handleMouseUp);
90669         Event.removeListener(document, "mousemove", this.handleMouseMove);
90670
90671         // Add in a "proxy" mousemove listener
90672         var original_handleMouseMove = this.handleMouseMove;
90673         this.handleMouseMove = function(e)
90674         {
90675            // Create a faked event so that pageX and pageY will be set
90676            var event = createMouseEvent(e);
90677            original_handleMouseMove.call(this, event);
90678
90679            // Make sure to save the touch coords since the "touchend" event always have pageX and pageY set to 0
90680            this._lastTouchPageX = event.pageX;
90681            this._lastTouchPageY = event.pageY;
90682         };
90683
90684         // Add in a "proxy" mouseup listener
90685         var original_handleMouseUp = this.handleMouseUp;
90686         this.handleMouseUp = function(e)
90687         {
90688            // Create a faked event so that we can make sure event propagation isn't stopped
90689            var event = createMouseEvent(e, false, false);
90690            original_handleMouseUp.call(this, event);
90691         };
90692
90693         // Add our own proxy touch listeners
90694         Event.on(document, "touchend", this.handleMouseUp, this, true);
90695         Event.on(document, "touchmove", this.handleMouseMove, this, true);
90696
90697         // Make sure the shim is listening to touch events instead of mouse events
90698         var original__createShim = this._createShim;
90699         this._createShim = function()
90700         {
90701            original__createShim.call(this);
90702
90703            // Stop listening to mouse events
90704            Event.removeListener(s, "mouseup",  this.handleMouseUp);
90705            Event.removeListener(s, "mouseover",  this.handleMouseMove);
90706
90707            // Start listening to touch events
90708            Event.on(s, "touchend",  this.handleMouseUp, this, true);
90709            Event.on(s, "touchmove", this.handleMouseMove, this, true);
90710         };
90711
90712         // Patch the fire events method so we it can treat the "touchend" event as a "mouseup" event (having pageX & pageY)
90713         var original_fireEvents = this.fireEvents;
90714         this.fireEvents = function(e, isDrop)
90715         {
90716            var event = e;
90717            if (isDrop)
90718            {
90719               // Create a fake event object with all attributes the drag drop classes seem to use
90720               event = createMouseEvent(e, false, false);
90721               event.pageX = this._lastTouchPageX;
90722               event.pageY = this._lastTouchPageY;
90723            }
90724
90725            original_fireEvents.call(this, event, isDrop);
90726         };
90727
90728      };
90729      DDM_patch.call(YAHOO.util.DragDropMgr);
90730
90731      // Now patch the YAHOO.util.DragDrop prototype
90732      var DD_patch = function()
90733      {
90734         var original_init = YAHOO.util.DragDrop.prototype.init;
90735         YAHOO.util.DragDrop.prototype.init = function(id, sGroup, config)
90736         {
90737            original_init.call(this, id, sGroup, config);
90738
90739            // Stop listening to mouse events
90740            Event.removeListener(this._domRef || this.id, "mousedown", this.handleMouseDown);
90741
90742            // Start listening to touch events
90743            Event.on(this._domRef || this.id, "touchstart", this.handleMouseDown, this, true);
90744         };
90745
90746         var original_setOuterHandleElId = YAHOO.util.DragDrop.prototype.setOuterHandleElId;
90747         YAHOO.util.DragDrop.prototype.setOuterHandleElId = function(id)
90748         {
90749            original_setOuterHandleElId.call(this, id);
90750
90751            // Stop listening to mouse events
90752            Event.removeListener(id, "mousedown", this.handleMouseDown);
90753
90754            // Start listening to touch events
90755            Event.on(id, "touchstart", this.handleMouseDown, this, true);
90756         };
90757
90758         var original_handleMouseDown = YAHOO.util.DragDrop.prototype.handleMouseDown;
90759         YAHOO.util.DragDrop.prototype.handleMouseDown = function(e, oDD)
90760         {
90761            // Create a faked event with pageX and pageY attributes to keep yui happy
90762            var event = createMouseEvent(e, false, false);
90763
90764            // Make sure to save the touch coords since the "touchend" event al
90764ways have pageX and pageY set to 0
90765            this._lastTouchPageX = event.pageX;
90766            this._lastTouchPageY = event.pageY;
90767
90768            original_handleMouseDown.call(this, event, oDD);
90769         };
90770      };
90771      DD_patch.call(YAHOO.util.DragDrop);
90772
90773   }
90774})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.