YAHOO.namespace("com.lgan.Analyzer");

(function(){
    
    //Function borrowed from old Analyzer.
    function GetRadioVal(strRadName) {
        var retVal = "";
        var aryRads = document.getElementsByName(strRadName);
        
        for (var i = 0; i < aryRads.length; i++) {
            if (aryRads[i].checked) {
                retVal = aryRads[i].value;
            }
        }
        
        return retVal;
    }
    
    var MONTH_NAMES = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
    
    var
      Dom = YAHOO.util.Dom,
      Event = YAHOO.util.Event,
      CustomEvent = YAHOO.util.CustomEvent,
      Config = YAHOO.util.Config,
      Dialog = YAHOO.widget.Dialog,
      Button = YAHOO.widget.Button,
      ListDialog = YAHOO.com.lgan.ListDialog,
      Analyzer = YAHOO.com.lgan.Analyzer,
      Logger = YAHOO.com.lgan.Logger || YAHOO
    ;
    
    ///**
    //  Corrects the getLocation method's use (supplied by YUI in 2.5.1) to account for drag-drop objects located in a scrolling container.
    //*/
    //YAHOO.util.DragDropMgr.refreshCache = function(groups) {
    //    //Logger.log("refreshing element location cache", "info", "DragDropMgr");
    //
    //    // refresh everything if group array is not provided
    //    var g = groups || this.ids;
    //    
    //    var parentRegion = null;
    //    for (var sGroup in g) {
    //        if ("string" != typeof sGroup) {
    //            continue;
    //        }
    //        for (var i in this.ids[sGroup]) {
    //            var oDD = this.ids[sGroup][i];
    //            if (this.isTypeOfDD(oDD)) {
    //                var loc = this.getLocation(oDD);
    //                if (loc) {
    //                    parentRegion = Dom.getRegion(oDD.getEl().parentNode);
    //                    if(parentRegion.contains(loc)){
    //                        this.locationCache[oDD.id] = loc;
    //                    }
    //                    parentRegion = null;  //Reset for next iteration
    //                } else {
    //                    delete this.locationCache[oDD.id];
    //                    //Logger.log("Could not get the loc for " + oDD.id, "warn", "DragDropMgr");
    //                }
    //            }
    //        }
    //    }
    //};
    
    /**
      @param {Object} userConfig This must be supplied somewhere along the subclass chain, or here.
    */
    Analyzer.BaseApp = function(userConfig){
        this.init(userConfig);
    };
    
    /**
      XML Namespace URI to use with any custom DOM attributes.
      @static
    */
    Analyzer.BaseApp.XMLNS = "http://www.lgan.com/aplus/";
    
    
    /**
      XML Namespace Prefix to use with any custom DOM attributes.
      @static
    */
    Analyzer.BaseApp.XMLNSPrefix = "aplus";
    
    /**
      @param {Object} objRow A record from the FormattedValues metadata table.
      @return {HTMLOption} Returns HTML Option objects.
    */
    Analyzer.BaseApp.FValuesToOptions = function(objRow){
        //return new Option(objRow.LongLabel, objRow.RawStart, false,false);
        return new Option(objRow.LongLabel, objRow.LongLabel, false,false);
    };
      
    var EVENT_TYPES = {
      "INIT": "Init",
      "BEFORE_INIT": "BeforeInit",
      "CONFIG_APPLIED": "ConfigApplied",
      "INIT_SUBCLASS": "InitSubclass",
      "ALL_METADATA_LOADED": "AllMetadataLoaded",
      "METADATA_LOADED": "MetadataLoaded",
      "DATA_WARNING": "DataWarning",
      "DATA_ERROR": "DataError",
      "HTTP_ERROR": "HTTPError",
      "TOPIC_CHANGED": "TopicChanged",
      "CATEGORY_CHANGED": "CategoryChanged",
      "DATE_CHANGED": "DateChanged",
      "BEFORE_UPDATE_BVS": "BeforeUpdateBVs",
      "BVS_UPDATED": "BVsUpdated",
      "BVLIST_CREATED": "BVListCreated",
      "FILTER_DATA_UPDATE": "FilterDataUpdate",
      "DDLIST_CHANGED": "DDListChanged",
      "DDTARGET_ENABLEDSTATE_UPDATE":"DDTargetEnabledStateUpdate",
      "BEFORE_VARIABLE_BLUR": "BeforeVariableBlur",
      "VARIABLE_BLURRED": "VariableBlurred",
      "VARIABLE_FOCUSED": "VariableFocused",
      "BEFORE_DESTROYING_DD_HANDLE": "BeforeDestroyingDDHandle",
      "PREVIEW_CREATED": "PreviewCreated",
      "PREVIEW_RENDERED": "PreviewRendered",
      "DESCRIPTION_PREVIEW_CONTAINER_CREATED": "DescriptionPreviewContainerCreated",
      "RESPONSE_LIST_PREVIEW_CREATED":"ResponseListPreviewCreatedEvent",
      "RESPONSE_LIST_CHOSEN":"ResponseListChosen",
      "FC_DATA_UPDATE":"FormatChoiceDataUpdate"
    };
    
    var DD_HIDFIELD_ASSOCS = {
      "ulDDRows":"hidrowVars",
      "ulDDColumns":"hidcolVars",
      "ulDDAxis":"hidaxisVars",
      "ulDDGroups":"hidgrpVars",
      "ulDDFilters":"hidfilVars",
      "ulDDTables":"hidtblVars"
    };
    
    var DD_MULTOPT_ASSOCS = {
      "ulDDRows":    "MultOptCont_Rows",
      "ulDDColumns": "MultOptCont_Columns",
      "ulDDFilters": "MultOptCont_Filters"
    };
    
    /**
      Constant representing the Analyzer's default configuration properties.
    */
    var DEFAULT_CONFIG = {
      /**
        E-mail address used for automated tech support messages.  Meant to be overwritten when subclassed.
        @config Support
        @type String
        @default info, at lgan doot com.
      */
      TECHSUPPORT:{
        key: "Support",
        value: [
          "info",
          ["lgan","com"].join(".")
        ].join("@")
      },
      
      /**
        Meant to be overwritten when subclassed.
        @config Sysname
        @type String
        @default "<code>Analyzer</code>"
      */
      SYSNAME:{
        key: "Sysname",
        value: "Analyzer",
        validator: YAHOO.lang.isString
      },
      
      /**
        This is the URL to use to download metadata table dumps, which are too free-form for a YUI DataSource (at least as of 2.5.2).
        NOTE:  This and any other metadata-fetching URL's can't be in the /aplus/ folder; that is a separate folder in the filesystem, and will cause a new session to spawn when a request is made to it.
      */
      METADATA_TABLE_URL:{
        key: "MetadataTableURL",
        value: "/var/fetchanmd.asp?",
        validator: YAHOO.lang.isString
      },
      
      DRAGDROP_CLASS:{
        key: "DragDropClass",
        value: Analyzer.DDProxy
      },
      
      DRAGDROP_LIMIT:{
        key: "DragDropLimit",
        value: null,
        validator: YAHOO.lang.isNumber
      },
      
      DD_TOGGLE_ASSOCS:{
        key:"DragDrop_Toggle_Associations",
        value:{
          "ulDDFilters":"chkUseFilters",
          "ulDDTables":"chkUseMultipleTables"
        },
        validator: YAHOO.lang.isObject
      },
      
      /**
        This is the text that goes in the context menu item for fetching a variable's description.
      */
      CM_DESCRIPTION_LABEL:{
        key: "cmDescriptionLabel",
        value: "Description"
      },
      
      /**
        Expected values:  "today", "yesterday".  Meant for systems that use a single-date input (month, day, year elements).
      */
      LAST_DATA_AVAILABLE_DAY:{
        key: "LastDataAvailableDay",
        value: null,
        validator: YAHOO.lang.isString
      },
      
      /**
        The URL for querying, in fashion of the YAHOO.util.DataSource object's constructor (basically, ready to tack a name-value pair on the end for a GET).
      */
      FV_QUERY_URL:{
        key: "FormattedValueQueryURL",
        value: "/var/fetchFV.asp?",
        validator: YAHOO.lang.isString
      },
      
      FORMATTED_VALUE_FIELDS:{
        key: "FormattedValueFields",
        value: ["RawStart","SortOrder","LongLabel","ShortLabel"],
        validator: YAHOO.lang.isArray
      },
      
      LIST_PREVIEWS:{
        key: "ListPreviews",
        value: {
          "ulDDSource":  "Preview_InactiveVar",
          "ulDDColumns": "Preview_Columns",
          "ulDDRows":    "Preview_Rows",
          "ulDDAxis":    "Preview_Axis",
          "ulDDGroups":  "Preview_Groups",
          "ulDDTables":  "Preview_Tables",
          "ulDDFilters": "Preview_Filters"
        },
        validator: YAHOO.lang.isObject
      },
      
      PREFIX_VARLABEL_WITH_VARNAME:{
        key: "PrefixVarLabelWithVarname",
        value: false,
        validator: YAHOO.lang.isBoolean
      },
      
      //Time details - what displays when the page is loaded.
      /**
        If this is true, there will be a pseudo-link in the data availability
        area (time frame selection) that displays a Panel widget, detailing the
        data sources for the system.
      */
      SHOW_DATASOURCES:{
        key: "ShowDataSources",
        value: false,
        validator: YAHOO.lang.isBoolean
      },
      
      /**
        The default month span starts at 1, not 0; the beginning date input is
        considered the beginning of the month, the end input the end of the
        month.  So, 12 months goes from January to December.
      */
      DEFAULT_MONTH_SPAN:{
        key: "DefaultNumMonths",
        value: 12,
        validator: YAHOO.lang.isNumber
      },
      
      DEFAULT_MONTH_OFFSET:{
        key: "DefaultMonthOffset",
        value: {
          from: "end", //or:  "beginning"
          numMonths: 0
        },
        validator: YAHOO.lang.isObject
      },
      
      DEFAULT_VAR_FORMAT_PRIORITY:{
        key:"DefaultVarFormatPriority",
        value:1
      },
      
      /**
        CSS properties for showing and hiding form elements on the Query
        Builder tab.  By default, uses the <code>visibility</code> property to
        toggle the appearance; an alternative would be <code>display</code>.
      */
      DRAG_DROP_VISUAL_TOGGLE:{
        key:"DragDropVisualToggle",
        value:{
          cssProp: "display",
          cssValues:{
            show: "block",
            hide: "none"
          }
        },
        validator: YAHOO.lang.isObject
      },
      
      /**
        If a list of data values is greater than or equal to this number,
        that list is considered "long".  This affects filter dialog.
      */
      LONG_LIST_LENGTH:{
        key:"iLongListLength",
        value:20,
        validator: YAHOO.lang.isNumber
      },
      
      /**
        If a list of data values is greater than or equal to this number,
        that list is considered "long".  This affects previews.
      */
      LONG_LIST_LENGTH:{
        key:"iLongPreviewListLength",
        value:5,
        validator: YAHOO.lang.isNumber
      },
      
      /**
        If this is true, then long preview lists will have their first three
        values shown, followed by the remainder.
      */
      TRUNCATE_LONG_LIST_PREVIEWS:{
        key:"TruncateLongListPreviews",
        value:true,
        validator: YAHOO.lang.isBoolean
      },
      
      ALLOW_FILTERING:{
        key:"AllowFiltering",
        value:true,
        validator: YAHOO.lang.isBoolean
      },
      
      ALLOW_SUBSETTING:{
        key:"AllowSubsetting",
        value:true,
        validator: YAHOO.lang.isBoolean
      },
      
      /**
        False -> "Move elsewhere in Analysis" context menu and submenu never display.
      */
      ALLOW_MOVE_ACTIVE_VARS_WITH_CONTEXT_MENU:{
        key: "AllowMoveActiveVarsWithContextMenu",
        value: true,
        validator: YAHOO.lang.isBoolean
      },
      
      
      //DOM hooks
      
      //Widgets
      TABVIEW_CONTAINER:{
        key: "elTabviewContainer",
        value: "AnalyzerInterfaceContainer"
      },
      CATEGORY_TREE:{
        key: "strCategoryTreeElId",
        value: "divCategoryTree"
      },
      
      //Drag-drop inputs
      DD_INPUT_ROW:{
        key: "hidden_row",
        value: "hidrowVars"
      },
      DD_INPUT_COL:{
        key: "hidden_col",
        value: "hidcolVars"
      },
      DD_INPUT_TBL:{
        key: "hidden_tbl",
        value: "hidtblVars"
      },
      DD_INPUT_HID:{
        key: "hidden_filter",
        value: "hidfilVars"
      },
      
      //Misc. inputs
      TOPIC_INPUT:{
        key: "elTopicInput",
        value: "TopicBox"
      },
      
      CATEGORY_INPUT:{
        key: "elCategoryInput",
        value: "VarCatBox"
      },
      
      TIME_INPUT_FM:{
        key: "elTimeInput_FM",
        value: "FromMonth"
      },
      TIME_INPUT_FY:{
        key: "elTimeInput_FY",
        value: "FromYear"
      },
      TIME_INPUT_TM:{
        key: "elTimeInput_TM",
        value: "ToMonth"
      },
      TIME_INPUT_TY:{
        key: "elTimeInput_TY",
        value: "ToYear"
      },
      TIME_BOUND_FM:{
        key: "elTimeBound_FM",
        value: "elTimeBound_FM"
      },
      TIME_BOUND_FY:{
        key: "elTimeBound_FY",
        value: "elTimeBound_FY"
      },
      TIME_BOUND_TM:{
        key: "elTimeBound_TM",
        value: "elTimeBound_TM"
      },
      TIME_BOUND_TY:{
        key: "elTimeBound_TY",
        value: "elTimeBound_TY"
      },
      TOPIC_REFLECT:{
        key: "elTopicReflect",
        value: "elTopicReflect"
      }
    };
    
      
    /**
      Utility method; inserts line-break characters every so often.
      @static
      @param p_sTitle {String} Title line to break up.
      @param p_iLineLength {Integer} Defaults to 40.
      @return {String}
    */
    Analyzer.BaseApp.BreakLongTitle = function(p_sTitle, p_iLineLength){
        var iLength = p_iLineLength || 40;
        //Set header, which can often be too long for the available space
        var aryHeader = (p_sTitle).split(" ");
        //Insert a line break before every line's worth of characters
        for(var j=0, strGuage = ""; j<aryHeader.length; j++){
            if(strGuage.length + aryHeader[j].length > 40){
                aryHeader.splice(j, 0, "<br />");
                j++;
                strGuage = "";
            }
            strGuage += aryHeader[j];
        }
        return aryHeader.join(" ");
    };
    
    Analyzer.BaseApp.prototype = {
      /**
        Constructor function.  Necessary for superclass calls
        @constructor Analyzer.BaseApp
      */
      constructor: Analyzer.BaseApp,
      
      /**
        @property objDDProxies
      */
      objDDProxies:{},
      
      /**
        The hash table of all Drag-Drop Targets on the page.
        @property objDDBoxes
      */
      objDDBoxes:{},
      
      /**
        This associative array stores the asyncRequests' transport objects, indexed by query (e.g. <code>ds=<var>TableName</var></code>).
        @property MetadataTableStore
      */
      MetadataTableStore:{},
      
      /**
        This associative array stores indexed table dumps.
        @property IndexedMetadata
      */
      IndexedMetadata:{},
      
      //YAHOO widgets
      /**
        Interface item.  Initialized and configured in <code>init</code>.
        @property tabView
      */
      tabView:null,
      
      /**
        @property FilterDialog
      */
      FilterDialog: null,
                        
      initEvents: function(){
          Logger.log('Called.', 'trace', 'BaseApp.initEvents');
          var SIGNATURE = CustomEvent.LIST;
          
          /**
            Event that fires when the <code>init</code> method is completed.
          */
          this.InitEvent = this.createEvent(EVENT_TYPES.INIT);
          this.InitEvent.signature = SIGNATURE;
          
          /**
            Fires at beginning of the init method for this class and all subclasses.
          */
          this.BeforeInitEvent = this.createEvent(EVENT_TYPES.BEFORE_INIT);
          this.BeforeInitEvent.signature = SIGNATURE;
          
          /**
            Event that fires when the <code>init</code> method of a subclass is completed (judged by constructor comparison).
          */
          this.InitSubclassEvent = this.createEvent(EVENT_TYPES.INIT_SUBCLASS);
          this.InitSubclassEvent.signature = SIGNATURE;
          
          /**
            Event that fires when the user config is applied.
          */
          this.ConfigAppliedEvent = this.createEvent(EVENT_TYPES.CONFIG_APPLIED);
          this.ConfigAppliedEvent.signature = SIGNATURE;
          
          /**
            Event for page initialization.  Fires when all metadata in configuration list is loaded.
          */
          this.AllMetadataLoadedEvent = this.createEvent(EVENT_TYPES.ALL_METADATA_LOADED);
          this.AllMetadataLoadedEvent.signature = CustomEvent.FLAT;
          
          /**
            Event for page initialization.  Fires when metadata transport and processing is completed for one data source.
          */
          this.MetadataLoadedEvent = this.createEvent(EVENT_TYPES.METADATA_LOADED);
          this.MetadataLoadedEvent.signature = SIGNATURE;
          
          /**
            Event for HTTP transports - fires when JSON replyCode is 300.
          */
          this.DataWarningEvent = this.createEvent(EVENT_TYPES.DATA_WARNING);
          this.DataWarningEvent.signature = CustomEvent.FLAT;
          
          /**
            Event for HTTP transports - fires when JSON replyCode is 500.
          */
          this.DataErrorEvent = this.createEvent(EVENT_TYPES.DATA_ERROR);
          this.DataErrorEvent.signature = CustomEvent.FLAT;
          
          /**
            Event for HTTP transports - fires when HTTP status is 500.
          */
          this.HTTPErrorEvent = this.createEvent(EVENT_TYPES.HTTP_ERROR);
          this.HTTPErrorEvent.signature = CustomEvent.FLAT;
      
          //Events for interface actions
          
          /**
            Instigator of "Query Builder" tab resets.
          */
          this.TopicChangedEvent = this.createEvent(EVENT_TYPES.TOPIC_CHANGED);
          this.TopicChangedEvent.signature = SIGNATURE;
          
          /**
            Maybe we'll use this...
          */
          this.CategoryChangedEvent = this.createEvent(EVENT_TYPES.CATEGORY_CHANGED);
          this.CategoryChangedEvent.signature = CustomEvent.FLAT;
          
          /**
            Takes one parameter:  An object, associating an input with a reflection element (elIn, elRef) by configuration key.
          */
          this.DateChangedEvent = this.createEvent(EVENT_TYPES.DATE_CHANGED);
          this.DateChangedEvent.signature = CustomEvent.FLAT;
          
          /**
            Maybe we'll use this...
          */
          this.BeforeUpdateBVsEvent = this.createEvent(EVENT_TYPES.BEFORE_UPDATE_BVS);
          this.BeforeUpdateBVsEvent.signature = SIGNATURE;
          
          /**
            Maybe we'll use this...
          */
          this.BVsUpdatedEvent = this.createEvent(EVENT_TYPES.BVS_UPDATED);
          this.BVsUpdatedEvent.signature = SIGNATURE;
          
          /**
            Maybe we'll use this...
          */
          this.BVListCreatedEvent = this.createEvent(EVENT_TYPES.BVLIST_CREATED);
          this.BVListCreatedEvent.signature = CustomEvent.FLAT;
          
          /**
            Takes one parameter:  A string, identifying the analysis variable for which a filter was just modifed (Form: X<var>n</var>).
          */
          this.FilterDataUpdateEvent = this.createEvent(EVENT_TYPES.FILTER_DATA_UPDATE);
          this.FilterDataUpdateEvent.signature = CustomEvent.FLAT;
          
          /**
            Notifies that a variable has entered or left a drag-drop list.
          */
          this.DDListChangedEvent = this.createEvent(EVENT_TYPES.DDLIST_CHANGED);
          this.DDListChangedEvent.signature = SIGNATURE;
          
          this.DDTargetEnabledStateUpdateEvent = this.createEvent(EVENT_TYPES.DDTARGET_ENABLEDSTATE_UPDATE);
          this.DDTargetEnabledStateUpdateEvent.signature = SIGNATURE;
          
          this.BeforeVariableBlurEvent = this.createEvent(EVENT_TYPES.BEFORE_VARIABLE_BLUR);
          this.BeforeVariableBlurEvent.signature = SIGNATURE;
          
          this.VariableBlurredEvent = this.createEvent(EVENT_TYPES.VARIABLE_BLURRED);
          this.VariableBlurredEvent.signature = SIGNATURE;
          
          this.VariableFocusedEvent = this.createEvent(EVENT_TYPES.VARIABLE_FOCUSED);
          this.VariableFocusedEvent.signature = SIGNATURE;
          
          this.BeforeDestroyingDDHandleEvent = this.createEvent(EVENT_TYPES.BEFORE_DESTROYING_DD_HANDLE);
          this.BeforeDestroyingDDHandleEvent.signature = CustomEvent.FLAT;
          
          this.PreviewCreatedEvent = this.createEvent(EVENT_TYPES.PREVIEW_CREATED);
          this.PreviewCreatedEvent.signature = CustomEvent.FLAT;
          
          this.PreviewRenderedEvent = this.createEvent(EVENT_TYPES.PREVIEW_RENDERED);
          this.PreviewRenderedEvent.signature = CustomEvent.FLAT;
          
          this.DescriptionPreviewContainerCreatedEvent = this.createEvent(EVENT_TYPES.DESCRIPTION_PREVIEW_CONTAINER_CREATED);
          this.DescriptionPreviewContainerCreatedEvent.signature = CustomEvent.FLAT;
          
          this.FormatChoiceDataUpdateEvent = this.createEvent(EVENT_TYPES.FC_DATA_UPDATE)
          this.FormatChoiceDataUpdateEvent.signature = CustomEvent.FLAT;
          
          this.ResponseListChosenEvent = this.createEvent(EVENT_TYPES.RESPONSE_LIST_CHOSEN);
          this.ResponseListChosenEvent.signature = CustomEvent.FLAT;
          
          this.ResponseListPreviewCreatedEvent = this.createEvent(EVENT_TYPES.RESPONSE_LIST_PREVIEW_CREATED);
          this.ResponseListPreviewCreatedEvent.signature = CustomEvent.FLAT;
          
          Logger.log('Finished.', 'trace', 'BaseApp.initEvents');
      },
      
      /**
        Loads everything from the DEFAULT_CONFIG object.
      */
      initDefaultConfig: function(){
          Logger.log('Called.', 'trace', 'BaseApp.initDefaultConfig');
          var objDetails;
          var aryDetails = ["handler", "value", "validator", "suppressEvent", "supercedes"];
          for(var cfgProp in DEFAULT_CONFIG){
              if(DEFAULT_CONFIG.hasOwnProperty(cfgProp)){
                  Logger.log('Initializing ' + DEFAULT_CONFIG[cfgProp].key + ', under key "' + cfgProp + '".', 'trace', 'BaseApp.initDefaultConfig');
                  objDetails = {};
                  for(var i=0; i<aryDetails.length; i++){
                      if(DEFAULT_CONFIG[cfgProp].hasOwnProperty(aryDetails[i])){
                          objDetails[ aryDetails[i] ] = DEFAULT_CONFIG[cfgProp][ aryDetails[i] ]
                      }
                  }
                  this.cfg.addProperty(DEFAULT_CONFIG[cfgProp].key, objDetails);
              }
          }
          Logger.log('Finished.', 'trace', 'BaseApp.initDefaultConfig');
      },
      
      /**
        @return {String} Returns a string representing this Analyzer instance.
      */
      toString: function(){
          return "Analyzer";
      },
      
      LoadAndHandleData: function(strQueryParm, funcEnvelopeHandler){
          var refAnalyzer = this;
          
          var _xhrFailure = function(oResponse){
              Logger.log("Called.", "trace", "BaseApp.LoadAndHandleData._xhrFailure");
              refAnalyzer.HTTPErrorEvent.fire({
                "strRequest": oResponse.argument.query,
                "oResponse": oResponse
              });
              Logger.log("Finished.", "trace", "BaseApp.LoadAndHandleData._xhrFailure");
          };
          
          var sTableURL = this.cfg.getProperty("MetadataTableURL");
          
          //Note the data's in transit
          this._MetadataQueue[sTableURL + strQueryParm] = true;
          
          var transaction = YAHOO.util.Connect.asyncRequest('GET', sTableURL + strQueryParm, {
            argument:{
              query:strQueryParm
            },
            failure: _xhrFailure,
            success: function(objResponse){
                //Augment transport object (they come with responseXML anyway, responseJSON's the logical next step)
                Logger.log("Called.", "trace", "BaseApp.LoadAndHandleData._xhrSuccess");
                Logger.log("Called for '" + objResponse.argument.query + "'.", "trace", "BaseApp.LoadAndHandleData._xhrSuccess");
                objResponse.responseJSON = YAHOO.lang.JSON.parse(objResponse.responseText);
                
                //Set up an error-reporting object in case things didn't go smoothly (smoothly being replyCode 200)
                var objError = {};
                for(var k in objResponse){
                    if(YAHOO.lang.hasOwnProperty(objResponse, k)){
                        objError[k] = objResponse[k];
                    }
                }
                objError.msg = objResponse.responseJSON.replyText;
                
                switch(objResponse.responseJSON.replyCode){
                    case 300:
                        refAnalyzer.DataWarningEvent.fire(objError);
                        //Fallthrough - warnings still execute
                    case 200:
                        //At this point, the data has come back good and workable.  Pass the envelope to the assistant function.
                        funcEnvelopeHandler(objResponse);
                    break;
                    case 500:
                        //Fallthrough
                    default:
                        refAnalyzer.DataErrorEvent.fire(objError);
                    break;
                }
            }
          });
      },
      
      UpdateTopicDropdown: function(){
          Logger.log("Called.", "trace", "BaseApp.UpdateTopicDropdown");
          var objEnvelope = this.MetadataTableStore["ds=analtopic"].responseJSON;
          
          var cboEl = Dom.get(this.cfg.getProperty("elTopicInput"));
          if(!cboEl){
              Logger.log("Unable to find cboEl looking for '" + this.cfg.getProperty("elTopicInput") + "'.", "error", "BaseApp.UpdateTopicDropdown");
          } else {
              cboEl.options.length = 0;
              
              for(var i=0, j=objEnvelope.data.length; i<j; i++){
                  cboEl.options[cboEl.options.length] = new Option(objEnvelope.data[i].TopicTitle, objEnvelope.data[i].TopicName, false, false);
              }
              
              this.TopicChangedEvent.fire();
          }
          Logger.log("Finished.", "trace", "BaseApp.UpdateTopicDropdown");
      },
      
      /**
        Destroys TreeView widget.
      */
      DestroyCategoryTree: function(){
          this.accordion.removeChildren(this.accordion.getRoot());
          
          //var elTree = Dom.get(this.cfg.getProperty("strCategoryTreeElId"));
          //if(!elTree){
          //    Logger.log("Category Tree element could not be found.","error","BaseApp.DestroyCategoryTree");
          //} else {
          //    elTree.innerHTML = "";
          //}
      },
      
      /**
        Regenerates the <code>TreeView</code> of variables
      */
      GenerateCategoryTree: function(){
          Logger.log("Called.", "trace", "BaseApp.GenerateCategoryTree");
          if(!Dom.get(this.cfg.getProperty("strCategoryTreeElId"))){
              Logger.log("Nop.", "debug", "BaseApp.GenerateCategoryTree");
          } else {
              var tree = this.accordion;
              
              var curTopicName = Dom.get(this.cfg.getProperty("elTopicInput")).value;
              var curTopicMD = this.IndexedMetadata["analtopic"][ "TopicName=" + curTopicName ];
              if(!curTopicMD){
                  Logger.log("Unable to look up metadata for topic aned '" + curTopicName + "'.", "error", "BaseApp.GenerateCategoryTree");
              } else {
                  var curTopicID = curTopicMD.TopicID;
                  
                  //Build first tier of input data:  The list of categories
                  
                  //Collect list of category IDs appropriate to the current topic
                  var aryUseCats = [];
                  for(var i=0, aryCats = this.MetadataTableStore["ds=analcat"].responseJSON.data; i<aryCats.length; i++){
                      //Logger.log("Checking " + curTopic + " vs. " + aryCats[i].TopicName, "debug", "BaseApp.GenerateCategoryTree");
                      if(aryCats[i].TopicID == curTopicID){
                          aryUseCats.push(aryCats[i]);
                      }
                  }
                  
                  //Get category metadata
                  var objLookupTitles = this.IndexedMetadata["variablecat"];
                  var aryNewOptData = [];
                  for(var row, j=0; j<aryUseCats.length; j++){
                      row = objLookupTitles[ "CatID="+aryUseCats[j].CatID ]
                      aryNewOptData.push({
                        meta: row,
                        label: row.CatName
                      });
                  }
                  //Logger.log(YAHOO.lang.JSON.stringify(aryNewOptData), "debug", "BaseApp.GenerateCategoryTree");
                  
                  var ywNode = (true) ? YAHOO.widget.MenuNode : YAHOO.widget.TextNode;
                  
                  for(var tmpNode, tmpChild, htmlBuilder, i=0; i<aryNewOptData.length; i++){
                      //Build header bar
                      //Always have these nodes expanded, because lazy loading causes the drag-drop initializations to fail (HTML isn't rendered until node is expanded)
                      tmpNode = new ywNode(aryNewOptData[i], tree.getRoot(), true);
                      
                      //Build child node - the drag/drop list
                      htmlBuilder = '<ul class="varbin generated" id="' + this.MakeCatID(aryNewOptData[i].meta.CatID) + '"></ul>';
                      tmpChild = new YAHOO.widget.HTMLNode({html:htmlBuilder}, tmpNode, false, false);
                  }
                  
                  //Render the tree to create the drag-drop lists we'll need 
                  tree.draw();
                  
                  
                  for(var i=0; i<aryNewOptData.length; i++){
                      Logger.log("Checking for DOM node:  " + Dom.get(this.MakeCatID(aryNewOptData[i].meta.CatID)), "debug", "BaseApp.GenerateCategoryTree");
                      this.BVListCreatedEvent.fire({
                        CatID: aryNewOptData[i].meta.CatID,
                        type:  EVENT_TYPES.BVLIST_CREATED
                      });
                  }
                  
                  //Hide all but the first category
                  for(var i=1, aryChildren = tree.getRoot().children; i<aryChildren.length; i++){
                      aryChildren[i].collapse();
                  }
                  
                  //this.CategoryChangedEvent.fire();
              }
          }
          Logger.log("Finished.", "trace", "BaseApp.GenerateCategoryTree");
      },
      
      
      /**
        Repository for storing choice of formats (response lists in HYS context).  Indexed by DOM Var ID.
        @property IndexedFormatChoiceData
        @type {Object}
      */
      IndexedFormatChoiceData: {},
      
      /**
        @param p_elDDVar {HTMLElement | String} Element with associated YUI drag-drop object.
        @return Returns format choice, or default value.
      */
      GetFormatChoice: function(p_elDDVar){
          //First, load the default value, which should be chosen if there's a choice.
          var retval;
          var defvarpriority = this.cfg.getProperty("DefaultVarFormatPriority");
          //First, check for the previous selection.
          var elLookup = Dom.get(p_elDDVar);
          if(!elLookup){
              Logger.log("Could not find element from reference '" + p_elDDVar + "'.", "debug", "BaseAPp.GetFormatChoice");
          } else {
              if(this.IndexedFormatChoiceData[elLookup.id]){
                  retval = this.IndexedFormatChoiceData[elLookup.id];
              } else {
                  //Second, check to see that we have a choice by looking up all available options.
                  var aryPriorities = this.IndexedMetadata["varpriorities"][ "VarID=" + this.ExtractDDDOMVarID(elLookup.id) ];
                  if(!aryPriorities){
                      Logger.log("Unable to look up variable priorities for '" + elLookup.id + "'.", "error", "BaseApp.GetFormatChoice");
                  } else {
                      //Seek; if default priority isn't available, just take the first available, as there's no runner-up default defined.
                      var objPriorities = {};
                      for(var i=0; i<aryPriorities.length; i++){
                          objPriorities[ "_" + aryPriorities[i].Priority ] = true;
                      }
                      
                      if(objPriorities[ "_" + defvarpriority ]){
                          retval = defvarpriority;  //It's available - keep it.
                      } else {
                          //Default unavailable - take whatever's there, null if nothing.
                          if(aryPriorities[0]){
                              retval = aryPriorities[0].Priority;
                          } else {
                              retval = null;
                          }
                          
                      }
                  }
              }
              
          }
          return retval;
      },
      
      /**
        Will need to be called at least when the topic changes.
      */
      ClearQueryBuilder: function(){
          Logger.log("Called.","trace","BaseApp.ClearQueryBuilder");
          
          Logger.log("Purging filter data from indexed data.", "trace", "BaseApp.ClearQueryBuilder");
          Logger.log(YAHOO.lang.JSON.stringify(this.IndexedFilterData), "debug", "BaseApp.ClearQueryBuilder");
          for(var key in this.IndexedFilterData) if (YAHOO.lang.hasOwnProperty(this.IndexedFilterData, key)) {
              delete this.IndexedFilterData[key];
          }
          //Just call the update function - this updates hidden form fields.  The other methods update DOM elements that are about to disappear.
          Logger.log("Purging filter data from interface.", "trace", "BaseApp.ClearQueryBuilder");
          this.UpdateFiltersRepresentation();
          
          Logger.log("Purging format choice data.", "trace", "BaseApp.ClearQueryBuilder");
          for(var key in this.IndexedFormatChoiceData){
              delete this.IndexedFormatChoiceData[key];
          }
          
          //Empty all the UL's, updating their inputs.
          //The least politically-correct way in the world to say this:  "Well, we gotta blow away all those children."
          var aryNormalDropTargets = Dom.getElementsByClassName("varbin", "ul", "dragdroparea");
          //Logger.log("Container list built.  " + aryNormalDropTargets.length + " elements.","debug","BaseApp.ClearQueryBuilder");
          var elChild;
          for(var i=0; i<aryNormalDropTargets.length; i++){
              Logger.log("Pruning tree of " + aryNormalDropTargets[i].id,"trace","BaseApp.ClearQueryBuilder");
              while(aryNormalDropTargets[i].hasChildNodes()){
                  elChild = aryNormalDropTargets[i].lastChild;
                  this.BeforeDestroyingDDHandleEvent.fire(elChild);
                  aryNormalDropTargets[i].removeChild(elChild);  //Note that the reference to elChild is still alive
                  //Logger.log("Removed from list " + aryNormalDropTargets[i].id + " child " + elChild.id, "debug", "BaseApp.ClearQueryBuilder");
              }
              
              this.DDListChangedEvent.fire(aryNormalDropTargets[i]);
          }
          Logger.log("Finished pruning trees.", "trace", "BaseApp.ClearQueryBuilder");
          
          try{
              //Resetting checkboxes to initial display
              var elTmp, aryQueue;
              for(var k in this.InitialCheckboxStates) if(YAHOO.lang.hasOwnProperty(this.InitialCheckboxStates, k)){
                  elTmp = Dom.get(k);
                  if(!elTmp){
                      Logger.log("Checkbox not found from reference '" + k + "'.", "error", "BaseApp.ClearQueryBuilder")
                  } else {
                      elTmp.checked = this.InitialCheckboxStates[k];
                      
                      //Fire all 'click' and 'change' listeners by building a queue in index order.
                      aryQueue = [];
                      for(var i=0, aryListeners = Event.getListeners(elTmp); aryListeners && aryListeners.length && i<aryListeners.length; i++){
                          //For reference on these objects, look in source code of YAHOO.util.Event.getListeners.  Basically, we have these to work with:
                          //{
                          //  type:   l[this.TYPE],
                          //  fn:     l[this.FN],
                          //  obj:    l[this.OBJ],
                          //  adjust: l[this.OVERRIDE],
                          //  scope:  l[this.ADJ_SCOPE],
                          //  index:  i
                          //}
                          //Logger.log(aryListeners[i], "debug", "BaseApp.ClearQueryBuilder");
                          switch(aryListeners[i].type){
                              case "click":  //Fallthrough
                              case "change":
                                  aryQueue[aryListeners[i].index] = aryListeners[i];
                              break;
                              default:
                              break;
                          }
                      }
                      for(var j=0; j<aryQueue.length; j++){
                          if(aryQueue[j]){
                              try{
                                  aryQueue[j].fn.call(aryQueue[j].scope, aryQueue[j].obj);
                              } catch(e) {
                                  Logger.log(YAHOO.lang.JSON.stringify(e), "error", "BaseApp.ClearQueryBuilder");
                                  Logger.log(aryQueue[j], "debug", "BaseApp.ClearQueryBuilder");
                              }
                          }
                      }
                  }
              }
          } catch(e){
              Logger.log(e.message, "error", "BaseApp.ClearQueryBuilder");
              Logger.log(e, "debug", "BaseApp.ClearQueryBuilder");
          }
          
          Logger.log("Finished.","trace","BaseApp.ClearQueryBuilder");
      },
      
      /**
        Index of "checked" states by checkbox element ID's.  No id -> no state tracking.
        @property InitialCheckboxStates
        @type Object
      */
      InitialCheckboxStates: {},
      
      /**
        Reads states of checkboxes on Analyzer instantiation.
      */
      initCheckboxStates: function(){
          for(var i=0, aryChecks = Dom.get("dragdroparea").getElementsByTagName("input"); i<aryChecks.length; i++){
              if(aryChecks[i].type=="checkbox"){
                  if(aryChecks[i].id){
                      this.InitialCheckboxStates[ aryChecks[i].id ] = aryChecks[i].checked;
                  } else {
                      Logger.log("Skipped logging the initial state of a checkbox named '" + aryChecks[i].name + "', because it had no id.", "warning", "BaseApp.initCheckboxStates");
                  }
              }
          }
      },
      
      /**
        Method to handle the mechanics of clearing away all references to a Drag-Drop object.
        @param p_el {HTMLElement | String} Drag-drop handle to deregister
      */
      DeregisterDDObj: function(p_el){
          var elDD = Dom.get(p_el);
          if(!elDD){
              Logger.log("Element not found by reference:  " + p_el, "error", "BaseApp.DeregisterDDObj");
          } else {
              if(!this.objDDProxies[ elDD.id ]){
                  Logger.log("Element not registered in drag-drop index.", "error", "BaseApp.DeregisterDDObj");
              } else {
                  delete this.objDDProxies[ elDD.id ];
              }
          }
          
          //Logger.log("Deregistered DD object " + varid, 'trace', 'BaseApp.DeregisterDDObj');
      },
      
      IsDDRegistered: function(varid){
          return !! this.objDDProxies[ varid ];
      },
      
      /**
        This is the only Drag-Drop registration method that optionally takes an element or element ID as an argument.
      */
      RegisterDDObj: function(DDRef){
          var elDDHook = Dom.get(DDRef); 
          this.objDDProxies[ elDDHook.id ] = new (this.cfg.getProperty("DragDropClass"))(elDDHook, "analyzerdd_" + elDDHook.id);
      },
      
      onDataSourcesLoaded: function(){
          var objEnvelope = this.MetadataTableStore["ds=datasource"].responseJSON;
          
          if(this.cfg.getProperty("ShowDataSources") === true){
              Dom.setStyle("elMultipleDataSourceRevealer", "display", "inline"); //Note that this returns void; so just hope it worked.
          }
          
          var
            elSelFM = Dom.get(this.cfg.getProperty("elTimeInput_FM")),
            elSelFY = Dom.get(this.cfg.getProperty("elTimeInput_FY")),
            elSelTM = Dom.get(this.cfg.getProperty("elTimeInput_TM")),
            elSelTY = Dom.get(this.cfg.getProperty("elTimeInput_TY"))
          ;
          
          //Augment the data with JavaScript dates
          var dateMin = null, dateMax = null, curMinDate, curMaxDate;
          for(var i=0; i<objEnvelope.data.length; i++){
              curMinDate = new Date(parseInt(objEnvelope.data[i]["MinFromYear"],10), parseInt(objEnvelope.data[i]["MinFromMonth"],10)-1);
              curMaxDate = new Date(parseInt(objEnvelope.data[i]["MaxFromYear"],10), parseInt(objEnvelope.data[i]["MaxFromMonth"],10), 0);  //Should this be beginning or end of month?  End, for cases of single-date inputs like Snohomish.
              objEnvelope.data[i]["MinDate"] = curMinDate;
              objEnvelope.data[i]["MaxDate"] = curMaxDate;
              
              //Find year range
              if(dateMin === null) dateMin = curMinDate;
              if(dateMax === null) dateMax = curMaxDate;
              
              dateMin = new Date(Math.min(dateMin.valueOf(), curMinDate.valueOf()));
              dateMax = new Date(Math.max(dateMax.valueOf(), curMaxDate.valueOf()));
          }
          
          
          if(dateMin === null || dateMax === null){
              Logger.log('The date boundaries are NULL due to lack of data.', 'error', 'BaseApp.onDataSourcesLoaded');
          } else {
              //Update interface to reflect date range possible
              var
                elTimeBound_FM = Dom.get(this.cfg.getProperty("elTimeBound_FM")),
                elTimeBound_FY = Dom.get(this.cfg.getProperty("elTimeBound_FY")),
                elTimeBound_TM = Dom.get(this.cfg.getProperty("elTimeBound_TM")),
                elTimeBound_TY = Dom.get(this.cfg.getProperty("elTimeBound_TY"))
              ;
              if(!elTimeBound_FM){Logger.log("Couldn't find elTimeBound_FM.","warning","BaseApp.onDataSourcesLoaded")} else {elTimeBound_FM.innerHTML = MONTH_NAMES[dateMin.getMonth()];}
              if(!elTimeBound_TM){Logger.log("Couldn't find elTimeBound_TM.","warning","BaseApp.onDataSourcesLoaded")} else {elTimeBound_TM.innerHTML = MONTH_NAMES[dateMax.getMonth()];}
              if(!elTimeBound_FY){Logger.log("Couldn't find elTimeBound_FY.","warning","BaseApp.onDataSourcesLoaded")} else {elTimeBound_FY.innerHTML = dateMin.getFullYear();}
              if(!elTimeBound_TY){Logger.log("Couldn't find elTimeBound_TY.","warning","BaseApp.onDataSourcesLoaded")} else {elTimeBound_TY.innerHTML = dateMax.getFullYear();}
              
              //Initialize to init-configured date range by setting the beginning and ending input dates
              var
                dateInitBeg = new Date(dateMin.valueOf()),
                dateInitEnd = new Date(dateMax.valueOf())
              ;  //These dates are just-in-case defaults
              var objOffsetData = this.cfg.getProperty("DefaultMonthOffset");
              var intMonthSpan = this.cfg.getProperty("DefaultNumMonths");
              if( !(YAHOO.lang.isNumber(intMonthSpan) && intMonthSpan > 0) ){
                  //Bug, but not a show-stopper.
                  Logger.log("Unexpected value of DefaultNumMonths:"  + intMonthSpan, "error", "BaseApp.onDataSourcesLoaded");
              } else {
                  if(!YAHOO.lang.isNumber(objOffsetData.numMonths)){
                      //Bug, but not a show-stopper.
                      Logger.log("Unexpected value of DefaultMonthOffset.numMonths:"  + objOffsetData.numMonths, "error", "BaseApp.onDataSourcesLoaded");
                  } else {
                      Logger.log("objOffsetData.numMonths:  " + objOffsetData.numMonths, "trace", "BaseApp.onDataSourcesLoaded");
                      switch(objOffsetData.from){
                          case "beginning":
                              dateInitBeg.setMonth(dateInitBeg.getMonth() + objOffsetData.numMonths);
                              //Reset ending date to initial date, adjusting the months according to the configured span
                              dateInitEnd = new Date(dateInitBeg.valueOf());
                              dateInitEnd.setMonth(dateInitEnd.getMonth() + intMonthSpan-1);
                          break;
                          case "end":
                              dateInitEnd.setMonth(dateInitEnd.getMonth() - objOffsetData.numMonths);
                              dateInitBeg = new Date(dateInitEnd.valueOf());
                              dateInitBeg.setMonth(dateInitBeg.getMonth() - intMonthSpan+1);
                          break;
                          default:
                              //Bug, but not a show-stopper.
                              Logger.log("Unexpected value of DefaultMonthOffset.from:  " + objOffsetData.from, "error", "BaseApp.onDataSourcesLoaded");
                          break;
                      }
                  }
              }
              
              //Define default year ranges in SELECT elements
              var
                iMinYear = dateMin.getFullYear(),
                iMaxYear = dateMax.getFullYear()
              ;
              var cboEl, yrDefault;
              for(var j=0, aryYearBoxes=[elSelFY,elSelTY], aryDefaultDates=[dateInitBeg,dateInitEnd]; j<aryYearBoxes.length; j++){
                  cboEl = aryYearBoxes[j];
                  cboEl.options.length = 0;
                  
                  yrDefault = aryDefaultDates[j].getFullYear();
                  for(var k=iMinYear; k<=iMaxYear; k++){
                      cboEl.options[cboEl.options.length] = new Option(k, k, false, k==yrDefault);
                  }
              }
              
              //Select default months
              for(var k=0, aryMonthBoxes = [elSelFM, elSelTM], aryDefaultDates=[dateInitBeg,dateInitEnd]; k<aryMonthBoxes.length; k++){
                  aryMonthBoxes[k].options[aryDefaultDates[k].getMonth()].selected = true;
              }
          }
          
          //Notify that the dates have changed
          this.DateChangedEvent.fire();
      },
      
      /**
        Make hash tables for quick lookups later
      */
      MakeMetadataIndex: function(p_strQuery, p_strKey){
          Logger.log("Called.", "trace", "BaseApp.MakeMetadataIndex");
          //Build an index for quick category lookups
          var objIndexedMetadata = {};
          var objTransport = this.MetadataTableStore[p_strQuery];
          if(!objTransport.responseJSON){
              //How to note this error?
              Logger.log("responseJSON object not found on data transport.", "error", "BaseApp.MakeMetadataIndex");
          } else {
              for(var i=0, aryMDRecs = objTransport.responseJSON.data; i<aryMDRecs.length; i++){
                  objIndexedMetadata[ p_strKey +"="+ aryMDRecs[i][p_strKey] ] = aryMDRecs[i];
              }
          }
          this.IndexedMetadata[ p_strQuery.split("=")[1] ] = objIndexedMetadata;
          Logger.log("Finished.", "trace", "BaseApp.MakeMetadataIndex");
      },
      
      /**
        The indexed functions here will be called after the page is loaded, so the Analyzer object can be referenced directly.
        @property PreloadMetadataQueriesAndActions
        @type {Object}
      */
      PreloadMetadataQueriesAndActions: {
        "ds=analtopic":function(){
            //Index on TopicName to look up TopicID quicker.  (Yes, that is backwards-looking on purpose.  The back-end expects TopicName for the dropdown value, not TopicID; but the metadata works with TopicID in CatVar.  --AJN 20080617)
            this.MakeMetadataIndex("ds=analtopic", "TopicName");
        },
        "ds=subtopic":function(){
            //No indexing for the subtopic table; there's no ID column.
        },
        "ds=analcat":function(){
            //Building an index for quick lookups can't be done - this is a multi-to-multi table.
        },
        "ds=variablecat":function(){
            //Build an index for quick category lookups
            this.MakeMetadataIndex("ds=variablecat", "CatID");
        },
        "ds=datasource":function(){
            this.onDataSourcesLoaded();
        },
        "ds=catvar":function(){
            //Building an index for quick lookups can't be done - this is a multi-to-multi table.
        },
        "ds=breakout_variables":function(){
            //Build an index for quick category lookups
            this.MakeMetadataIndex("ds=breakout_variables", "VarID");
        },
        "ds=varpriorities":function(){
            var objIndexedMetadata = {};
            for(var i=0, aryMDRecs = this.MetadataTableStore[ "ds=varpriorities" ].responseJSON.data, keyCurrent; i<aryMDRecs.length; i++){
                keyCurrent = "VarID="+ aryMDRecs[i]["VarID"];
                if(!objIndexedMetadata[ keyCurrent ]){
                    objIndexedMetadata[ keyCurrent ] = [];
                }
                objIndexedMetadata[ keyCurrent ].push(aryMDRecs[i]);
            }
            this.IndexedMetadata["varpriorities"] = objIndexedMetadata;
        }
      },
      
      /**
        Destructively updates the visible source list of variables, both in DOM and JS drag-drop objects
        @param {Object} p_oArgs Object with members "type" and "CatID".  If "type" is absent, this will update the HTML element <code>ulDDSource</code>.  If "CatID" is absent, this fails.
      */
      UpdateBreakoutVariablesList: function(p_oArgs){
          Logger.log("Called.", "trace", "BaseApp.UpdateBreakoutVariablesList");
          Logger.log(p_oArgs, "debug", "BaseApp.UpdateBreakoutVariablesList");
          this.BeforeUpdateBVsEvent.fire()
          
          var elTmp, elSrc;
          switch(p_oArgs.type){
              case EVENT_TYPES.CATEGORY_CHANGED:
                  elSrc = Dom.get( "ulDDSource" );
              break;
              case EVENT_TYPES.BVLIST_CREATED:
                  elSrc = Dom.get( this.MakeCatID(p_oArgs.CatID) );
              break;
              default:
                  Logger.log("Unexpected event being handled by UpdateBreakoutVariablesList; unsure what HTMLUlElement to update.  Defaulting to ulDDSource.", "debug", "BaseApp.UpdateBreakoutVariablesList");
                  elSrc = Dom.get( "ulDDSource" );
              break;
          }
          
          if(!elSrc){
              Logger.log("Found no element to update.", "error", "BaseApp.UpdateBreakoutVariablesList");
          } else {
              Logger.log("elSrc's ID is " + elSrc.id, "debug", "BaseApp.UpdateBreakoutVariablesList");
              
              var curCat = p_oArgs.CatID;
              if(!curCat){
                  Logger.log("No CatID supplied in argument object; list can't be built.", "error", "BaseApp.UpdateBreakoutVariablesList");
              } else {
                  //Logger.log("Removing old children from DOM and drag-drop object list.", "trace", "BaseApp.UpdateBreakoutVariablesList");
                  //Use DOM methods instead of innerHTML so we can capture IDs on their way out
                  while(elSrc.hasChildNodes()){
                      elTmp = elSrc.removeChild(elSrc.lastChild);
                      //Logger.log("elTmp's ID is " + elTmp.id, "debug", "BaseApp.UpdateBreakoutVariablesList");
                      if(! this.IsDDRegistered(elTmp.id)){
                          Logger.log("Found no stored DD object for " + elTmp.id + ".  This shouldn't happen.", "warning", "BaseApp.UpdateBreakoutVariablesList");
                      } else {
                          this.DeregisterDDObj(elTmp);
                      }
                      //Logger.log("Removed from " + elSrc.id + " child " + elTmp.id, "debug", "BaseApp.UpdateBreakoutVariablesList");
                  }
                  
                  var aryNewVars = [];
                  //The variables to create are IN the associated category
                  
                  //Logger.log("curCat = " + curCat, "debug", "BaseApp.UpdateBreakoutVariablesList");
                  
                  Logger.log("Filtering.", "trace", "BaseApp.UpdateBreakoutVariablesList");
                  var aryCatVarRows = [];
                  for(var i=0, aryCatVarBase=this.MetadataTableStore["ds=catvar"].responseJSON.data; i<aryCatVarBase.length; i++){
                      if(aryCatVarBase[i].CatID == curCat) aryCatVarRows.push(aryCatVarBase[i]);
                  }
                  
                  Logger.log("Building list of variables (selecting records from breakout_variables table dump).", "trace", "BaseApp.UpdateBreakoutVariablesList");
                  //Remove from this list any variables already in use in the form
                  var aryBVs = [];
                  for(var j=0; j<aryCatVarRows.length; j++){
                      tmpID = aryCatVarRows[j].VarID;
                      if(this.IsDDRegistered(this.MakeDDDOMVarID(tmpID))){
                          //This variable is already in use - no need to recreate.
                          //Logger.log("Skipped listing " + tmpID + " for creation.", "debug", "BaseApp.UpdateBreakoutVariablesList");
                      } else {
                          aryBVs.push(this.IndexedMetadata["breakout_variables"][ "VarID=" + tmpID ]);
                          //Logger.log("Listed " + tmpID + " for creation.", "debug", "BaseApp.UpdateBreakoutVariablesList");
                      }
                  }
                  
                  Logger.log("Creating new HTML children and JS objects for dragging and dropping.", "trace", "BaseApp.UpdateBreakoutVariablesList");
                  
                  //These may be drag-drop interaction targets if we're in the dropdown-list approach to variable displays.
                  var blnUseVarName = this.cfg.getProperty("PrefixVarLabelWithVarname");
                  var newid;
                  for(var k=0; k<aryBVs.length; k++){
                      newid = this.MakeDDDOMVarID(aryBVs[k].VarID);
                      if(Dom.get(newid)){
                          Logger.log("Element already exists:  '" + newid + "'.", "error", "BaseApp.UpdateBreakoutVariablesList");
                      } else {
                          elTmp = document.createElement("li");
                          elTmp.id = newid;
                          Dom.addClass(elTmp, "CatID:" + curCat); //Store reference to category for hidden inputs
                          elTmp.innerHTML = (blnUseVarName ? "[" + aryBVs[k].VarName + "] " : "") + aryBVs[k].VarLabel;
                          Dom.addClass(elTmp, "anavar");
                          elSrc.appendChild(elTmp);
                          this.RegisterDDObj(elTmp);
                      }
                  }
                  
                  if(p_oArgs.suppressScroll === true){
                      //Don't scroll.
                  } else {
                      var animScroll = new YAHOO.util.Scroll(
                        elSrc,
                        {
                          scroll:{
                            to:[0, 0]
                          }
                        },
                        0
                      );
                      animScroll.animate();
                  }
                  
                  //Notify that this list has been updated
                  this.BVsUpdatedEvent.fire(elSrc);
              }
          }
          Logger.log("Finished.", "trace", "BaseApp.UpdateBreakoutVariablesList");
      },
      
      MakeDDDOMVarID: function(id){
          return "X" + id;
      },
      ExtractDDDOMVarID: function(id){
          return parseInt(id.substring(1),10);
      },
      
      MakeFilterVarID: function(id){
          return "Filter_" + id;
      },
      ExtractFilterVarID: function(id){
          return id.substring("Filter_".length);
      },
      
      MakeCatID: function(id){
          return "Cat" + id;
      },
      ExtractCatID: function(id){
          return parseInt(id.substring(3),10);
      },
      
      /**
        Kick off the interface updates.
        @param {Boolean} p_blnLast True -> this is the lowest inherited method, and when this is done, the application is fully initialized.
      */
      onAllMetadataLoaded: function(p_blnLast){
          //try{
              Logger.log("Called.", "trace", "BaseApp.onAllMetadataLoaded");
              
              this.SwapChartTableAnalysisForms();
              
              this.UpdateTopicDropdown();
              
              //Display the initially-hidden Analyzer, and hide the loading prompt
              Dom.setStyle("AnalyzerLoading", "display", "none");
              Dom.setStyle("AnalyzerInterfaceContainer", "display", "block");
              
              if(p_blnLast){
                  this._initialized = true;
              }
              
              Logger.log("Finished.", "trace", "BaseApp.onAllMetadataLoaded");
          //} catch(e) {
          //    alert(YAHOO.lang.JSON.stringify(e));
          //}
      },
      
      /**
        True -> The application is finished loading - including all of the initialization metadata.
        @property _initialized
        @type Boolean
      */
      _initialized: false,
      
      /**
        A list of all metadata currently in transit.  Logged as URI's (Strings), typically but not necessarily of the form URL + query parameter(s).
        @property _MetadataQueue
        @type Object
      */
      _MetadataQueue: {},
      
      /**
        Fires the AllMetadataLoadedEvent once all XMLHTTP requests have finished; note that this does not check whether they finished SUCCESSFULLY.
        This function assumes it has the scope of the Analyzer object.  Make sure to subscribe with this in mind.
        @param {String} eventType YAHOO Custom Event string identifier.
        @param {Array} aryParams Arguments to .fire, passed as an array due to LIST signature of event.  Two optional values are expected.
      */
      MaybeFireAllMetadataLoaded: function(eventType, aryParams){
          var strURL = aryParams[0] || "";
          var strQuery = aryParams[1] || "";
          var strURI = strURL + strQuery;
          
          this._MetadataQueue[strURI] = false;
          
          var iTransitTally = 0;
          for(var key in this._MetadataQueue) if (YAHOO.lang.hasOwnProperty(this._MetadataQueue, key)) {
              if (this._MetadataQueue[key]) iTransitTally++;
          }
          if(iTransitTally == 0) this.AllMetadataLoadedEvent.fire(true);
      },
      
      ReflectTopic: function(){
          var elDfn = Dom.get(this.cfg.getProperty("elTopicReflect"));
          var elIn = Dom.get(this.cfg.getProperty("elTopicInput"));
          elDfn.innerHTML = elIn.options[elIn.selectedIndex].text;
      },
      
      /**
        Visually updates date reflections.
      */
      ReflectDate: function(){
          var elReflect = Dom.get("elTimeReflect");
          if(!elReflect){
              Logger.log("Could not find reflection element.", "error", "BaseApp.ReflectDate");
          } else {
              var aryInputs = Dom.get(["FromMonth","FromYear","ToMonth","ToYear"]);
              if(!(aryInputs[0] && aryInputs[1] && aryInputs[2] && aryInputs[3])){
                  Logger.log("Could not find all date input elements.", "error", "BaseApp.ReflectDate");
                  Logger.log(aryInputs, "debug", "BaseApp.ReflectDate");
              } else {
                  var htmlReflect = MONTH_NAMES[parseInt(aryInputs[0].value, 10)-1] + ", " + aryInputs[1].value + " through " + MONTH_NAMES[parseInt(aryInputs[2].value, 10)-1] + ", " + aryInputs[3].value;
                  elReflect.innerHTML = htmlReflect;
              }
          }
      },
      
      /**
        @param {Object} arg Expected to have:  <code>strRequest</code>, <code>oResponse</code>.
      */
      onHTTPError: function(arg){
          Logger.log("Called.", "trace", "BaseApp.onHTTPError");
          
          //DEBUG
          //var aryDump = [];
          //for(var key in arg){
          //    aryDump.push(key);
          //}
          //Logger.log(YAHOO.lang.JSON.stringify(aryDump.join(", ")), "debug", "BaseApp.onHTTPError");
          //Logger.log(YAHOO.lang.JSON.stringify(arg.request), "debug", "BaseApp.onHTTPError");
          //Logger.log(YAHOO.lang.JSON.stringify(arg.callback), "debug", "BaseApp.onHTTPError");
          //Logger.log(arg.caller, "debug", "BaseApp.onHTTPError");
          //Logger.log(YAHOO.lang.JSON.stringify(arg.message), "debug", "BaseApp.onHTTPError");
          
          
          //Return a subset of the arguments, because not all components of the argument object can be stringified (e.g. any reference to the Analyzer).
          
          //Note:  Do NOT include responseXML here, IE will silently puke on it, which was a tedious hour for me to hunt down.  --AJN 20080619
          var aryRepParams = ["error", "getAllResponseHeaders", "getResponseHeader", "responseText", "status", "statusText", "tId"];
          
          var argsubset = {
            request: arg.strRequest
          };
          for(var tmpval, i=0; i<aryRepParams.length; i++){
              tmpval = arg.oResponse[ aryRepParams[i] ];
              if(tmpval){
                  argsubset[ aryRepParams[i] ] = tmpval;
              }
          }
          
          Logger.log("Defining message body.", "trace", "BaseApp.onHTTPError");
          
          var strBodyHTML = [
            "<p>There was a server error loading the requested query.</p>",
            this.WriteReportErrorHTML(argsubset),
            ( (argsubset.status == 0) ? "<p>A note:  The type of error you encountered, an HTTP communication failure, is potentially rectifiable by simply trying your action again.  Please report this error with the above link if you've encountered it twice.</p>" : '')
          ].join("");
          
          this.ErrorPanel.setHeader("Server Error");
          this.ErrorPanel.setBody(strBodyHTML);
          this.ErrorPanel.show();
          
          Logger.log("Finished.", "trace", "BaseApp.onHTTPError");
      },
      
      /**
        Function that returns HTML to assist users with reporting an error.  Basically provides a stringified Object for debugging help.
        @param p_oSupport {Object} Data to help with debugging.  Try to keep references to an Analyzer instance out of this, as that will cause an infinite circular reference.
        @return {String} Returns an HTML paragraph that includes an <code>&lt;a href="mailto:..."&gt;</code> element.
      */
      WriteReportErrorHTML: function(p_oSupport){
          Logger.log("Called.", "trace", "BaseApp.WriteReportErrorHTML");
          
          return [
            "<p>If you would like to report this issue to technical support, you can send a bug report by e-mail with ",
            '<a href="mailto:',
            this.cfg.getProperty("Support"),
            '?subject=[',
            this.cfg.getProperty("Sysname"),
            '] Error report&amp;body=',
            encodeURI(
              'Hello,\n\nI encountered an issue while using the Analyzer service.  The website supplied the below code to help with debugging.\n\n' +
              YAHOO.lang.JSON.stringify(p_oSupport)
            ).replace(/\&/g,"%26"),   //encodeURI misses the ampersand; note that the 'g' flag must be set to get all the ampersands.
            '">this link</a>; the message body includes technical code which will help resolve the issue.</p>'
          ].join("");
          
          Logger.log("Finished.", "trace", "BaseApp.WriteReportErrorHTML");
      },
      
      /**
        Executes in the context of the FilterDialog.
      */
      onDialogCancel: function(){
          this.cancel();
      },
      
      /**
        Executes in the context of the FilterDialog.
      */
      onFilterSubmit: function(e){
          //Event.preventDefault(e);  //Stop event from propagating to a full submission
          this.submit();
      },
      
      /**
        Executes in the context of the FilterDialog.
      */
      onFilterClear: function(e){
          this.ClearList();
          this.submit();
      },
      
      /**
        Destructively updates IndexedFilterData at the index of the variable ID passed from the dialog.
        Executes in the context of the Analyzer instance.
      */
      onFilterManualSubmit: function(){
          var objData = this.FilterDialog.getData();
          Logger.log(YAHOO.lang.JSON.stringify(objData), "debug", "BaseApp.onFilterManualSubmit");
          
          this.IndexedFilterData[objData.varid] = objData;
          this.FilterDataUpdateEvent.fire(objData.varid);
      },
      
      /**
        Associative array that maintains the state of all filters applied in the form.
        @property IndexedFilterData
      */
      IndexedFilterData: {},
      
      /**
        Modifies a filter's indexed data, destroying the stored value list.  Then calls <code>this.UpdateFiltersRepresentation</code> to update application state.
        Removes a filter's visual representation (CSS filter-applied class) and indexed filter data.
      */
      RemoveFilter: function(p_varid){
          Logger.log("Called.", "trace", "BaseApp.RemoveFilter");
          
          var objFilt = this.IndexedFilterData[p_varid];
          if(!objFilt){
              Logger.log("Unable to find indexed filter data for '" + p_varid + "'.  Skipping refresh.", "debug", "BaseApp.RemoveFilter");
          } else {
              if(objFilt.filterdialoglist && objFilt.filterdialoglist instanceof Array) objFilt.filterdialoglist.length = 0;
              this.UpdateFiltersRepresentation();
          }
          Logger.log("Finished.", "trace", "BaseApp.RemoveFilter");
      },
      
      /**
        Clears out extraneous/unused filters in the filter index (empty value lists).
        Refreshes CSS classes on drag-drop variables.
        Totally recreates <code>hidWhereClause</code> value, wiping out previous value.  Builds <code>hidWhereClause</code> from <code>this.IndexedFilterData</code>.
        @argument {String} p_type Unused.
        @argument {Array} p_args Unused.
      */
      UpdateFiltersRepresentation: function(p_type, p_args){
          Logger.log("Called.", "trace", "BaseApp.UpdateFiltersRepresentation");
          
          var elHidWhere = document.getElementsByName("hidWhereClause")[0];
          var aryWhereClause = [];
          
          for(var anavar in this.IndexedFilterData){
              var aryFiltValues = this.IndexedFilterData[anavar].filterdialoglist;
              var elVarHandle = Dom.get(anavar);
              
              //Add or remove the CSS class denoting an active filter
              //Build the hidden Where clause
              if(!aryFiltValues || aryFiltValues.length == 0){
                  //This variable no longer has a filter.  Remove the class and delete the data from the index.
                  Dom.removeClass(elVarHandle, "filter-applied");
                  delete this.IndexedFilterData[anavar];
              } else {
                  //This variable has a filter.  Add the CSS class if it isn't there already.
                  Dom.addClass(elVarHandle, "filter-applied");
                  
                  //Tack on to the where clause.
                  var objSavedForm = this.IndexedFilterData[anavar];

                  Logger.log("Current indexed item:" + YAHOO.lang.JSON.stringify(objSavedForm), "debug", "BaseApp.UpdateFiltersRepresentation");
                  aryWhereClause.push(
                    "^" + [
                      anavar,
                      objSavedForm["In_or_Ex"],
                      objSavedForm["filterdialoglist"].join("~")
                    ].join("~")
                  );
                  Logger.log("Updated where clause:  " + YAHOO.lang.JSON.stringify(aryWhereClause), "debug", "BaseApp.UpdateFiltersRepresentation");
              }
          }
          
          elHidWhere.value = aryWhereClause.join("");
          Logger.log("Finished.", "trace", "BaseApp.UpdateFiltersRepresentation");
      },
      
      /**
        Meant to be an argument normalizer for ConfigureAndShowFilter.
        @param {Object} objArgs Argument object from the fired DropOnFilter event.
      */
      FilterUpdate: function(objArgs){
          this.ConfigureAndShowFilter(Dom.get(objArgs.strDDId));
      },
      
      /**
        Sends a request for formatted-values data via the DataSource object ydsFormattedValues.  The Success callback function checks the replyCode and replyText of the response envelope.
        @param {HTMLElment} elSource Meant to be a child element of an "active" varbin droptarget.
      */
      ConfigureAndShowFilter: function(elSource){
          Logger.log(elSource.id, "debug", "BaseApp.ConfigureAndShowFilter");
          var _xhrSuccess = function(oRequest, oResponse){
              var blnContinue = false;
              
              //Prepare an Object for error reporting.
              var oError = {
                "strRequest":oRequest,
                "oResponse":oResponse,
                "dTime":(new Date()),
                "msg":oResponse.meta.replyText
              };
              switch(oResponse.meta.replyCode){
                  case 300:
                      this.DataWarningEvent.fire(oError);
                      //Fallthrough
                  case 200:
                      blnContinue = true;
                  break;
                  case 500:
                      //Fallthrough
                  default:
                      this.DataErrorEvent.fire(oError);
                  break;
              }
              if(!blnContinue){
                  Logger.log("Metadata service return did not warrant continuation.", "error", "BaseApp.ConfigureAndShowFilter._xhrSuccess");
              } else {
                  //Logger.log("Formatted values to load for query " + oRequest + ":  " + oResponse.results.length, "debug", "BaseApp.ConfigureAndShowFilter._xhrSuccess");
                  if(!this.FilterDialog){
                      Logger.log("No FilterDialog available.", "debug", "BaseApp.ConfigureAndShowFilter._xhrSuccess");
                  } else {
                      var newid = this.MakeDDDOMVarID(oRequest.split("VarID=")[1].split("&")[0]); //Multiple parameters may be passed
                      
                      //Load stored filter, if any
                      var objPrevFilterInstance = this.IndexedFilterData[newid];
                      if(
                        objPrevFilterInstance && 
                        objPrevFilterInstance.filterdialoglist && 
                        objPrevFilterInstance.filterdialoglist.length > 0
                      ){
                          Logger.log("Loading previously-set filter data.", "trace", "BaseApp.ConfigureAndShowFilter._xhrSuccess");
                          this.FilterDialog.cfg.setProperty("preselected", objPrevFilterInstance.filterdialoglist);
                          this.FilterDialog.cfg.setProperty("inorex", objPrevFilterInstance.In_or_Ex);
                      } else {
                          Logger.log("Resetting filter dialog to empty preselection.", "trace", "BaseApp.ConfigureAndShowFilter._xhrSuccess");
                          this.FilterDialog.cfg.setProperty("preselected", []);
                          this.FilterDialog.cfg.setProperty("inorex", "EX");
                      }
                      
                      var strVarRequest = "";
                      for(var i=0, aryPs = oRequest.split("&"); i<aryPs.length; i++){
                          if(aryPs[i].split("VarID=").length > 1){
                              strVarRequest = aryPs[i];
                              break;
                          }
                      }
                      
                      if(strVarRequest == ""){
                          Logger.log("Could not retrieve requested VarID.", "error", "BaseApp.ConfigureAndShowFilter._xhrSuccess");
                      } else {
                          //Logger.log("oRe", "debug", "BaseApp.ConfigureAndShowFilter._xhrSuccess");
                          this.FilterDialog.setHeader(Analyzer.BaseApp.BreakLongTitle("Filter for " + this.IndexedMetadata["breakout_variables"][strVarRequest].VarLabel));
                          this.FilterDialog.cfg.setProperty("varid", newid);
                          this.FilterDialog.cfg.setProperty("pool", oResponse.results);
                          this.FilterDialog.show();
                      }
                  }
              }
          };
          var _xhrFailure = function(oRequest, oResponse){
              Logger.log("Called.", "trace", "BaseApp.ConfigureAndShowFilter._xhrFailure");
              this.HTTPErrorEvent.fire({
                "strRequest": oRequest,
                "oResponse": oResponse
              });
              Logger.log("Finished.", "trace", "BaseApp.ConfigureAndShowFilter._xhrFailure");
          };
          
          this.ydsFormattedValues.sendRequest(
            this.GenerateFormattedValuesRequest(elSource),
            {//Callback is an object instead of just a success function - failure needs to be handled, too.
              success: _xhrSuccess,
              failure: _xhrFailure,
              scope: this,  //Adjust execution scope
              argument: {asdf:"fdsa"}
            }  
          );
      },
      
      /**
        Fully populates the value of the hidden form field associated with the 'box' parameter.
      */
      RefreshHiddenDDInput: function(p_sType, p_aArgs){
          Logger.log("Called.", "trace", "BaseApp.RefreshHiddenDDInput");
          var elBox = Dom.get(p_aArgs[0]);
          //Logger.log(elBox, "debug", "BaseApp.RefreshHiddenDDInput");
          if(!elBox){
              Logger.log("elBox not found.", "error", "BaseApp.RefreshHiddenDDInput");
              //Logger.log(p_aArgs, "debug", "BaseApp.RefreshHiddenDDInput");
          } else {
              var elHid = Dom.get(DD_HIDFIELD_ASSOCS[elBox.id]);
              if(!elHid){
                  //Maybe not an error...inactive boxes don't have this input.
                  if(!Dom.hasClass(elBox, "active")){
                      Logger.log("No hidden input found for '" + elBox.id + "'; this is inactive, so there's no problem.", "debug", "BaseApp.RefreshHiddenDDInput");
                  } else {
                      Logger.log("Unable to find hidden input associated with '" + elBox.id + "'.", "error", "BaseApp.RefreshHiddenDDInput");
                  }
              } else {
                  var aryNewValue = [], strNewValue, strCatId;
                  
                  for(var i=0, aryBoxChildren = elBox.getElementsByTagName("li"); i<aryBoxChildren.length; i++){
                      //Fetch the variable's category, which is stored as a CSS class
                      for(var j=0, aryClasses = aryBoxChildren[i].className.split(" "); j<aryClasses.length; j++){
                          //Break the loop when strCatClass finally achieves a value (if "CatID:" isn't in the current CSS class, the value at [1] will be null, causing the assignment to evaluate to null)
                          if(strCatId = aryClasses[j].split("CatID:")[1]){
                              break;
                          }
                      }
                      
                      //Add to the variable list
                      aryNewValue.push("~" + strCatId + "~" + aryBoxChildren[i].id + "~" + this.GetFormatChoice(aryBoxChildren[i]) + "~" + aryBoxChildren[i].innerHTML);
                      
                  }
                  strNewValue = aryNewValue.join("");
                  elHid.value = strNewValue;
                  Logger.log("Successfully set value of " + elHid.id + " to:\n" + strNewValue, "trace", "BaseApp.RefreshHiddenDDInput");
              }
          }
      },
      
      onDataWarning: function(p_oArgs){
          Logger.log("Called.", "trace", "BaseApp.onDataWarning");
          this.ErrorPanel.setHeader("Application Warning");
          this.ErrorPanel.setBody(
            [
              "<p>There was an application warning while processing your request.  This shouldn't interfere with your working, but if it does, we would like to hear about this issue.</p>",
              (p_oArgs.msg ? "<p>The warning was:</p><blockquote>" + p_oArgs.msg + "</blockquote>" : ""),
              this.WriteReportErrorHTML(p_oArgs)
            ].join("\n")
          );
          this.ErrorPanel.show();
      },
      
      onDataError: function(p_oArgs){
          Logger.log("Called.", "trace", "BaseApp.onDataError");
          this.ErrorPanel.setHeader("Application Error");
          this.ErrorPanel.setBody(
            [
              "<p>There was an application error processing your request.</p>",
              (p_oArgs.msg ? "<p>The error was:</p><blockquote>" + p_oArgs.msg + "</blockquote>" : ""),
              this.WriteReportErrorHTML(p_oArgs)
            ].join("\n")
          );
          this.ErrorPanel.show();
      },
      
      /**
        Destructively updates the category dropdown with categories appropriate to the current analysis topic.
      */
      RefreshCategoryDropdown: function(p_sType, p_aArgs){
          Logger.log("Called.", "trace", "BaseApp.RefreshCategoryDropdown");
          var elTarget = Dom.get(this.cfg.getProperty("elCategoryInput"));
          if(!elTarget){
              Logger.log("Unable to find configuration property 'elCategoryInput'.  No update done.", "error", "BaseApp.RefreshCategoryDropdown");
              Logger.log(elTarget, "debug", "BaseApp.RefreshCategoryDropdown");
          } else {
              var elTopic = Dom.get(this.cfg.getProperty("elTopicInput"));
              
              //In some systems, like HYS, the Topic is a hidden form input.  So do searches for the element's value, instead of something like childNodes or options (if checking to see if the topic has been set once already).
              //Logger.log(elTopic.nodeName, "debug", "BaseApp.RefreshCategoryDropdown");
              
              if(!elTopic){
                  Logger.log("Unable to find configuration property 'elTopicInput'.  No update done.", "error", "BaseApp.RefreshCategoryDropdown");
              } else {
                  if(!elTopic.value){
                      //Can't use elTopic in this call of RefreshCategoryDropdown.  Is it because there aren't any children of the select element, if it's a select?  Is it a hidden input, value script-set?
                      Logger.log("No topic defined.  No update done.", "debug", "BaseApp.RefreshCategoryDropdown");
                      //Logger.log(elTopic, "debug", "BaseApp.RefreshCategoryDropdown");
                  } else {
                      var curTopicName = elTopic.value;
                      var curTopicMD = this.IndexedMetadata["analtopic"][ "TopicName=" + curTopicName ];
                      if(!curTopicMD){
                          Logger.log("Unable to look up metadata for topic aned '" + curTopicName + "'.", "error", "BaseApp.GenerateCategoryTree");
                      } else {
                          var curTopicID = curTopicMD.TopicID;
                          //Collect list of category IDs appropriate to the current topic
                          var aryUseCats = [];
                          for(var i=0, aryCats = this.MetadataTableStore["ds=analcat"].responseJSON.data; i<aryCats.length; i++){
                              //Logger.log("Checking " + curTopic + " vs. " + aryCats[i].TopicName, "debug", "BaseApp.GenerateCategoryTree");
                              if(aryCats[i].TopicID == curTopicID){
                                  aryUseCats.push(aryCats[i]);
                              }
                          }
                          
                          //Get category metadata
                          var objLookupTitles = this.IndexedMetadata["variablecat"];
                          var aryNewOptData = [];
                          var blnIndent;
                          for(var row, j=0; j<aryUseCats.length; j++){
                              row = objLookupTitles[ "CatID="+aryUseCats[j].CatID ]
                              blnIndent = (row.Note == "[indent]");
                              aryNewOptData.push({
                                value: row.CatID,
                                label: row.CatName,
                                indent: blnIndent
                              });
                          }
                          
                          elTarget.options.length = 0;
                          for(var k=0; k<aryNewOptData.length; k++){
                              elTarget.options[k] = new Option(
                                (aryNewOptData[k].indent ? '          ' : '') + aryNewOptData[k].label,
                                aryNewOptData[k].value,
                                k==0,
                                k==0
                              );
                              if(aryNewOptData[k].indent){
                                  Dom.setStyle(elTarget.options[k], "padding-left", "2.5em");
                              }
                          }
                          
                          this.CategoryChangedEvent.fire({
                            CatID: elTarget.options[elTarget.selectedIndex].value,
                            type: EVENT_TYPES.CATEGORY_CHANGED
                          });
                      }
                  }
              }
          }
          Logger.log("Finished.", "trace", "BaseApp.RefreshCategoryDropdown");
      },
      
      RefreshListDialogLongness: function(p_sType, p_aArgs){
          Logger.log("Called.", "trace", "BaseApp.RefreshListDialogLongness");
          //Logger.log(YAHOO.lang.dump([p1, p2]), "debug", "BaseApp.RefreshListDialogLongness");  //Argument 3 is the scope object.
          this.FilterDialog.cfg.setProperty("intLongListLength", p_aArgs[0]);
          Logger.log("Finished.", "trace", "BaseApp.RefreshListDialogLongness");
      },
      
      /**
        Assign DOM and Custom Event handlers and triggers.  This is the component interaction script.
        NOTE:  click handlers for checkboxes in the drag drop area should be written to not rely absolutely on the click event.  ClearQueryBuilder calls these handlers without supplying a click target.
      */
      initInteractions: function(){
          Logger.log("Called.", "trace", "BaseApp.initInteractions");
          
          this.cfg.subscribeToConfigEvent(DEFAULT_CONFIG.LONG_LIST_LENGTH.key, this.RefreshListDialogLongness, this, true);
          
          this.DataWarningEvent.subscribe(this.onDataWarning, this, true);
          
          this.DataErrorEvent.subscribe(this.onDataError, this, true);
          //this.ydsFormattedValues.subscribe("dataErrorEvent", this.onHTTPError, this, true); //This can also be for data warnings
          this.HTTPErrorEvent.subscribe(this.onHTTPError, this, true);
          
          this.TopicChangedEvent.subscribe(this.ClearQueryBuilder, this, true);
          this.TopicChangedEvent.subscribe(this.ReflectTopic, this, true);
          this.TopicChangedEvent.subscribe(this.UpdateStatOptionStates, this, true);
          
          //Topic interactions with dropdown/single-source box
          YAHOO.util.Event.addListener(this.cfg.getProperty("elCategoryInput"), "change", function(p_e){
              this.CategoryChangedEvent.fire({
                CatID: Event.getTarget(p_e).value,
                type:  EVENT_TYPES.CATEGORY_CHANGED
              });
          }, this, true);
          this.CategoryChangedEvent.subscribe(this.UpdateBreakoutVariablesList, this, true);
          this.TopicChangedEvent.subscribe(this.RefreshCategoryDropdown, this, true);
          
          //Topic interactions with tree
          this.TopicChangedEvent.subscribe(this.DestroyCategoryTree, this, true);
          this.TopicChangedEvent.subscribe(this.GenerateCategoryTree, this, true);
          this.BVListCreatedEvent.subscribe(this.UpdateBreakoutVariablesList, this, true);
          
          //Preserve order of variables in drag-drop list, even when removing a variable from analysis
          Analyzer.DDProxy.VariableMoved.subscribe(this.MaybeRefreshBreakoutVariablesList, this, true);
          
          
          this.DateChangedEvent.subscribe(this.ReflectDate, this, true);
          
          
          //Add listeners to the DOM events
          YAHOO.util.Event.addListener(Dom.get(this.cfg.getProperty("elTopicInput")), "change", function(){
              this.TopicChangedEvent.fire();  //Note that 'this' here refers to the contextual object, set as the next argument of addListener
          }, this, true);
          YAHOO.util.Event.addListener("btnResetTab2", "click", function(p_e){
              Event.preventDefault(p_e);
              this.TopicChangedEvent.fire();
              //this.ClearQueryBuilder();
          }, this, true);
          
          //Tie date custom events to DOM events
          for(var elInput, i=0, aryDateParams = ["FM","FY","TM","TY"]; i<aryDateParams.length; i++){
              var cfgPropName = "elTimeInput_" + aryDateParams[i];
              cfgInput = this.cfg.getProperty(cfgPropName);
              if(!cfgInput){
                  Logger.log("Could not get configuration property '" + cfgPropName + "'.", "warning", "BaseApp.initInteractions");
              } else {
                  YAHOO.util.Event.addListener(
                    cfgInput,
                    "change",
                    function(){this.DateChangedEvent.fire();},
                    this,
                    true
                  );
              }
          }
          
          Analyzer.DDProxy.VariableMoved.subscribe(this.VariableMotionHandler, this, true);
          
          //Turn on/off mask when drags begin and end involving the "Active variable" area.
          Analyzer.DDProxy.ActiveVarDragStart.subscribe(this.EnableDDMask, this, true);
          Analyzer.DDProxy.ActiveVarDragEnd.subscribe(this.DisableDDMask, this, true);
          
          //Give actions to do when all metadata is loaded
          this.AllMetadataLoadedEvent.subscribe(this.onAllMetadataLoaded, this, true);
          
          //Every time metadata loads, it might be the last needed.  Check.
          this.MetadataLoadedEvent.subscribe(this.MaybeFireAllMetadataLoaded, this, true);
          
          //Tie the filter-drops to calling a new panel after inserting a new child
          Analyzer.DDProxy.DropOnFilter.subscribe(this.FilterUpdate, this, true);
          
          this.FilterDataUpdateEvent.subscribe(this.UpdateFiltersRepresentation, this, true);
          
          //Context menu interactions:  Highlight the context menu variable on context-click, dim when the menu closes.
          this.ActiveVarContextMenu.triggerContextMenuEvent.subscribe(this.HighlightContextTarget);
          this.ActiveVarContextMenu.hideEvent.subscribe(this.DimContextTarget);
          
          //When a drag-drop list is updated, the hidden input needs to be reset, and any multi-var options need to be toggled.
          this.DDListChangedEvent.subscribe(this.RefreshHiddenDDInput, this, true);
          this.DDListChangedEvent.subscribe(this.RefreshMultiVarOptionsVisiblity, this, true);
          this.DDListChangedEvent.subscribe(this.RefreshTargetListMetadata, this, true);
          this.DDListChangedEvent.subscribe(function(p_sType, p_aArgs){
              Logger.log("Called.", "trace", "BaseApp.__BRFSS");
              var objTablePreviews = {
                "ulDDColumns":"TablePreview_ColVar",
                "ulDDRows":"TablePreview_RowVar"
              };
              
              var elUpdatedList = Dom.get(p_aArgs[0]);
              if(!elUpdatedList){
                  Logger.log("Updated element not found.", "debug", "BaseApp.__BRFSS");
              } else {
                  Logger.log("Updated element found.", "trace", "BaseApp.__BRFSS");
                  var elPreview = Dom.get(objTablePreviews[ elUpdatedList.id ]);
                  if(!elPreview){
                      Logger.log("No preview element found.", "debug", "BaseApp.__BRFSS");
                      Logger.log(elUpdatedList, "debug", "BaseApp.__BRFSS");
                  } else {
                      Logger.log("Preview element found.", "trace", "BaseApp.__BRFSS");
                      if(elUpdatedList.childNodes.length == 0){
                          Logger.log("Children not present.", "trace", "BaseApp.__BRFSS");
                          elPreview.innerHTML = "";
                      } else {
                          Logger.log("Children present.", "trace", "BaseApp.__BRFSS");
                          var elid = elUpdatedList.childNodes[0].id;
                          var key = "VarID=" + this.ExtractDDDOMVarID(elid);
                          var mdrow = this.IndexedMetadata["breakout_variables"][ key ];
                          if(!mdrow){
                              Logger.log("Unable to find metadata row keyed ''.", "error", "BaseApp.__BRFSS");
                          } else {
                              elPreview.innerHTML = mdrow.VarName;
                          }
                      }
                  }
              }
              Logger.log("Finished.", "trace", "BaseApp.__BRFSS");
          }, this, true);
          
          
          
          //Refresh visual alternations for drag'n'drop elements
          this.BVsUpdatedEvent.subscribe(this.RefreshAlternations, this, true);
          this.DDListChangedEvent.subscribe(this.RefreshAlternations, this, true);
          
          //Navigation niceties
          var aryAltNavs = Dom.getElementsByClassName("tabview-alt-nav", "a", "AnalyzerInterfaceContainer");
          Logger.log("Found " + aryAltNavs.length + " elements with the class 'tabview-alt-nav'.", "debug", "BaseApp.initInteractions");
          for(var i=0; i<aryAltNavs.length; i++){
              Event.addListener(aryAltNavs[i], "click", this.NavigateTabView(aryAltNavs[i]), this, true);
          }

          this.InitEvent.subscribe(function(p_sType, p_fConstructor){
              if(p_fConstructor == Analyzer.BaseApp){
                  //This class, the abstract superclass, has finished initializing.
              } else {
                  //A subclass has finished initializing.
                  this.InitSubclassEvent.fire();
              }
          }, this, true);
          
          //Toggles - uses scope adjuster
          this.DDTargetEnabledStateUpdateEvent.subscribe(this.UpdateDDTargetVisibility, this, true);
          function _GenerateToggle(p_elToToggle){
              //Note that this function won't always have a supplied event argument (like when the form is resetting)
              return function(p_e){
                  var elToggled = Event.getTarget(p_e) || this.cfg.getProperty("DragDrop_Toggle_Associations")[Dom.get(p_elToToggle).id];
                  this.UpdateDDTargetEnabledStatus(p_elToToggle, elToggled.checked);
              };
          }
          function _GenerateToggleInit(p_elToToggle, p_elToggleHandle){
              return function(){
                  this.UpdateDDTargetEnabledStatus(p_elToToggle, Dom.get(p_elToggleHandle).checked);
              };
          };
          for(var k in this.cfg.getProperty("DragDrop_Toggle_Associations")) if (YAHOO.lang.hasOwnProperty(this.cfg.getProperty("DragDrop_Toggle_Associations"), k)){
              Event.addListener(this.cfg.getProperty("DragDrop_Toggle_Associations")[k], "click", _GenerateToggle(k), this, true);
              //Initial state - set when the user configuration is supplied
              this.ConfigAppliedEvent.subscribe(_GenerateToggleInit(k, this.cfg.getProperty("DragDrop_Toggle_Associations")[k]), this, true);
          }
          
          this.ConfigAppliedEvent.subscribe(this.MaybeHideSubsetting, this, true);
          Logger.log("TRACE");
          //Set up Filter Dialog once the configuration is applied, because long lists may be defined by the user.
          this.ConfigAppliedEvent.subscribe(function(){
              this.initFilterDialog();
              //Capture the dialog's form data before the submit event tries to fire
              this.FilterDialog.manualSubmitEvent.subscribe(this.onFilterManualSubmit, this, true);
              
              //this.FilterDialog.cfg.configChangedEvent.subscribe(function(){
              //    Logger.log(YAHOO.lang.JSON.stringify(arguments), "debug", "BaseApp.init");
              //});
          }, this, true);
          Logger.log("TRACE");
          
          //applyConfig queues all the setting handlers, but does NOT fire them (at least in YUI 2.5.1).  That has to be done with fireQueue; so, fire once the config is applied.
          //Don't override the scope here.
          //this.ConfigAppliedEvent.subscribe(function(){alert("ConfigAppliedEvent fired.")});
          //this.ConfigAppliedEvent.subscribe(this.cfg.fireQueue);
          
          Event.addListener(document, "click", this.ActiveVarContextMenu.hide, this.ActiveVarContextMenu, true);
          
          //Variable focus and preview interactions
          Event.addListener(document, "click", this.FocusOnVariable, this, true);
          Analyzer.DDProxy.VarDragStart.subscribe(this.FocusOnVariable, this, true);
          Analyzer.DDProxy.VarDragStart.subscribe(this.SelectivelyRefreshDDCache, this, true);
          
          Analyzer.DDProxy.VarDragStart.subscribe(this.initDDGroups, this, true);
          Analyzer.DDProxy.VarMotionComplete.subscribe(this.idleDDGroups, this, true);
          
          this.BeforeVariableBlurEvent.subscribe(this.MaybeDeviewInactiveVariable, this, true);
          this.BeforeVariableBlurEvent.subscribe(this.DimPreview, this, true);
          //this.VariableBlurredEvent.subscribe();
          this.VariableFocusedEvent.subscribe(this.VariableFocusedHandler, this, true);
          Analyzer.DDProxy.VariableMoved.subscribe(function(p_oArgs){
              //Logger.log(YAHOO.lang.dump(p_oArgs), "debug", "SOMEWHERE");
              
              //Disable previews for the moved element.
              this.DeviewVariable(p_oArgs.strDDId);
              
              //Enable preview, which may or may not work, but at least won't raise an error.
              //Don't enable the preview if moving the variable to the source box, unless it's focused.  It makes sense if you're not programming the darn thing.
              if(Dom.hasClass(p_oArgs.strDropTargetId, "active")){
                  this.PreviewVariable(p_oArgs.strDDId);
              } else {
                  if(this.focusvariable && (this.focusvariable.id == strDDId)){
                      this.PreviewVariable(p_oArgs.strDDId);
                  } else {
                      //Disabling variable.  Nop.
                  }
              }
              
              //Reorder - called here because of execution order (VariableMoved fires after DDListChangedEvent)
              this.ReorderPreview(Dom.get(p_oArgs.strDDId).parentNode);
              
              //Scroll to new position, since it may have just been changed.
              this.ScrollPreview(p_oArgs.strDDId);
          }, this, true);
          this.PreviewCreatedEvent.subscribe(this.MaybeHighlightPreviewOnload, this, true);
          this.PreviewRenderedEvent.subscribe(this.ScrollPreview, this, true);
          
          //Resetting the drag-drop area
          this.BeforeDestroyingDDHandleEvent.subscribe(this.DeviewVariable, this, true);
          this.BeforeDestroyingDDHandleEvent.subscribe(this.DeregisterDDObj, this, true);
          
          //Preview events//
          //Add deferred handlers for radio buttons in response previews
          this.ResponseListChosenEvent.subscribe(this.UpdateResponseListChoice, this, true);
          Event.addListener(Dom.getElementsByClassName("var-preview", "ol", "dragdroparea"), "click", function(p_e){
              this.ResponseListChosenEvent.fire(p_e);
          }, this, true);
          
          //Three events that call for updating the response list preview:
          // * The list is created
          // * The toggle between "Survey" and "Collapsed" is switched.
          // * A new filter set is chosen
          this.FormatChoiceDataUpdateEvent.subscribe(this.UpdateResponseListPreview, this, true);
          this.ResponseListPreviewCreatedEvent.subscribe(this.UpdateResponseListPreview, this, true);
          this.FilterDataUpdateEvent.subscribe(this.UpdateResponseListPreview, this, true);
          
          //Side-effects of choosing a new response list:
          // * Previous filters are invalidated.
          // * drag-drop hidden input needs to be updated
          this.FormatChoiceDataUpdateEvent.subscribe(this.RemoveFilter, this, true);
          this.FormatChoiceDataUpdateEvent.subscribe(this.RefreshHiddenDDInput_FromPreview, this, true);
          
          
          Event.addListener("elMultipleDataSourceRevealer", "click", this.RevealDataSources, this, true);
          
          //Change from tabular to chart construction on query builder or vice-versa
          Event.addListener(document.getElementsByName("optOutputType"), "click", this.SwapChartTableAnalysisForms, this, true);
          
          //Charting groups have an extra dropdown
          this.DDListChangedEvent.subscribe(this.MaybeToggleChartPercDef, this, true);
          
          //Sentinel
          Event.addListener(Dom.getElementsByClassName("Analyzer", "form", document.body), "submit", this.EnforceSubmissionRules, this, true);
          
          Logger.log("Finished.", "trace", "BaseApp.initInteractions");
      },
      
      /**
        Hides the filtering box if the configuration denotes that we won't use it.
      */
      MaybeHideSubsetting: function(){
          if(this.cfg.getProperty("AllowSubsetting") === false){
              Dom.setStyle("divContainer_DDFilters", "display", "none");
          }
      },
      
      /**
        Prevents form submission if one of the rules in <code>this.SubmissionRules</code> is not met.
      */
      EnforceSubmissionRules: function(p_e){
          var aryBadResults = [];
          
          var tmpRuleResult;
          for(var rule in this.SubmissionRules) if (YAHOO.lang.hasOwnProperty(this.SubmissionRules, rule)){
              tmpRuleResult = this.SubmissionRules[ rule ].call(this, false);
              if(tmpRuleResult == "Ok"){
                  YAHOO.log("Passed rule '" + rule + "'.", "debug", "BaseApp.EnforceSubmissionRules");
              } else {
                  aryBadResults.push(tmpRuleResult);
              }
          }
          
          var htmlBadNews;
          if(aryBadResults.length == 0){
              //Form submits
              
              //No bad news
              htmlBadNews = "";
          } else {
              //Prevent submission
              Event.preventDefault(p_e);
              
              //List the bad news
              var aryBadNewsHTML = [];
              aryBadNewsHTML.push("<p>Did not pass all form conditions:</p>");
              aryBadNewsHTML.push("<ul>");
              for(var i=0; i<aryBadResults.length; i++){
                  aryBadNewsHTML.push("  <li>" + aryBadResults[i] + "</li>");
              }
              aryBadNewsHTML.push("</ul>");
              
              htmlBadNews = aryBadNewsHTML.join("\n");
          }
          
          var aryReporterEls = Dom.getElementsByClassName("error-reporter", "div", Dom.getElementsByClassName("Analyzer", "form", document.body)[0]);
          for(var i=0; i<aryReporterEls.length; i++){
              aryReporterEls[i].innerHTML = htmlBadNews;
          }
      },
      
      /**
        Index of functions, type Boolean -> String; they return either "Ok" or an error message.  The parameter is whether any inline notifications written in the function occur.
        @property SubmissionRules
        @type Object
      */
      SubmissionRules:{
          //At least one statistic should be requested
          "At least one statistic requested": function(p_bLoud){
              var retval = "At least one statistic must be requested (count, row percentage, et cetera).";
              
              for(var i=0, aryChecks = Dom.getElementsByClassName("one-required", "input", "fsOptions"); i<aryChecks.length; i++){
                  if(aryChecks[i].checked){
                      retval = "Ok";
                      break;
                  }
              }
              
              return retval;
          },
          
          //Checks whether the dates fall in metadata bounds and the beginning comes before the end.
          "Dates make sense": function(p_bLoud){
              var retval = "Date verification check did not finish.";
              
              var objEnvelope = this.MetadataTableStore["ds=datasource"].responseJSON;
              if(!objEnvelope){
                  Logger.log("DataSources metadata not found; unable to verify dates.");
              } else {
                  //Take advantage of pre-computed Date objects for determining maximum boundaries
                  var dateMin = null, dateMax = null, curMinDate, curMaxDate;
                  for(var i=0; i<objEnvelope.data.length; i++){
                      curMinDate = objEnvelope.data[i]["MinDate"];
                      curMaxDate = objEnvelope.data[i]["MaxDate"];
                      
                      //Find year range
                      if(dateMin === null) dateMin = curMinDate;
                      if(dateMax === null) dateMax = curMaxDate;
                      
                      dateMin = new Date(Math.min(dateMin.valueOf(), curMinDate.valueOf()));
                      dateMax = new Date(Math.max(dateMax.valueOf(), curMaxDate.valueOf()));
                  }
                  
                  Logger.log([dateMin, dateMax], "debug", "BaseApp.SubmissionRule");
                  
                  if(dateMin === null || dateMax === null){
                      Logger.log("The date boundaries are NULL due to lack of data.", "error", "BaseApp.SubmissionRule");
                  } else {
                      //Define interface date variables
                      var
                        elSelFM = Dom.get(this.cfg.getProperty("elTimeInput_FM")),
                        elSelFY = Dom.get(this.cfg.getProperty("elTimeInput_FY")),
                        elSelTM = Dom.get(this.cfg.getProperty("elTimeInput_TM")),
                        elSelTY = Dom.get(this.cfg.getProperty("elTimeInput_TY"))
                      ;
                      
                      //Check that dates fall within metadata boundaries and don't overlap one another
                      //Here, the logic branches (not just if-else asserting anymore).
                      if(!(
                        elSelFM && !elSelFM.disabled
                        && elSelFY && !elSelFY.disabled
                        && elSelTM && !elSelTM.disabled
                        && elSelTY && !elSelTY.disabled
                      )){
                          Logger.log("Not all date inputs found (or found active) for two-month, two-year bounding.  Checking for single-day inputs.", "trace", "BaseApp.SubmissionRule");
                          
                          //Possibly reset maximum date - e.g. in Snohomish, the last available day is actually yesterday.
                          var cfgLastDay = this.cfg.getProperty("LastDataAvailableDay");
                          var dateNow = new Date();
                          var dateToday = new Date(dateNow.getFullYear(), dateNow.getMonth(), dateNow.getDate());
                          switch(cfgLastDay){
                              case "today":
                                  dateMax = dateToday;
                              break;
                              case "yesterday":
                                  dateMax = new Date(dateToday.valueOf());
                                  dateMax.setDate(dateMax.getDate() - 1);
                              break;
                              default:
                                  //Nop - stick with end of last month.
                              break;
                          }
                          Logger.log(dateMax, "debug", "BaseApp.SubmissionRule");
                          
                          //These should be set later as Configuration parameters instead of hard-coded ID's.
                          var
                            elSelSDM = Dom.get("cboSingleDate_Month"),
                            elSelSDD = Dom.get("cboSingleDate_Day"),
                            elSelSDY = Dom.get("cboSingleDate_Year")
                          ;
                          if(!(elSelSDM && elSelSDD && elSelSDY)){
                              Logger.log("Unsure what kind of date inputs there are.", "error", "BaseApp.SubmissinRule");
                          } else {
                              var dateDay = new Date(parseInt(elSelSDY.value, 10), parseInt(elSelSDM.value, 10)-1, parseInt(elSelSDD.value, 10));
                              Logger.log(dateDay, "debug", "BaseApp.SubmissionRule");
                              if(dateMin.valueOf() <= dateDay.valueOf() && dateDay.valueOf() <= dateMax.valueOf()){
                                  retval = "Ok";
                              } else {
                                  retval = "The requested day falls outside the data availability.";
                              }
                          }
                      } else {
                          Logger.log("Found all date inputs active for two-month, two-year bounding.", "trace", "BaseApp.SubmissionRule");
                          var dateBeg = new Date(parseInt(elSelFY.value, 10), parseInt(elSelFM.value, 10)-1, 1);
                          var dateEnd = new Date(parseInt(elSelTY.value, 10), parseInt(elSelTM.value, 10), 0); //The day before the first day of the next month; going THROUGH end month.
                          
                          Logger.log([dateBeg, dateEnd], "debug", "BaseApp.SubmissionRule");
                          
                          if(dateEnd.valueOf() - dateBeg.valueOf() < 0){
                              retval = "Beginning month should come before ending month.";
                          } else {
                              var blnInBounds = true;
                              for(var i=0, aryDates = [dateBeg, dateEnd], iDateVal; i<aryDates.length; i++){
                                  blnInBounds = blnInBounds && dateMin.valueOf() <= aryDates[i].valueOf() && aryDates[i].valueOf() <= dateMax.valueOf();
                              }
                              if(!blnInBounds){
                                  retval = "The requested date range falls outside the data availability.";
                              } else {
                                  retval = "Ok";
                              }
                          }
                      }
                      
                  }
              }
              
              return retval;
          },
          
          "Analysis variable defined": function(p_bLoud){
              var retval = "Analysis verification check did not finish.";
              
              var elColumns = Dom.get("ulDDColumns"), elRows = Dom.get("ulDDRows"), elAxis = Dom.get("ulDDAxis");
              if(!(elColumns && elRows && elAxis)){
                  Logger.log("Unable to find all base-analysis elements.", "error", "BaseApp.SubmissionRule");
                  Logger.log([elColumns, elRows, elAxis], "debug", "BaseApp.SubmissionRule");
              } else {
                  if(elColumns.childNodes.length + elRows.childNodes.length + elAxis.childNodes.length == 0){
                      var outtype = GetRadioVal("optOutputType");
                      var strInsert;
                      switch(outtype){
                          case "datatable":
                              strInsert = "column or row";
                          break;
                          case "datachart":
                              strInsert = "bar";
                          break;
                          default:
                              strInsert = "analysis";
                          break;
                      }
                      retval = "At least one " + strInsert + " variable must be requested.";
                  } else {
                      retval = "Ok";
                  }
              }
              
              return retval;
          }
      },
      
      /**
        @return String Returns the AnalysisType from the relevent AnalTopic or SubTopic row.
      */
      GetCurrentAnalysisType: function(){
          var retval = null;
          var elTopic = Dom.get(this.cfg.getProperty("elTopicInput"));
          if(!elTopic){
              Logger.log("Couldn't find topic box.", "error", "BaseApp.GetCurrentAnalysisType");
          } else {
              var metaTopic = this.IndexedMetadata[ "analtopic" ][ "TopicName=" + elTopic.value ];
              if(!metaTopic){
                  //Well, the function's a wash at this point.  Was it in error?  It's an error if the page isn't initialized.
                  if(this._initialized){
                      Logger.log("Couldn't find topic metadata from input's value ('" + elTopic.value + "').", "error", "BaseApp.GetCurrentAnalysisType");
                      Logger.log(elTopic, "debug", "BaseApp.GetCurrentAnalysisType");
                  } else {
                      Logger.log("Couldn't find topic metadata from input's value; no Options available yet.", "debug", "BaseApp.GetCurrentAnalysisType");
                  }
              } else {
                  retval = metaTopic.AnalysisType;
                  
                  //It could be that the AnalysisType is defined in the SubTopic table.
                  if(!retval){
                      var metaSubTopic = this.MetadataTableStore[ "ds=subtopic" ];
                      for(var i=0, arySTs = metaSubTopic.responseJSON.data; i<arySTs.length; i++){
                          if(arySTs[i].TopicName == elTopic.value){
                              retval = arySTs[i].AnalysisType;
                              break;
                          }
                      }
                  }
              }
          }
          return retval;
      },
      
      /**
        Maybe enable and reveal (or the opposite) the percentage definition select box.
      */
      MaybeToggleChartPercDef: function(p_sType, p_aArgs){
          Logger.log("Called.", "trace", "BaseApp.MaybeToggleChartPercDef");
          var elChangedList = Dom.get(p_aArgs[0]);
          if(elChangedList && elChangedList.id == "ulDDGroups"){
              var elSel = Dom.get("cboPctType");
              if(!elSel){
                  Logger.log("Unable to find select element.", "error", "BaseApp.MaybeToggleChartPercDef");
              } else {
                  //First test:  Any group children?
                  var blnEnable = elChangedList.childNodes.length > 0;
                  if(!blnEnable){
                      Logger.log("Child count doesn't warrant displaying the chart percentage type.  Skipping other tests.", "debug", "BaseApp.MaybeToggleChartPercDef");
                  } else {
                      //This box shouldn't display at all when the topic's analysis type doesn't warrant a percentage formatting.
                      var blnGoodAnalysisType = true;
                      
                      var strAType = this.GetCurrentAnalysisType();
                      Logger.log("strAType:  " + strAType, "debug", "BaseApp.MaybeToggleChartPercDef");
                      
                      switch(strAType){
                          case "mean":
                              blnGoodAnalysisType = false;
                          break;
                          default:
                          break;
                      }
                      
                      blnEnable = blnEnable && blnGoodAnalysisType;
                      
                      if(!blnEnable) Logger.log("Topic doesn't warrant displaying the chart percentage type.", "debug", "BaseApp.MaybeToggleChartPercDef");
                  }
                  
                  Dom.setStyle(elSel, "visibility", blnEnable ? "visible" : "hidden");
                  elSel.disabled = !blnEnable;
              }
          }
          Logger.log("Finished.", "trace", "BaseApp.MaybeToggleChartPercDef");
      },
      
      SwapChartTableAnalysisForms: function(p_e){
          var outtype = GetRadioVal("optOutputType");
          
          //Reset query.
          this.TopicChangedEvent.fire();
          
          this.initDDTargetMeta();
          
          var aryChartEls = ["dlContainer_DDAxis","dlContainer_DDGroups","ChartOptions","ChartAddlOutputOptions"];
          var aryTableEls = ["dlContainer_DDColumns","dlContainer_DDRows","TableOptions","TableAddlOutputOptions"];
          
          switch(outtype){
              case "datatable":
                  //Hide all charting components.
                  Dom.setStyle(aryChartEls, "display", "none");
                  
                  //Reveal all tabular components.
                  Dom.setStyle(aryTableEls, "display", "block");
                  
                  //Swap label on shared components.
                  for(var i=0, arySpans = Dom.getElementsByClassName("table-chart-swap", "span", "dragdroparea"); i<arySpans.length; i++){
                      arySpans[i].innerHTML = "table";
                  }
              break;
              case "datachart":
                  //Hide all tabular components.
                  Dom.setStyle(aryTableEls, "display", "none");
                  
                  //Reveal all charting components.
                  Dom.setStyle(aryChartEls, "display", "block");
                  
                  //Swap label on shared components.
                  for(var i=0, arySpans = Dom.getElementsByClassName("table-chart-swap", "span", "dragdroparea"); i<arySpans.length; i++){
                      arySpans[i].innerHTML = "chart";
                  }
              break;
              default:
                  Logger.log("Unknown value for 'optOutputType':  '" + outtype + "'.", "error", "BaseApp.SwapChartTableAnalysisForms");
              break;
          }
      },
      
      /**
        Operates on the assumption that the parent node of an input element is a label element.
      */
      UpdateStatOptionStates: function(){
          var strAType = this.GetCurrentAnalysisType();
          
          try{
              
          } catch(e){
              Logger.log(e.message, "error", "BaseApp.UpdateStatOptionStates");
          }
          
          var
            elchkColumnPcts = Dom.get("chkColumnPcts"),
            elchkRowPcts = Dom.get("chkRowPcts"),
            elchkTablePcts = Dom.get("chkTablePcts"),
            elchkTableCIs = Dom.get("chkTableCIs"),
            eloptChrtVals_NPct = Dom.get("optChrtVals_NPct"),
            eloptChrtVals_NVal = Dom.get("optChrtVals_NVal")
          ;
          
          if(!(elchkColumnPcts && elchkRowPcts && elchkTablePcts && eloptChrtVals_NPct && eloptChrtVals_NVal)){
              Logger.log("Unable to get all element references.", "error", "BaseApp.UpdateStatOptionStates");
              Logger.log([elchkColumnPcts, elchkRowPcts, elchkTablePcts, eloptChrtVals_NPct, eloptChrtVals_NVal], "debug", "BaseApp.UpdateStatOptionStates");
          } else {
              //Set the chart percentages
              switch(strAType){
                  case "count":  //Fallthrough
                  case "sum":
                      //By default, Percent is checked when there's a choice.
                      eloptChrtVals_NPct.disabled = false;
                      eloptChrtVals_NPct.checked = true;
                  break;
                  default:
                      //Percent doesn't make sense for others
                      eloptChrtVals_NPct.disabled = true;
                      eloptChrtVals_NVal.checked = true;
                  break;
              }
              
              //Set the table percentages - since these are checkboxes, we can be a little more mechanically simple (for iteration, anyway) with them.
              for(var i=0, bDisable = (strAType=="mean"), aryEls = [elchkColumnPcts, elchkRowPcts, elchkTablePcts, elchkTableCIs]; i<aryEls.length; i++){
                  if(aryEls[i]){
                      aryEls[i].disabled = bDisable;
                      if(aryEls[i].parentNode.nodeName.toLowerCase() != "label"){
                          Logger.log("Assumption violated:  Parent is not a label element.", "error", "BaseApp.UpdateStatOptionStates");
                          Logger.log([aryEls[i], aryEls[i].parentNode], "debug", "BaseApp.UpdateStatOptionStates");
                      } else {
                          Dom.setStyle(aryEls[i].parentNode, "color", bDisable ? "gray" : "");
                      }
                  }
              }
          }
      },
      
      /**
        @param {HTMLElement} p_elContext The drag-drop element this list of options is being generated for.
        @return Array
      */
      GetValidEnabledDropTargets: function(p_elContext){
          //Find all valid drop targets
          var aryValids = [];
          
          var blnAllowFilter = false;
          var elContext = Dom.get(p_elContext);
          if(!elContext){
              Logger.log("Unable to find context element from argument.", "error", "BaseApp.GetValidEnabledDropTargets");
              Logger.log(p_elContext, "debug", "BaseApp.GetValidEnabledDropTargets");
          } else {
              var metaBV = this.IndexedMetadata[ "breakout_variables" ][ "VarID=" + this.ExtractDDDOMVarID(elContext.id) ];
              if(!metaBV){
                  Logger.log("Unable to find indexed breakout variable metadata.", "error", "BaseApp.GetValidEnabledDropTargets");
                  Logger.log(p_elContext, "debug", "BaseApp.GetValidEnabledDropTargets");
                  Logger.log(elContext, "debug", "BaseApp.GetValidEnabledDropTargets");
              } else {
                  Logger.log(metaBV, "debug", "BaseApp.GetValidEnabledDropTargets");
                  blnAllowFilter = !metaBV.DateVariable;
              }
          }
          
          var tmpMetaRow;
          for (var k in this.DDTargetMeta) if (YAHOO.lang.hasOwnProperty(this.DDTargetMeta, k)) {
              tmpMetaRow = this.DDTargetMeta[k];
              //Logger.log(tmpMetaRow, "debug", "BaseApp.GetValidEnabledDropTargets");
              if (tmpMetaRow.enabled || tmpMetaRow.forcible) {
                  if (YAHOO.lang.isValue(tmpMetaRow.childcap) && tmpMetaRow.children >= tmpMetaRow.childcap) {
                      Logger.log("Missed cap mark.", "debug", "BaseApp.GetValidEnabledDropTargets");
                  } else {
                      if(!tmpMetaRow.active){
                          //Skip.
                      } else {
                          if(k=="ulDDFilters" && !blnAllowFilter){
                              //Disallowed filter.  Skip.
                          } else {
                              aryValids.push(k);
                          }
                      }
                  }
              }
          }
          Logger.log(YAHOO.lang.JSON.stringify(aryValids), "debug", "BaseApp.GetValidEnabledDropTargets");
          
          return aryValids;
      },
      
      /**
        @param {HTMLElement} p_oArg Expected to be the result of the targeted drag-drop object's <code>.getEl()</code> accessor.
      */
      initDDGroups: function(p_oArg){
          var yuiddp = this.objDDProxies[ Dom.get(p_oArg).id ];
          
          if(!yuiddp){
              Logger.log("Unable to find drag-drop JS Object.", "error", "BaseApp.initDDGroups");
              Logger.log(p_oArg, "debug", "BaseApp.initDDGroups");
          } else {
              var aryValids = this.GetValidEnabledDropTargets(p_oArg);
              
              yuiddp.addToGroup(this.NominateDDInteractionGroup("source"));
              
              //Make drag-drop groups for all of the valid targets
              for(var i=0; i<aryValids.length; i++){
                  yuiddp.addToGroup(this.NominateDDInteractionGroup(aryValids[i]));
              }
              
              Logger.log(YAHOO.lang.JSON.stringify(yuiddp.groups), "debug", "BaseApp.initDDGroups");
          }
      },
      
      /**
        This sets a drag-drop object's groups to be only the interaction group of its parent - or none at all if the variable is in a source list.
        @param {HTMLElement} p_oArg The DOM handle of the drag-drop object.
      */
      idleDDGroups: function(p_oArg){
          var yuiddp = this.objDDProxies[ Dom.get(p_oArg).id ];
          if(!yuiddp){
              Logger.log("Unable to find drag-drop JS Object.", "error", "BaseApp.idleDDGroups");
              Logger.log(p_oArg, "debug", "BaseApp.idleDDGroups");
          } else {
              //Destructively update groups
              //yuiddp.groups = {};  //Shortcut, not future-proof.
              for(var k in yuiddp.groups) if(YAHOO.lang.hasOwnProperty(yuiddp.groups, k)){
                  yuiddp.removeFromGroup(k);
              }
              
              var elParent = yuiddp.getEl().parentNode;
              if(Dom.hasClass(elParent, "active")){
                  //Keep one group.
                  yuiddp.addToGroup(this.NominateDDInteractionGroup(elParent.id));
              }
          }
      },
      
      /**
        A lazily loaded panel that displays the available data sources for this Analyzer.  Initialized when a user requests it, as it is not expected to be an oft-requested feature.
        @property DataSourcePeekPanel
      */
      DataSourcePeekPanel: null,
      
      RevealDataSources: function(p_e){
          var elClickHandle = Event.getTarget(p_e);
          
          //Load
          if(!this.DataSourcePeekPanel){
              this.DataSourcePeekPanel = new YAHOO.widget.Panel(
                "DataSourcePeek",
                {
                  visible: false,
                  width: "25em",
                  constraintoviewport: true,
                  close: true,
                  draggable: true,
                  context: [elClickHandle, "tl", "br"]
                }
              );
              
              if(!this.MetadataTableStore["ds=datasource"].responseJSON){
                  Logger.log("No JSON available for datasource table.", "error", "BaseApp.RevealDataSources");
              } else {
                  var arySources = this.MetadataTableStore["ds=datasource"].responseJSON.data;
                  var aryHTML = [];
                  aryHTML.push("<dl>");
                  for(var i=0; i<arySources.length; i++){
                      aryHTML.push("  <dt>" + arySources[i].SourceName + "</dt>");
                      if(arySources[i].SourceFootnote){
                          aryHTML.push("  <dd><dfn>" + arySources[i].SourceFootnote + "</dfn></dd>");
                      }
                      aryHTML.push(
                        "  <dd>" +
                          MONTH_NAMES[ parseInt(arySources[i].MinFromMonth,10)-1 ] +
                          ", " +
                          arySources[i].MinFromYear +
                          " through " +
                          MONTH_NAMES[ parseInt(arySources[i].MaxFromMonth,10)-1 ] +
                          ", " +
                          arySources[i].MaxFromYear +
                        "</dd>"
                      );
                  }
                  aryHTML.push("</dl>");
                  this.DataSourcePeekPanel.setBody(aryHTML.join("\n"));
              }
              
              this.DataSourcePeekPanel.render(document.body);
          }
          
          this.DataSourcePeekPanel.show();
      },
      
      RefreshTargetListMetadata: function(p_sType, p_aArgs){
          var elDDTarget = p_aArgs[0];
          if(!elDDTarget){
              Logger.log("Drag-drop target not found.", "error", "BaseApp.RefreshTargetListMetadata");
          } else {
              if(!Dom.hasClass(elDDTarget, "active")){
                  //Nop - source list, no need to update metadata for this.
              } else {
                  this.DDTargetMeta[ elDDTarget.id ].children = elDDTarget.childNodes.length;
              }
          }
      },
      
      /**
        @property PauseSourceVarListRefresh
        @type Boolean
      */
      PauseSourceVarListRefresh: false,
      
      MaybeRefreshBreakoutVariablesList: function(p_oArgs){
          Logger.log("Called.", "trace", "BaseApp.MaybeRefreshBreakoutVariablesList");
          if (this.PauseSourceVarListRefresh){
              Logger.log("Variable list refreshing paused.  Nop.", "debug", "BaseApp.MaybeRefreshBreakoutVariablesList");
          } else {
              var elDest = Dom.get(p_oArgs.strDropTargetId);
              if(!elDest){
                  Logger.log("Unable to find movement destination.", "error", "BaseApp.MaybeRefreshBreakoutVariablesList");
              } else {
                  if(!Dom.hasClass(elDest, "source")){
                      Logger.log("Non-source destination; no need to refresh source list.", "debug", "BaseApp.MaybeRefreshBreakoutVariablesList");
                  } else {
                      var elCatInput = Dom.get(this.cfg.getProperty("elCategoryInput"));
                      if(elCatInput.childNodes.length == 0){
                          Logger.log("Breakout variable list not refreshed due to lack of categories.  (Is the page initializing?)", "debug", "BaseApp.MaybeRefreshBreakoutVariablesList");
                      } else {
                          var catid = elCatInput.value;
                          if(!catid){
                              Logger.log("Category ID not determined.", "error", "BaseApp.MaybeRefreshBreakoutVariablesList");
                              Logger.log(elCatInput, "debug", "BaseApp.MaybeRefreshBreakoutVariablesList");
                          } else {
                              this.UpdateBreakoutVariablesList({type:null, CatID:catid, suppressScroll: true});
                          }
                      }
                  }
              }
          }
          Logger.log("Finished.", "trace", "BaseApp.MaybeRefreshBreakoutVariablesList");
      },
      
      /**
        Argument normalizer for RefreshHiddenDDInput.
        @param {HTMLElement} p_oArgs Expected to be a DOM handle for a YUI drag-drop object.
      */
      RefreshHiddenDDInput_FromPreview: function(p_oArgs){
          Logger.log("Called.", "trace", "BaseApp.RefreshHiddenDDInput_FromPreview");
          var elPreviewed = Dom.get(p_oArgs);
          if(!elPreviewed){
              Logger.log("Unable to find previewed element from argument '" + p_oArgs + "'.", "error", "BaseApp.RefreshHiddenDDInput_FromPreview");
          } else {
              var elParent = elPreviewed.parentNode;
              if(!elParent){
                  Logger.log("Previewed element appears to not have a parent node.", "error", "BaseApp.RefreshHiddenDDInput_FromPreview");
                  Logger.log(elPreviewed, "debug", "BaseApp.RefreshHiddenDDInput_FromPreview");
              } else {
                  this.RefreshHiddenDDInput(null, [elParent]);  //Accomodate for CustomEvent.LIST signature:  String -> Array -> Void.
              }
          }
          Logger.log("Finished.", "trace", "BaseApp.RefreshHiddenDDInput_FromPreview");
      },
      
      /**
        Refreshes Drag-drop manager's cache, to correct an obscure bug in IE and Opera (not Firefox or Safari):  The drag-drop manager doesn't recognize a Table or Filter as a valid droptarget if a drag-drop object is dropped on an already visible target, and then the table or filter is revealed by clicking.  They aren't recognized if they are hidden when a valid drag-droup action takes place.
        @param p_oArg {HTMLElement | String} Identifier or handle for drag-drop element.  The element is used to refresh only a few groups.  If this is an invalid argument, all groups will be refreshed in the Drag-Drop Manager.
      */
      SelectivelyRefreshDDCache: function(p_oArg){
          //Try to refresh just the groups available to the freshly hidden/revealed box; worst-case, refresh'em all.
          try{
              YAHOO.util.DragDropMgr.refreshCache(this.objDDBoxes[ Dom.get(p_oArg).id ].groups);
          } catch(e){
              Logger.log("Cache not refreshed, because there is no drop-target registered under " + (p_oArg.id || p_oArg) + ".  This error rose while looking:\n" + e.message + "\nRefreshing entire cache instead.", "debug", "BaseApp.SelectivelyRefreshDDCache");
              YAHOO.util.DragDropMgr.refreshCache();
          }
      },
      
      /**
        Adds the highlight CSS class to the focused variable's preview.
      */
      HighlightPreview: function(p_elToPreview){
          var elBeingPreviewed = Dom.get(p_elToPreview) || this.focusvariable;
          if(!elBeingPreviewed){
              Logger.log("Unable to find element with focus, given argument '" + p_elToPreview + "'.", "error", "BaseApp.HighlightPreview");
          } else {
              var elPreview = Dom.get("Preview_" + elBeingPreviewed.id);
              if(!elPreview){
                  Logger.log("Unable to find preview element.", "error", "BaseApp.HighlightPreview");
              } else {
                  Dom.addClass(elPreview, "preview-highlight");
              }
          }
      },
      
      /**
        Handles highlighting of the preview element; won't highlight if the preview element isn't selected, which it may not be due to some previews loading asynchronously.
        @param p_elPreviewed {HTMLElement | String} The drag-drop handle whose preview has just been created.
      */
      MaybeHighlightPreviewOnload: function(p_elPreviewed){
          var elDDHandle = Dom.get(p_elPreviewed);
          if(!elDDHandle){
              Logger.log("Unable to find drag-drop handle from argument '" + p_elPreviewed + "'.", "debug", "BaseApp.MaybeHighlightPreviewOnload");
          } else {
              if(this.focusvariable != elDDHandle){
                  Logger.log("Focus variable no longer matches the intended handle ('" + (this.focusvariable ? this.focusvariable.id : this.focusvariable) + "' vs '" + elDDHandle.id + "').  Nop.", "debug", "BaseApp.MaybeHighlightPreviewOnload");
              } else {
                  this.HighlightPreview(elDDHandle);
              }
          }
      },
      
      /**
        Removes the highlight CSS class from the focused variable's preview.
      */
      DimPreview: function(){
          var elPreviewHL;
          if(this.focusvariable){
              elPreviewHL = Dom.get("Preview_" + this.focusvariable.id);
          } else {
              elPreviewHL = Dom.getElementsByClassName("preview-highlight", "li", "dragdroparea")[0];
          }
          if(!elPreviewHL){
              Logger.log("Found no element requiring dimming.", "debug", "BaseApp.DimPreview");
          } else {
              Dom.removeClass(elPreviewHL, "preview-highlight");
          }
      },
      
      VariableFocusedHandler: function(p_sType, p_aArgs){
          var elTarget = Dom.get(p_aArgs[0]);
          this.PreviewVariable(elTarget);
          
          //Maybe scroll - called here because we want to guarantee the preview exists
          if(!Dom.hasClass(elTarget.parentNode, "active")){
              //Inactive box has no need to scroll.
          } else {
              this.HighlightPreview(elTarget);
              this.ScrollPreview(elTarget);
          }
      },
      
      /**
        Handles the (HTTP-)successful return of a YAHOO.util.DataSource.  May fire PreviewRenderedEvent.
      */
      UpdateResponseListPreview_HandleSuccess: function(oRequest, oResponse){
          Logger.log("Called.", "trace", "BaseApp.UpdateResponseListPreview_HandleSuccess");
          //Logger.log(YAHOO.lang.JSON.stringify([oRequest, oResponse, oArgument]), "debug", "BaseApp.UpdateResponseListPreview_HandleSuccess");
          var blnContinue = false;
          
          //Prepare an Object for error reporting.
          var oError = {
            "strRequest":oRequest,
            "oResponse":oResponse,
            "dTime":(new Date()),
            "msg":oResponse.meta.replyText
          };
          switch(oResponse.meta.replyCode){
              case 300:
                  this.DataWarningEvent.fire(oError);
                  //Fallthrough
              case 200:
                  blnContinue = true;
              break;
              case 500:
                  //Fallthrough
              default:
                  this.DataErrorEvent.fire(oError);
              break;
          }
          if(!blnContinue){
              Logger.log("Metadata service return did not warrant continuation.", "error", "BaseApp.UpdateResponseListPreview_HandleSuccess");
          } else {
              var varid = this.MakeDDDOMVarID(oRequest.split("VarID=")[1].split("&")[0]);
              var elTargetList = Dom.get("Preview_Response_List_" + varid);
              if(!elTargetList) {
                  Logger.log("Unable to find target list with id '" + varid + "'.", "error", "BaseApp.UpdateResponseListPreview_HandleSuccess");
              } else {
                  var
                    metaFilterData = this.IndexedFilterData[varid],
                    objFilteredValues={}, //Keyed value is consistently 'true'
                    blnWhitelist = false //Keep all non-keyed matches, which would be everything without keys
                  ;
                  if(!metaFilterData){
                      //Leave default values as above
                  } else {
                      for(var k=0; k<metaFilterData.filterdialoglist.length; k++){
                          objFilteredValues[ metaFilterData.filterdialoglist[k] ] = true;
                      }
                      blnWhitelist = metaFilterData.In_or_Ex == "IN";
                  }
                  
                  //Key to distinguish unique values
                  var DISTINGUISHER = "LongLabel";
                  
                  //Build distinct value list
                  var objResponseValues = {};
                  for(var i=0; i<oResponse.results.length; i++){
                      objResponseValues[ oResponse.results[i][DISTINGUISHER] ] = true;
                  }
                  
                  //Key:false -> this value will be visually disabled (strikethrough, hidden, whatever the stylesheet says)
                  var objResultsToShow = {};
                  for(var key in objResponseValues){if(YAHOO.lang.hasOwnProperty(objResponseValues, key)){
                      //we include (whitelist) or exclude (blacklist) any listed filtered value
                      objResultsToShow[ key ] = (!!objFilteredValues[ key ]) ? blnWhitelist : !blnWhitelist;
                  }}
                  
                  var
                    aryFVs = [],
                    iActiveValTally = 0
                  ;
                  var strTmp = null;
                  for(var key in objResultsToShow){if(YAHOO.lang.hasOwnProperty(objResultsToShow, key)){
                      aryFVs.push(key);
                      if(objResultsToShow[key] === true) iActiveValTally++;
                  }}
                  
                  var aryHtmlFVs = [];
                  //Maybe denote the list as long
                  //Long lists either show everything or truncate to the first few non-excluded responses
                  Logger.log("About to list " + iActiveValTally + " active values, of " + aryFVs.length + " total.", "debug", "BaseApp.UpdateResponseListPreview_HandleSuccess");
                  var
                    blnIsLongList = (aryFVs.length > this.cfg.getProperty("iLongPreviewListLength")),
                    blnTruncate = !!this.cfg.getProperty("TruncateLongListPreviews")
                  ;
                  //Logger.log("[blnIsLongList, blnTruncate]", "debug", "BaseApp.UpdateResponseListPreview_HandleSuccess");
                  //Logger.log([blnIsLongList, blnTruncate], "debug", "BaseApp.UpdateResponseListPreview_HandleSuccess");
                  if(blnIsLongList && blnTruncate){
                      //Just show the first few, then a tally of the remainder
                      for(var i=0; i<aryFVs.length; i++){
                          if(objResultsToShow[ aryFVs[i] ] === true){
                              aryHtmlFVs.push('  <li>' + aryFVs[i] + '</li>');
                              
                              //Stop when we have three or four values
                              if(aryHtmlFVs.length >= 3){
                                  if(iActiveValTally == 4){
                                      continue;
                                  } else {
                                      break;
                                  }
                              }
                          } else {
                              //No showing inactive values.
                          }
                      }
                      var iLeftoverTally = iActiveValTally - aryHtmlFVs.length;
                      if(iLeftoverTally > 0){
                          aryHtmlFVs.push('  <li class="meta-response">And <dfn>' + iLeftoverTally + '</dfn> other values...</li>');
                      }
                  } else {
                      //We're not truncating the list - so, show all with a CSS class added.
                      for(var i=0; i<aryFVs.length; i++){
                          aryHtmlFVs.push('  <li class="' + (objResultsToShow[ aryFVs[i] ] ? '' : 'excluded') + '">' + aryFVs[i] + '</li>');
                      }
                  }
                  
                  if(blnIsLongList){
                      Dom.addClass(elTargetList, "long-response-list");
                  }
                  
                  elTargetList.innerHTML = aryHtmlFVs.join("\n");
                  
                  this.PreviewRenderedEvent.fire(varid);
              }
          }
          Logger.log("Finished.", "trace", "BaseApp.UpdateResponseListPreview_HandleSuccess");
      },
      
      /**
        Handles several custom events.
        @param p_oArgs {HTMLElement} Expected to be a drag-drop-associated HTML element.
      */
      UpdateResponseListPreview: function(p_oArgs){
          Logger.log("Called.", "trace", "BaseApp.UpdateResponseListPreview");
          var elToPreview = Dom.get(p_oArgs);
          if(!elToPreview){
              Logger.log("Could not retrieve element to preview from descriptor (type " + typeof p_oArgs + ").", "error", "BaseApp.UpdateResponseListPreview");
              Logger.log(p_oArgs, "debug", "BaseApp.UpdateResponseListPreview");
          } else {
              //First, check that we're not working with a date variable, which is not supported for filtering or value previews now.  --AJN 20080613
              var metaBV = this.IndexedMetadata[ "breakout_variables" ][ "VarID=" + this.ExtractDDDOMVarID(elToPreview.id) ];
              if(!metaBV){
                  Logger.log("Unable to find breakout_variable metadata for argument.", "error", "BaseApp.UpdateResponseListPreview");
                  Logger.log(elToPreview, "debug", "BaseApp.UpdateResponseListPreview");
              } else {
                  if(!!metaBV.DateVariable){
                      Logger.log("Found a date variable.  Running preview list substitute.", "debug", "BaseApp.UpdateResponseListPreview");
                      var elTargetList = Dom.get("Preview_Response_List_" + elToPreview.id);
                      elTargetList.innerHTML = "<li>(date variable)</li>";
                  } else {
                      var _xhrFailure = function(oRequest, oResponse){
                          Logger.log("Called.", "trace", "BaseApp.UpdateResponseListPreview._xhrFailure");
                          this.HTTPErrorEvent.fire({
                            "strRequest": oRequest,
                            "oResponse": oResponse
                          });
                          Logger.log("Finished.", "trace", "BaseApp.UpdateResponseListPreview._xhrFailure");
                      };
                      Logger.log("About to request the variable responses.", "debug", "BaseApp.UpdateResponseListPreview");
                      this.ydsFormattedValues.sendRequest(
                        this.GenerateFormattedValuesRequest(elToPreview),
                        {
                          scope: this,
                          failure: _xhrFailure,
                          success: this.UpdateResponseListPreview_HandleSuccess
                        }
                      );
                      Logger.log("Sent the variable responses request.", "debug", "BaseApp.UpdateResponseListPreview");
                  }
              }
          }
          Logger.log("Finished.", "trace", "BaseApp.UpdateResponseListPreview");
      },
      
      /**
        @param {String | HTMLElement} p_el Assumed to be the DOM handle of a drag-drop element.
        @return Returns a GET query parameter set.
      */
      GenerateFormattedValuesRequest: function(p_el){
          var elTarget = Dom.get(p_el);
          var aryComponents = [];
          if(!elTarget){
              Logger.log("Unable to find target element.", "error", "BaseApp.GenerateFormattedValuesRequest");
          } else {
              aryComponents.push("VarID=" + this.ExtractDDDOMVarID(p_el.id));
              aryComponents.push("Priority=" + this.GetFormatChoice(p_el));
          }
          return aryComponents.join("&");
      },
      
      /**
        For active variables, scrolls the preview's parent to the preview.
        @param p_elHighlit {HTMLElement | String} Element that was highlighted, which should have a preview node alive in the DOM.
      */
      ScrollPreview: function(p_elDDHighlit){
          Logger.log("Called.", "trace", "BaseApp.ScrollPreview");
          Logger.log(p_elDDHighlit, "debug", "BaseApp.ScrollPreview");
          var elDDHandle = Dom.get(p_elDDHighlit);
          if(!elDDHandle){
              Logger.log("Highlighted element not found.", "error", "BaseApp.ScrollPreview");
          } else {
              if(!Dom.hasClass(elDDHandle, "anavar")){
                  Logger.log("Expecting the argument to be a drag-drop object.", "error", "BaseApp.ScrollPreview");
              } else {
                  if(!Dom.hasClass(elDDHandle.parentNode, "active")){
                      Logger.log("Inactive variables don't need to scroll.", "debug", "BaseApp.ScrollPreview");
                  } else {
                      var elTarget = Dom.get("Preview_" + elDDHandle.id);
                      if(!elTarget){
                          Logger.log("Preview element not found.", "error", "BaseApp.ScrollPreview");
                      } else {
                          var elParent = elTarget.parentNode;
                          var regTarget = Dom.getRegion(elTarget);
                          var regFC = Dom.getRegion(elParent.firstChild);
                          //Logger.log("Region of first-child:  " + regFC, "debug", "BaseApp.ScrollPreview");
                          //Logger.log("Region of parent:  " + Dom.getRegion(elParent), "debug", "BaseApp.ScrollPreview");
                          //Logger.log("Region of target:  " + regTarget, "debug", "BaseApp.ScrollPreview");
                          //Logger.log(elParent, "debug", "BaseApp.ScrollPreview");
                          //Logger.log(elParent.firstChild, "debug", "BaseApp.ScrollPreview");
                          //Logger.log(elTarget, "debug", "BaseApp.ScrollPreview");
                          
                          var newy = regTarget.top - regFC.top;
                          //Logger.log(newy, "debug", "BaseApp.ScrollPreview");
                          
                          var animScroll = new YAHOO.util.Scroll(
                            elParent,
                            {
                              scroll:{
                                to:[0, newy]
                              }
                            },
                            0.2
                          );
                          animScroll.animate();
                      }
                  }
              }
          }
          Logger.log("Finished.", "trace", "BaseApp.ScrollPreview");
      },
      
      /**
        @param p_el {HTMLULElement} A variable list that has been reordered, necessitating an update to the preview's order.
      */
      ReorderPreview: function(p_el){
          Logger.log("Called for '" + (p_el.id || p_el) + "'.", "trace", "BaseApp.ReorderPreview");
          var elVarbin = Dom.get(p_el);
          if(!(elVarbin && elVarbin.id && Dom.hasClass(elVarbin, "active"))){
              Logger.log("Parent not an active varbin.  Nop.", "debug", "BaseApp.ReorderPreview");
          } else {
              //Update order to reflect the contents of the updated list
              var elToReorder = Dom.get(this.cfg.getProperty("ListPreviews")[ elVarbin.id ]);
              if(!elToReorder){
                  Logger.log("No preview container found for '" + elVarbin.id + "'.", "debug", "BaseApp.ReorderPreview");
              } else {
                  var objPreviews = {}, elFC;
                  while(elToReorder.childNodes.length > 0){
                      elFC = elToReorder.firstChild;
                      //Logger.log("Child id:  " + elFC.id, "debug", "BaseApp.ReorderPreview");
                      objPreviews[ elFC.id.split("Preview_")[1] ] = elToReorder.removeChild(elFC);
                  }
                  
                  var aryDebug = [];
                  for(var key in objPreviews){if(YAHOO.lang.hasOwnProperty(objPreviews, key)){
                      aryDebug.push(key);
                  }}
                  Logger.log("Keys:  " + YAHOO.lang.JSON.stringify(aryDebug), "debug", "BaseApp.ReorderPreview");
                  
                  //Iterate over the list to get the variable order
                  for(var i=0, aryDDs = elVarbin.childNodes; i<aryDDs.length; i++){
                      //Logger.log("Reinserting preview for '" + aryDDs[i].id + "'.", "debug", "BaseApp.ReorderPreview");
                      elToReorder.appendChild(objPreviews[ aryDDs[i].id ]);
                      delete objPreviews[ aryDDs[i].id ];  //Cleanup
                  }
              }
          }
          Logger.log("Finished.", "trace", "BaseApp.ReorderPreview");
      },
      
        /**
          Creates a preview element and displays it in a preview zone associated with the argument's parent list.
          Fires PreviewCreatedEvent.
          @param p_el {HTMLElement | String} <code>&lt;li&gt;</code> element with associated YUI drag-drop object.
        */
        PreviewVariable: function(p_el){
            Logger.log("Called.", "trace", "BaseApp.PreviewVariable");
            var elToPreview = Dom.get(p_el);
            if(!elToPreview){
                Logger.log("Unable to find element to preview.", "error", "BaseApp.PreviewVariable");
            } else {
                var elParentToBe = Dom.get(this.cfg.getProperty("ListPreviews")[ elToPreview.parentNode.id ]);
                if(!elParentToBe){
                    Logger.log("No suitable parent container for the preview found.", "debug", "BaseApp.PreviewVariable");
                } else {
                    Logger.log("New parent:  '" + elParentToBe.id + "'.", "debug", "BaseApp.PreviewVariable");
                    var oArg = {
                      baseid: elToPreview.id,
                      newid: "Preview_" + elToPreview.id,
                      parent: elParentToBe,
                      metaBV: this.IndexedMetadata["breakout_variables"][ "VarID=" + this.ExtractDDDOMVarID(elToPreview.id) ],
                      metaVP: this.IndexedMetadata["varpriorities"][ "VarID=" + this.ExtractDDDOMVarID(elToPreview.id) ]
                    };
                    
                    if(Dom.get(oArg.newid)){
                        Logger.log("Preview already exists - skipping generation.", "debug", "BaseApp.PreviewVariable");
                    } else {
                        Logger.log("Preview doesn't already exist - proceeding.", "debug", "BaseApp.PreviewVariable");
                        
                        //Create the preview container, which active and inactive variables will need.
                        var elContainer = document.createElement("li");
                        elContainer.id = oArg.newid;
                        elParentToBe.appendChild(elContainer);
                        this.PreviewCreatedEvent.fire(elToPreview);
                        //PreviewRenderedEvent fires elsewhere, because the preview isn't yet rendered completely (still need the response list).
                        
                        //Define the preview text/HTML
                        
                        //Build preview HTML based on whether we're previewing an active or an inactive variable.
                        if(!Dom.hasClass(elToPreview.parentNode, "active")){
                            this.UpdateDescriptionPreview(oArg);
                        } else {
                            this.UpdateActiveVarPreview(oArg);
                        }
                    }
                }
            }
            Logger.log("Finished.", "trace", "BaseApp.PreviewVariable");
        },
        
      /**
        May fire PreviewRenderedEvent.
        @param p_oArgs {Object} Expected to be defined as in the body of PreviewVariable, and to have fields:  baseid, newid, parent, metaBV, metaVP.
      */
      UpdateDescriptionPreview: function(p_oArgs){
          //First assertion:  The analysis variable we're previewing exists.
          var elToPreview = Dom.get(p_oArgs.baseid);
          if(!elToPreview){
              Logger.log("Element to preview not found.", "error", "BaseApp.UpdateDescriptionPreview");
          } else {
              var elContainer = Dom.get(p_oArgs.newid);
              if(!elContainer){
                  Logger.log("Preview container not found.", "error", "BaseApp.UpdateDescriptionPreview");
              } else {
                  var strNewHTML = '[' + p_oArgs.metaBV.VarName + '] ' + p_oArgs.metaBV.Description;
                  elContainer.innerHTML = strNewHTML;
                  this.PreviewRenderedEvent.fire(p_oArgs.baseid);
              }
          }
      },
      
      /**
        May fire ResponseListPreviewCreatedEvent.
        @param p_oArgs {Object} Expected to be defined as in the body of PreviewVariable, and to have fields:  baseid, newid, parent, metaBV, metaVP.
      */
      UpdateActiveVarPreview: function(p_oArgs){
          var elToPreview = Dom.get(p_oArgs.baseid);
          if(!elToPreview){
              Logger.log("Element to preview not found.", "error", "BaseApp.UpdateActiveVarPreview");
          } else {
              var elContainer = Dom.get(p_oArgs.newid);
              if(!elContainer){
                  Logger.log("Preview container not found.", "error", "BaseApp.UpdateActiveVarPreview");
              } else {
                  if(Dom.get("Preview_Response_List_" + elToPreview.id)){
                      Logger.log("Preview list container that is about to be created already exists.", "error", "BaseApp.UpdateActiveVarPreview");
                  } else {
                      var strNewHTML = this.FormatActiveVarPreviewHeader(p_oArgs);
                      
                      //Add response list (though content-addition is deferred)
                      strNewHTML += '<ul class="response-list" id="Preview_Response_List_' + elToPreview.id + '"></ul>';
                      
                      elContainer.innerHTML = strNewHTML;
                      
                      this.ResponseListPreviewCreatedEvent.fire(elToPreview);
                  }
              }
          }
      },
        
      /**
        Overrideable continuation within UpdateActiveVarPreview.
        @param p_oArgs {Object} As in UpdateActiveVarPreview.
      */
      FormatActiveVarPreviewHeader: function(p_oArgs){
          var retval;
          if(!(p_oArgs && p_oArgs.baseid && p_oArgs.metaBV)){
              Logger.log("Argument object not specified.", "error", "BaseApp.FormatActiveVarPreviewHeader");
          } else {
              retval = '[' + p_oArgs.metaBV.VarName + ']';
              //If collapsible responses are available, give the option to select them.
              if(p_oArgs.metaVP.length > 1){
                  var strPriority = this.GetFormatChoice(Dom.get(p_oArgs.baseid)) + "";
                  var aryOpts = [];
                  for(var i=0, vps = p_oArgs.metaVP; i<vps.length; i++){
                      aryOpts.push('<label><input type="radio" name="VarFormatChoice_' + p_oArgs.baseid + '" id="VarFormatChoice_' + p_oArgs.baseid + '_' + vps[i].Priority + '" value="' + vps[i].Priority + '" ' + (strPriority==vps[i].Priority ? 'checked="checked"' : '') + ' /> ' + vps[i].FormatTitle + '</label>');
                  }
                  retval += " " + aryOpts.join(" ");
              }
          }
          return retval;
      },
        
      /**
        (With apologies to the English language.)
        Removes (destroys) a variable's description from any preview area, regardless of its current location.
        @param p_el {HTMLElement | String} The element with associated YUI drag-drop object that no longer needs to have a preview (e.g. because it's moved into the inactive area, or it is already inactive and the focus blurred).
      */
      DeviewVariable: function(p_el){
          //Logger.log("Called for element '" + (p_el.id || p_el) + "'.", "trace", "BaseApp.DeviewVariable");
          var elDD = Dom.get(p_el);
          if(!elDD){
              Logger.log("No variable found for supplied object " + p_el, "error", "BaseApp.DeviewVariable");
          } else {
              var elPreview = Dom.get("Preview_" + elDD.id);
              if(!elPreview){
                  //Logger.log("No preview element found.", "debug", "BaseApp.DeviewVariable");
              } else {
                  //Remove listeners in this element's subtree
                  Event.purgeElement(
                    elPreview,
                    true  // <- Recurse
                  );
                  
                  //Remove element from the DOM
                  elPreview.parentNode.removeChild(elPreview);
              }
          }
          //Logger.log("Finished.", "trace", "BaseApp.DeviewVariable");
      },
      
      /**
        Meant to be called before the focus on a drag-drop variable blurs.
      */
      MaybeDeviewInactiveVariable: function(){
          Logger.log("Called.", "trace", "BaseApp.MaybeDeviewInactiveVariable");
          if(this.focusvariable){
              if(Dom.hasClass(this.focusvariable.parentNode, "active")){
                  //Active variable, taking focus away does nothing.
              } else {
                  //Inactive variable, taking focus away removes preview
                  this.DeviewVariable(this.focusvariable);
              }
          }
          Logger.log("Finished.", "trace", "BaseApp.MaybeDeviewInactiveVariable");
      },

      /**
        Assumes a namescheme of the format choice input <code>name</code> attributes:  "<code>VarFormatChoice_<var>DomID</var></code>"
      */
      UpdateResponseListChoice: function(p_e){
          Logger.log("Called.", "trace", "BaseApp.UpdateResponseListChoice");
          var elTarget = Event.getTarget(p_e);
          //Ascend until an INPUT is found, or the parent is found.
          while(!(elTarget.nodeName == "OL" || elTarget.nodeName == "INPUT")){
              elTarget = elTarget.parentNode;
          }
          if(elTarget.nodeName != "INPUT"){
              Logger.log("Clicked element was not an input, nor a child of one.", "warning", "BaseApp.UpdateResponseListChoice");
          } else {
              var aryIdData = elTarget.id.split("_");
              
              var varid = aryIdData[1];
              this.IndexedFormatChoiceData[ varid ] = elTarget.value;
              
              //Fire with varid
              this.FormatChoiceDataUpdateEvent.fire(varid);
          }
          Logger.log("Finished.", "trace", "BaseApp.UpdateResponseListChoice");
      },
        
      /**
        @property focusvariable
        @type {HTMLLIElement}
      */
      focusvariable: null,
      
      /**
      @param p_e {Event | HTMLElement} Either an event if this is serving as the handler for the document's click, or an element supplied by a custom event of the drag-drop class.
      */
      FocusOnVariable: function(p_e){
          //Logger.log("Called.", "trace", "BaseApp.FocusOnVariable");
          
          var elTarget = Event.getTarget(p_e) || Dom.get(p_e);
          if(!elTarget){
              Logger.log("No event target found.", "debug", "BaseApp.FocusOnVariable");
              this.BlurVariable();
          } else {
              //Logger.log("Current target:  " + (elTarget.id || elTarget.nodeName) + ".", "debug", "BaseApp.FocusOnVariable");
              
              var elDDRoot = Dom.get("dragdroparea"), docbod = document.body, contRoot = this.ActiveVarContextMenu.element;
              while(elTarget && !(this.IsDDRegistered(elTarget.id) || elTarget == elDDRoot || elTarget == docbod) || elTarget == contRoot){
                  elTarget = elTarget.parentNode;
              }
              if(!elTarget){
                  Logger.log("Ascended out of the DOM tree.", "error", "BaseApp.FocusOnVariable");
              } else {
                  if(elTarget == contRoot){
                      //Don't blur the variable - clicking the context menu shouldn't cause a deview.
                      Logger.log("Clicked a context menu item.", "debug", "BaseApp.FocusOnVariable");
                  } else {
                      if(!Dom.hasClass(elTarget, "anavar")){
                          //Logger.log("Clicked element was not an li.anavar, nor the child of one.", "debug", "BaseApp.FocusOnVariable");
                          this.BlurVariable();
                      } else {
                          if(this.focusvariable == elTarget){
                              //Clicking the focused variable should not reassign focus.  Nop.
                          } else {
                              this.BlurVariable();
                              Dom.addClass(elTarget, "focused");
                              this.focusvariable = elTarget;
                              this.VariableFocusedEvent.fire(this.focusvariable);
                          }
                      }
                  }
              }
          }
          //Logger.log("Finished.", "trace", "BaseApp.FocusOnVariable");
      },
      
      BlurVariable: function(){
          //Logger.log("Called.", "trace", "BaseApp.BlurVariable");
          if(this.focusvariable){
              this.BeforeVariableBlurEvent.fire();
              Dom.removeClass(this.focusvariable, "focused");
              this.focusvariable = null;
              this.VariableBlurredEvent.fire();
          } else {
              //Nop
          }
          //Logger.log("Finished.", "trace", "BaseApp.BlurVariable");
      },
      
      /**
        Destroys list if hiding.  Function signature is CustomEvent.LIST.
        @param {String} p_sType
        @param {Array} p_aArgs
      */
      UpdateDDTargetVisibility: function(p_sType, p_aArgs){
          var strIdToToggle = p_aArgs[0];
          var objDDContainerAssocs = {
            "ulDDFilters":"dlContainer_DDFilters",
            "ulDDTables":"dlContainer_DDTables"
          }
          var cfgVis = this.cfg.getProperty("DragDropVisualToggle");
          Dom.setStyle(
            objDDContainerAssocs[ strIdToToggle ],
            cfgVis.cssProp,
            this.DDTargetMeta[strIdToToggle].enabled ? cfgVis.cssValues.show : cfgVis.cssValues.hide
          );
          
          //Now, empty the variables that were in use if this box has just been 'disabled'
          //Logger.log("About to empty.", "trace", "BaseApp.UpdateDDTargetVisibility");
          if(!this.DDTargetMeta[strIdToToggle].enabled){
              this.EmptyActiveList(strIdToToggle);
          }
          //Logger.log("Emptied.", "trace", "BaseApp.UpdateDDTargetVisibility");
      },
      
      /**
        Facilitates removing an entire active list's contents of variables, preserving order in source box, without refreshing the source list's order between each variable motion.
        @param {HTMLElement} p_elList Assumed to be one of the DDTargets; at least CSS-classed "<code>active</code>".  That class check is enforced because otherwise this causes an infinite loop.
      */
      EmptyActiveList: function(p_elList){
          Logger.log("Called.", "trace", "BaseApp.EmptyActiveList");
          var elDDBox = Dom.get(p_elList);
          if(!elDDBox){
              Logger.log("List to empty not found.", "error", "BaseApp.EmptyActiveList");
          } else {
              //Do move silently en masse
              this.PauseSourceVarListRefresh = true;
              
              for(var elFC; elFC = elDDBox.firstChild;){
                  //Logger.log("About to remove '" + elFC.id + "'.", "debug", "BaseApp.EmptyActiveList");
                  this.MoveAnaVariable(elFC.id, elDDBox, this.ddmask.id);
              }
              
              this.PauseSourceVarListRefresh = false;
              
              //Notify
              this.MaybeRefreshBreakoutVariablesList({strDropTargetId: "ulDDSource"});
          }
          Logger.log("Finished.", "trace", "BaseApp.EmptyActiveList");
      },
      
      /**
        Utility method that returns an event handler for tabview navigation links.
        @param p_elA {HTMLAnchorElement} Anchor element, with expected href "#tab<var>n</var>", a matching href to one of the YUI TabView navigation links.
        @return {Function} Returns a 'click' event handler.
      */
      NavigateTabView: function(p_elA){
          //Logger.log("Called.", "trace", "BaseApp.NavigateTabView");
          var href = p_elA.getAttribute("href");
          var iNextTab = parseInt(href.split("#tab")[1], 10);
          var f;
          if(iNextTab === NaN){
             Logger.log("Could not get new tab index from href '" + href + "'.", "error", "BaseApp.NavigateTabView");
             f = function(p_e){
                 Logger.log("There was an error defining this handler.", "error", "BaseApp.NavigateTabView");
             };
          } else {
              f = function(p_e){
                  Event.preventDefault(p_e);
                  //Simulate the private function 'activate' found in YUI's TabView.js
                  var nexttab = this.tabView.getTab(iNextTab);
                  this.tabView.set(
                    'activeTab',  //Property to set
                    nexttab,  //Value to set the property to
                    nexttab == this.tabView.get('activeTab') //'Silent' (event will not fire if the tab isn't actually changing
                  );
              }
          }
          //Logger.log("Finished; returning.", "trace", "BaseApp.NavigateTabView");
          return f;
      },
      
      
      /**
        Adds "alt" class to children of an updated element.
        @param p_aArgs {Array} Expecting the first array item to be a reference to the freshly-updated variables' list (not list item) element.
      */
      RefreshAlternations: function(p_sType, p_aArgs){
          //Logger.log("Called.", "trace", "BaseApp.RefreshAlternations");
          var elBox = Dom.get(p_aArgs[0]);
          if(!elBox){
              Logger.log("No element supplied.", "trace", "BaseApp.RefreshAlternations");
          } else {
              for(var f, i=0; i<elBox.childNodes.length; i++){
                  [Dom.removeClass, Dom.addClass][i%2](elBox.childNodes[i], "alt");
              }
          }
          //Logger.log("Finished.", "trace", "BaseApp.RefreshAlternations");
      },
      
      /**
        Sets the visibility (according to the config property <code>DragDropVisualToggle</code>) of the options whenever a list's contents are changed.
      */
      RefreshMultiVarOptionsVisiblity: function(p_sType, p_aArgs){
          Logger.log("Called.", "trace", "BaseApp.RefreshMultiVarOptionsVisiblity");
          var elBox = Dom.get(p_aArgs[0]);
          var strOptId = DD_MULTOPT_ASSOCS[ elBox.id ];
          
          if(!strOptId){
              Logger.log("No option list found for '" + elBox.id + "'.", "debug", "BaseApp.RefreshMultiVarOptionsVisiblity");
          } else {
              var elOptContainer = Dom.get(strOptId);
              if(!elOptContainer){
                  Logger.log("Could not find options container id'd '" + strOptId + "'.", "error", "BaseApp.RefreshMultiVarOptionsVisiblity");
              } else {
                  Logger.log("Setting style based on '" + elBox.id + "' having " + elBox.childNodes.length + " children.", "debug", "BaseApp.RefreshMultiVarOptionsVisiblity");
                  var cfgVis = this.cfg.getProperty("DragDropVisualToggle");
                  Dom.setStyle(
                    elOptContainer,
                    cfgVis.cssProp,
                    (elBox.childNodes.length > 1) ? cfgVis.cssValues.show : cfgVis.cssValues.hide
                  );
              }
          }
          Logger.log("Finished.", "trace", "BaseApp.RefreshMultiVarOptionsVisiblity");
      },
      
      /**
        Reference to the Analyzer Context Menu widget.
        @property ActiveVarContextMenu
      */
      ActiveVarContextMenu: null,
      
      /**
        Executes in the context of the ContextMenu widget.
      */
      HighlightContextTarget: function(){
          Dom.addClass(this.contextEventTarget, "context-highlight");
      },
      
      /**
        Executes in the context of the ContextMenu widget.
      */
      DimContextTarget: function(){
          Dom.removeClass(this.contextEventTarget, "context-highlight");
      },
      
      /**
        This routine calls the method to update hidden form fields which represent the drop boxes' variable state.
        This also updates drag-drop interaction groups of the variable that has just moved.
        The logic of this routine is slightly complicated by the dropmask being a valid drop target.
      */
      VariableMotionHandler: function(p_oArgs){
          Logger.log("Called.", "trace", "BaseApp.VariableMotionHandler");
          //Logging the JSON stringification of an Event in IE causes an unhelpfully-described error.
          try{
              Logger.log(YAHOO.lang.JSON.stringify(p_oArgs), "debug", "BaseApp.VariableMotionHandler");
          } catch(e){
              Logger.log("Tried to log the argument object, but couldn't, because:  " + e.message, "debug", "BaseApp.VariableMotionHandler");
          }
          
          var elStart = Dom.get(p_oArgs.strStartParentId);
          var elEnd = Dom.get(p_oArgs.strDropTargetId);
          var yuiddVar = this.objDDProxies[ p_oArgs.strDDId ];
          
          //Two cases to remove filters:  Moving to an inactive list; or filtering is disabled but subsetting isn't.
          var blnScrapFilter = false;
          if(!Dom.hasClass(elEnd, "active")){
              //Deactivate any filters, since the filter is now semantically deactivated.
              blnScrapFilter = true;
          } else {
              //Are we moving this variable to a subset definition?
              if(elEnd.id == "ulDDFilters"){
                  //We are - preserve the filter if subsetting's allowed (which it SHOULD be at this point).
                  if(this.cfg.getProperty("AllowSubsetting")){
                      //Preserve filter.
                  } else {
                      blnScrapFilter = true;
                  }
              } else {
                  //Moving to a non-Subsetter box.  Scrap filter if configured to not allow filtering.
                  if(this.cfg.getProperty("AllowFiltering")){
                      //Preserve filter.
                  } else {
                      blnScrapFilter = true;
                  }
              }
          }
          if(blnScrapFilter){
              this.RemoveFilter(p_oArgs.strDDId);
          }
          //Logger.log("Groups of the yuiddVar (post):  " + YAHOO.lang.JSON.stringify(yuiddVar.groups), "debug", "BaseApp.VariableMotionHandler");
          
          if(Dom.hasClass(elStart, "dropmask")){
              //The drop mask is an element of temporary storage.  No inputs need be updated.
          } else {
              this.DDListChangedEvent.fire(elStart);
          }
          
          if(Dom.hasClass(elEnd, "dropmask")){
              Logger.log("Redirecting variable dropped on the drop-mask.", "trace", "BaseApp.VariableMotionHandler");
              //Move the variable again, this time to its home list (identified by category ID transformation if using the tree).
              var strTargetId;
              if(Dom.get("ulDDSource")){
                  strTargetId = "ulDDSource";
              } else {
                  var strCatId;
                  for(i=0, aryClasses = Dom.get(p_oArgs.strDDId).className.split(" "); i<aryClasses.length; i++){
                      //Break the loop when strCatId finally achieves a value (if "CatID:" isn't in the current CSS class, the value at [1] will be null, causing the assignment to evaluate to null)
                      if(strCatId = aryClasses[i].split("CatID:")[1]){
                          break;
                      }
                  }
                  strTargetId = this.MakeCatID( strCatId );
              }
              
              var elRedirect = Dom.get(strTargetId);
              if(!elRedirect){
                  Logger.log("Unable to retrieve redirection target identified by '" + strTargetId + "'", "error", "BaseApp.VariableMotionHandler");
              } else {
                  this.MoveAnaVariable(p_oArgs.strDDId, elEnd, elRedirect);
              }
          } else {
              //Normal destination list - update inputs.
              this.DDListChangedEvent.fire(elEnd);
          }
          Logger.log("Finished.", "trace", "BaseApp.VariableMotionHandler");
      },
      
      /**
        @return {Array} Returns array of all ID's of active variables (that is, variables outside of the 'Drag Variables From Here' bin).
      */
      GetAllActiveVarIds: function(){
          var retary = [];
          for(var i=0, aryBins = Dom.get(["ulDDRows", "ulDDColumns", "ulDDAxis", "ulDDGroups", "ulDDTables", "ulDDFilters"]); i<aryBins.length; i++){
              for(var j=0, aryChildren = aryBins[i].childNodes; j<aryChildren.length; j++){
                  retary.push(aryChildren[j]);
              }
          }
          Logger.log("Found " + retary.length + " nodes looking for active variables.", "debug", "BaseApp.GetAllActiveVarIds");
          return retary;
      },
      /**
        @return {Array} Returns array of all ID's of inactive variables (variables in the 'Drag Variables From Here' bin).
      */
      GetAllInactiveVarIds: function(){
          var retary = Dom.getElementsByClass("anavar", "li", "divCategoryTree");
          Logger.log("Found " + retary.length + " nodes looking for inactive variables.", "debug", "BaseApp.GetAllInactiveVarIds");
          return retary;
      },
      
      /**
        This is a YAHOO.widget.Dialog because of that widget's built-in Button support.  No form logic is actually necessary.
        @property ErrorPanel
      */
      ErrorPanel: null,
      
      initErrorPanel: function(){
          
          this.ErrorPanel = new YAHOO.widget.Panel("yuiwErrorPanel_" + (new Date()).valueOf(), {
            width:"50em",
            close:true,
            visible:false,
            draggable:true,
            modal:false,
            fixedcenter:true,
            constraintoviewport: true
          });
          this.ErrorPanel.setBody("placeholder");
          this.ErrorPanel.render(document.body);
      },
      
      /**
        This is a YAHOO.widget.Dialog because of that widget's built-in Button support.  No form logic is actually necessary.
        @property VariableDescriptionPanel
      */
      VariableDescriptionPanel: null,
      
      /**
        This code is placed in its own method to keep the init method tidy.  No arguments or return values.
        @method initVariableDescriptionPanel
      */
      initVariableDescriptionPanel: function(){
          var newdiv = document.createElement("div");
          newdiv.id = "yuivardescpanel_" + (new Date()).valueOf();
          document.body.appendChild(newdiv);
          
          var aryButtons = [{
            text: "Ok",
            handler: function(){ this.hide(); },
            isDefault: true
          }];
          
          this.VariableDescriptionPanel = new YAHOO.widget.Dialog(
            newdiv.id,
            {
              buttons: aryButtons,
              close: true,
              draggable: true,
              fixedcenter: false,  //Handled in beforeShow instead
              visible: false,
              postmethod: "manual",
              width: "30em",
              constraintoviewport: false
            }
          );
          
          //Center for display before showing
          this.VariableDescriptionPanel.subscribe("beforeShow", function(p_sType, p_aArgs){
              this.cfg.setProperty('xy', [
                ( Dom.getViewportWidth() - 400 ) / 2, //Center (window width hard-coded because I can't find an EM-to-PX converter function).
                Dom.getDocumentScrollTop() + 40 //40px of padding from the top of the viewport
              ]);
          }, this.VariableDescriptionPanel, true);

          
          //Note:  Without this placeholder, the footer renders before the body, leaving the buttons stuck above the text.
          this.VariableDescriptionPanel.setBody("placeholder");
          this.VariableDescriptionPanel.render(document.body);
      },
      
      /**
        Displays the Description panel.
        @param {Analyzer.DDProxy} p_anavar
      */
      ShowDescription: function(p_anavar){
          Logger.log("Called.", "trace", "BaseApp.ShowDescription");
          var meta = this.IndexedMetadata["breakout_variables"][ "VarID=" + this.ExtractDDDOMVarID(p_anavar.id) ];
          
          this.VariableDescriptionPanel.setHeader(Analyzer.BaseApp.BreakLongTitle("Description for '" + meta.VarLabel + "'"));
          this.VariableDescriptionPanel.setBody(["<div>", meta.Description, "</div>"].join("\n"));
          this.VariableDescriptionPanel.render(document.body);
          this.VariableDescriptionPanel.show();
          Logger.log("Finished.", "trace", "BaseApp.ShowDescription");
      },
      
      /**
       * Metadata about drag-drop targets.  Initialized in its own init method.
       * Each entry is keyed by the DOM id of the drag-drop target, and stores its:
       * * context menu label (<code>contextlabel</code>)
       * * enabled (<code>enabled</code>) status (used for all targets, particularly for multiple tables and filters, but also for charting vs. tabular form boxes)
       * * children tally
       * * active (variable) list; true iff the DOM element has the CSS class <code>active</code>
       * * forcible list - context menu can force the list to be active by placing a variable in it.
       * @property DDTargetMeta
      */
      DDTargetMeta: {},
      
      /**
        Initializes DDTargetMeta.  Context menu labels are hard-coded in this method.  Fires no events.
        @param {Object} p_FieldRuleCollection An object containing overwrites to rules as defined in this function's body.  This Object should be careful to respect the configuration property <code>AllowSubsetting</code>.
      */
      initDDTargetMeta: function(p_FieldRuleCollection){
          var outtype = GetRadioVal("optOutputType");
          if(outtype == ""){
              Logger.log("Error loading the current output type.", "error", "BaseApp.initDDTargetMeta");
          } else {
              var objToggleAssocs = this.cfg.getProperty("DragDrop_Toggle_Associations");
              if(!objToggleAssocs){
                  Logger.log("Error loading the drag-drop toggle associations.", "error", "BaseApp.initDDTargetMeta");
              } else {
                  var objFieldRuleCollection = p_FieldRuleCollection || {enabled:null, forcible:null};
                  
                  var objAllMovementTargets = {
                    "ulDDRows": "Row variable",
                    "ulDDColumns": "Column variable",
                    "ulDDAxis": "Bar variable",
                    "ulDDGroups": "Group variable",
                    "ulDDTables": outtype=="datatable" ? "Table variable" : "Chart variable",
                    "ulDDFilters": "Analysis filter"
                  };
                  var defInAnalysis = objFieldRuleCollection.enabled || {
                    "ulDDColumns": outtype == "datatable",
                    "ulDDRows": outtype == "datatable",
                    "ulDDAxis": outtype == "datachart",
                    "ulDDGroups": outtype == "datachart",
                    "ulDDTables": !!(Dom.get(this.cfg.getProperty("DragDrop_Toggle_Associations")["ulDDTables"]).checked),  //!! is just in case 'checked' ever returns a string value (shouldn't)
                    "ulDDFilters": this.cfg.getProperty("AllowSubsetting") && !!(Dom.get(this.cfg.getProperty("DragDrop_Toggle_Associations")["ulDDFilters"]).checked)
                  };
                  Logger.log(YAHOO.lang.dump(defInAnalysis), "debug", "BaseApp.initDDTargetMeta");
                  var cfgDragDropLimit = this.cfg.getProperty("DragDropLimit");
                  var objChildCaps = {
                    "ulDDColumns": cfgDragDropLimit,
                    "ulDDRows": cfgDragDropLimit,
                    "ulDDAxis": 1,
                    "ulDDGroups": 1,
                    "ulDDTables": null,
                    "ulDDFilters": null
                  };
                  Logger.log(YAHOO.lang.dump(objChildCaps), "debug", "BaseApp.initDDTargetMeta");
                  var objForceRules = objFieldRuleCollection.forcible || {
                    "ulDDTables": true,
                    "ulDDFilters": this.cfg.getProperty("AllowSubsetting")
                  };
                  Logger.log(YAHOO.lang.dump(objForceRules), "debug", "BaseApp.initDDTargetMeta");
                  for(var i=0, aryDDTargets = Dom.getElementsByClassName("varbin", "ul", "dragdroparea"); i<aryDDTargets.length; i++){
                      this.DDTargetMeta[ aryDDTargets[i].id ] = {
                        contextlabel: objAllMovementTargets[ aryDDTargets[i].id ],
                        active: Dom.hasClass(aryDDTargets[i], "active"),
                        children: aryDDTargets[i].childNodes.length,
                        
                        childcap: objChildCaps[ aryDDTargets[i].id ],
                        
                        //Overwriteable rules
                        enabled: !! (defInAnalysis[ aryDDTargets[i].id ]),
                        forcible: !! (objForceRules[ aryDDTargets[i].id ])
                      };
                  }
              }
          }
      },
      
      /**
        Method to update active status of a drop target.
      */
      UpdateDDTargetEnabledStatus: function(p_ddtId, p_bEnabled){
          Logger.log("Called.", "trace", "BaseApp.UpdateDDTargetEnabledStatus");
          //Logger.log(YAHOO.lang.JSON.stringify([p_ddtId, p_bEnabled]), "debug", "BaseApp.UpdateDDTargetActiveStatus");
          this.DDTargetMeta[p_ddtId].enabled = p_bEnabled;
          this.DDTargetEnabledStateUpdateEvent.fire(p_ddtId);
          Logger.log("Finished.", "trace", "BaseApp.UpdateDDTargetEnabledStatus");
      },
      
      /**
        This code is placed in its own method to keep the init method tidy.  No arguments or return values.
      */
      initContextMenus: function(){
          /**
            Define the menu's buttons' labels, handlers and scopes
            @private
            @param {HTMLElement} p_eCV Context of the context-menu event; expected to be an Analyzer variable.
            @param {Object} p_xContext Execution context of the <code>onclick</code> handler.  Optional.
            @return {Array} Returns array of menu item Objects
          */
          var _GenerateMenu = function(p_eCV, p_xContext){
              var xContext = p_xContext || this;
              
              //Begin building array of menu items
              var retval = [
                {
                  text: this.cfg.getProperty("cmDescriptionLabel"),
                  onclick:{
                    scope: xContext, //Adjust scope to Analyzer instance
                    fn: function(){
                        this.ShowDescription(p_eCV);
                    }
                  }
                }
              ];
              
              //Set up the variable-moving menu item; include later.
              
              var _onclickHandler = function(p_sType, p_unknown, p_o){
                  //(I'm not sure what the second argument is - it just comes as an empty Object. -AJN 20080425)
                  
                  //Before moving the variable, we may need to force the box to display.
                  var objToggleAssocs = this.cfg.getProperty("DragDrop_Toggle_Associations");
                  if(!objToggleAssocs){
                      Logger.log("Error loading the drag-drop toggle associations.", "error", "BaseApp.initContextMenus._GenerateMenu._onclickHandler");
                  } else {
                      if(objToggleAssocs[p_o.to]){
                          var elCheck = Dom.get(objToggleAssocs[p_o.to]);
                          if(!elCheck.checked){
                              elCheck.checked = true;
                              this.UpdateDDTargetEnabledStatus(p_o.to, true);
                          }
                      }
                  }
                  
                  this.MoveAnaVariable(p_o.anavar, p_o.from, p_o.to);
              };
              
              var aryMovementOptions = [];
              for(var i=0, aryTargets = this.GetValidEnabledDropTargets(p_eCV), objDDMeta; i<aryTargets.length; i++){
                  objDDMeta = this.DDTargetMeta[ aryTargets[i] ];
                  if(objDDMeta.contextlabel){
                      aryMovementOptions.push(
                        {
                          text: objDDMeta.contextlabel,
                          onclick:{
                            scope: xContext,
                            fn: _onclickHandler,
                            obj: {
                              anavar:p_eCV.id,
                              from:p_eCV.parentNode.id,
                              to:aryTargets[i]
                            }
                          }
                        }
                      );
                  }
              }
              Logger.log("Found " + aryMovementOptions.length + " options for variable movement.", "debug", "BaseApp._GenerateMenu");
              if(!Dom.hasClass(p_eCV.parentNode, "active")){
                  //Inactive variables can be moved into the analysis via context-click.  Nothing more for the menu after this.
                  if(aryMovementOptions.length > 0) {
                      retval.push(
                        {
                          text: "Include in analysis",
                          submenu: {
                            id:"ShiftingSubmenu",
                            itemdata: aryMovementOptions
                          }
                        }
                      );
                  }
              } else {
                  //Active variables can have a filter applied (per config), or be removed - unless the variable is a date variable.
                  var metaBV = this.IndexedMetadata[ "breakout_variables" ][ "VarID=" + this.ExtractDDDOMVarID(p_eCV.id) ];
                  if(!metaBV){
                      Logger.log("Couldn't find metadata for argument.  Filtering left off as default action.", "error", "BaseApp._GenerateMenu");
                      Logger.log(p_eCV, "debug", "BaseApp._GenerateMenu");
                  } else {
                      if(metaBV.DateVariable){
                          Logger.log("Currently, Date variables do not have filtering allowed.  Apologies to var '" + p_eCV.id + "'.", "warning", "BaseApp._GenerateMenu");
                      } else {
                          var sLabel = "";  //We get a menu item if this isn't blank after the next if-branch.
                          
                          //Is this variable in the subsetting list?
                          if(p_eCV.parentNode.id != "ulDDFilters"){
                              //Nope.  Is filtering allowed?
                              if(this.cfg.getProperty("AllowFiltering") !== true){
                                  //Nope.  So, no menu item.
                              } else {
                                  sLabel = "Filter";
                              }
                          } else {
                              //In the subsetting list.  Just double-check that subsetting is possible (it REALLY should be).
                              if(this.cfg.getProperty("AllowSubsetting") !== true){
                                  Logger.log("Subsetting shouldn't be allowed, but the menu item was almost called up.", "error", "BaseApp._GenerateMenu");
                              } else {
                                  sLabel = "Subset";
                              }
                          }
                          
                          if(sLabel == ""){
                              //No filter/subset menu item available.
                          } else {
                              retval.push(
                                {
                                  text: sLabel,
                                  onclick:{
                                    scope: xContext, //Adjust scope to Analyzer instance
                                    fn: function(){
                                        this.ConfigureAndShowFilter(p_eCV);
                                    }
                                  }
                                }
                              );
                          }
                      }
                  }
                  if(aryMovementOptions.length > 0 && this.cfg.getProperty("AllowMoveActiveVarsWithContextMenu") === true) {
                      retval.push(
                        {
                          text: "Use elsewhere in analysis",
                          submenu: {
                            id:"ShiftingSubmenu",
                            itemdata: aryMovementOptions
                          }
                        }
                      );
                  }
                  retval.push(
                    {
                      text: "Remove from analysis",
                      onclick:{
                        scope: xContext, //Adjust scope to Analyzer instance
                        fn: function(){
                            this.MoveAnaVariable(p_eCV.id, p_eCV.parentNode.id, this.ddmask.id);
                        }
                      }
                    }
                  );
              }
              
              return retval;
          };
          
          var aryTargets = Dom.getElementsByClassName("varbin", "ul", "dragdroparea");
          var elTreeRoot = Dom.get(this.cfg.getProperty("strCategoryTreeElId"));
          if (elTreeRoot) {
              aryTargets.push(elTreeRoot);
          }
          
          this.ActiveVarContextMenu = new YAHOO.widget.ContextMenu(
            "yuiwActiveVarContextMenu",
            {
              trigger: aryTargets,
              lazyload:true
            }
          );
          var _onBeforeShow = function(p_sType, p_aArgs, p_oExObj){
              //Logger.log("Called", "trace", "BaseApp._onBeforeShow");
              var elTarget = this.contextEventTarget;  //elTarget should be a direct child of a varbin
              
              
              if(Dom.hasClass(elTarget, "varbin")){
                  //Logger.log("Target was list.", "trace", "BaseApp._onBeforeShow");
                  //The context menu is attempting to activate on the list element, probably in padding - disallow.
                  //This could be allowed (not set to null) later if there is a context menu specific to empty list space.
                  elTarget = null;
              } else {
                  //Ascend until the target element is a direct child of a varbin (ascension necessary if there's, say, an image that got the context click).
                  //Logger.log("Ascending for target.", "trace", "BaseApp._onBeforeShow");
                  while(!Dom.hasClass(elTarget.parentNode, "varbin")){
                      Logger.log("Node name:" + elTarget.nodeName, "trace", "BaseApp._onBeforeShow");
                      elTarget = elTarget.parentNode;
                  }
              }
              
              this.clearContent();
              if(!elTarget){
                  Logger.log("Context menu target element not found.", "debug", "BaseApp._onBeforeShow");
                  this.cancel();
              } else {
                  this.addItems(_GenerateMenu.call(p_oExObj, elTarget));  //Scope adjusted to Analyzer
                  this.render();
              }
          };
          this.ActiveVarContextMenu.subscribe("beforeShow", _onBeforeShow, this, false);  //Execution context:  The menu.  Scope needs to be adjusted within this function, though, so pass Analyzer.
      },
      
      /**
        This code is placed in its own method to keep the init method tidy.  No arguments or return values.
      */
      initFilterDialog: function(){
          this.FilterDialog = new ListDialog(
            "yuiwFilterDialog_made" + (new Date()).valueOf(),
            {
              close: true,
              draggable: true,
              fixedcenter: true,
              visible:false,
              postmethod: "manual",
              width:"auto",
              buttons: [
                {
                  text: "Apply Filter",
                  handler:this.onFilterSubmit,
                  isDefault: true
                },
                {
                  text: "Clear Filter",
                  handler:this.onFilterClear
                },
                {
                  text: "Cancel",
                  handler:this.onDialogCancel
                }
              ],
              translator: Analyzer.BaseApp.FValuesToOptions,
              intLongListLength: this.cfg.getProperty("iLongListLength")
            }
          );
          this.FilterDialog.setBody("placeholder");  //Without this, a body doesn't get created.
          this.FilterDialog.render(document.body);
      },
      
      /**
        This code is placed in its own method to keep the init method tidy.  No arguments or return values.
      */
      initFVDataSource: function(){
          this.ydsFormattedValues = new YAHOO.util.DataSource(
            this.cfg.getProperty("FormattedValueQueryURL"),
            {
              maxCacheEntries:256,  //We won't need this many
              responseType: YAHOO.util.DataSource.TYPE_JSON,
              responseSchema: {
                resultsList: "data",
                fields: this.cfg.getProperty("FormattedValueFields"),  //Recall that these are the only fields passed forward from the raw response.
                metaFields: {
                  replyCode: "replyCode",
                  replyText: "replyText"
                }
              }
            }
          );
          
          /*I originally thought I'd need this code to get to the replyCode and replyText, but as of YUI 2.5.0, that can be fetched into oParsedResponse via the metaFields schema addition.
          this.ydsFormattedValues.doBeforeParseData = function(oRequest, oFullResponse){
              //oFullResponse at this time is the Connect's transport object
              Logger.log("oRequest:  " + YAHOO.lang.JSON.stringify(oRequest), "debug", "BaseApp.ydsFormattedValues.doBeforeParseData");
              Logger.log("oFullResponse:  " + YAHOO.lang.JSON.stringify(oFullResponse), "debug", "BaseApp.ydsFormattedValues.doBeforeParseData");
              
              switch(oFullResponse.replyCode){
                  case 300:
                      //Fallthrough
                  case 200:
                      return oFullResponse;
                  break;
                  case 500:
                  break;
                  default:
                      //Error
                  break;
              }
              
          };
          
          this.ydsFormattedValues.doBeforeCallback = function(oRequest, oFullResponse, oParsedResponse){
              //oFullResponse at this time is the responseText, as a string
              Logger.log("oRequest:  " + YAHOO.lang.JSON.stringify(oRequest), "debug", "ydsFormattedValues.doBeforeCallback");
              Logger.log("oFullResponse:  " + YAHOO.lang.JSON.stringify(oFullResponse), "debug", "ydsFormattedValues.doBeforeCallback");
              Logger.log("oParsedResponse:  " + YAHOO.lang.JSON.stringify(oParsedResponse), "debug", "ydsFormattedValues.doBeforeCallback");
              return oParsedResponse;
          };
          */
      },
      
      /**
        TODO
      */
      initIO: function(){
          if(!Dom.get("IOForm")){
              //Nop
          } else {
              //Initialize saving dialog
              this.SavingDialog = new Dialog(
                "ydSavingDialog",
                {
                  close: true,
                  draggable: true,
                  fixedcenter: true,
                  visible:false,
                  postmethod: "manual",
                  width:"auto",
                  buttons: [
                    {
                      text: "Ok",
                      handler:function(){
                          var elSearchRoot = Dom.getElementsByClassName("Analyzer", "form")[0];
                          var aryAllInputs = Dom.getElementsByClassName("savable", null, elSearchRoot);
                          
                          var aryToSave = [];
                          var aryToTurnOff = [];
                          var arySansIDs = [];
                          
                          for (var i=0; i<aryAllInputs.length; i++){
                              if (Dom.hasClass(aryAllInputs[i], "do-save")){
                                  aryToSave.push(aryAllInputs[i]);
                              } else {
                                  if(aryAllInputs[i].disabled){
                                      //Don't need to disable this - no follow-up needed
                                  } else {
                                      aryToTurnOff.push(aryAllInputs[i]);
                                  }
                              }
                              
                              if (!aryAllInputs[i].id){
                                  arySansIDs.push(aryAllInputs[i]);
                              }
                          }
                          
                          Logger.log("Elements to disable:", "debug", "ydSavingDialog.Ok");
                          Logger.log(aryToTurnOff, "debug", "ydSavingDialog.Ok");
                          
                          Logger.log("Elements to save:", "debug", "ydSavingDialog.Ok");
                          Logger.log(aryToSave, "debug", "ydSavingDialog.Ok");
                          
                          if (arySansIDs.length > 0){
                              Logger.log("Savable elements MISSING ID's:", "error", "ydSavingDialog.Ok");
                              Logger.log(arySansIDs, "debug", "ydSavingDialog.Ok");
                          }
                          
                          alert("TODO:  Save");
                          this.hide();
                      }
                    },
                    {
                      text: "Cancel",
                      handler:this.onDialogCancel
                    }
                  ]
                }
              );
              
              var elSavingBody = document.createElement("div");
              elSavingBody.innerHTML = [
                '<p>Save your query parameters up to this point.</p>',
                '<p><label><input type="checkbox" id="$DialogID_Checkbox_Dates" /> Save dates</label></p>'
              ].join("\n").replace(/\$DialogID/g, this.SavingDialog.id);
              
              /*
              // Add saving selection buttons. //
              var elSavingButtonP = document.createElement("p");
              Dom.setStyle(elSavingButtonP, "text-align", "center");
              elSavingBody.appendChild(elSavingButtonP);
              
              var button1 = new Button({
                id: "MenuSave_SelectAll",
                type: "push",
                label: "Select All",
                container: elSavingButtonP,
                onclick:{
                  fn: function(){
                      var arySavables = Dom.getElementsByClassName("savable", null, Dom.getElementsByClassName("Analyzer", "form")[0]);
                      Dom.addClass(arySavables, "do-save");
                  },
                  obj:{},
                  scope: this
                }
              });
              
              var button2 = new Button({
                id: "MenuSave_InvertSelection",
                type: "push",
                label: "Invert Selection",
                container: elSavingButtonP,
                onclick:{
                  fn: function(){
                      var arySavables = Dom.getElementsByClassName("savable", null, Dom.getElementsByClassName("Analyzer", "form")[0]);
                      for (var i=0; i<arySavables.length; i++){
                          if (Dom.hasClass(arySavables[i], "do-save")){
                              Dom.removeClass(arySavables[i], "do-save");
                          } else {
                              Dom.addClass(arySavables[i], "do-save");
                          }
                      }
                  },
                  obj:{},
                  scope: this
                }
              });
              
              var button3 = new Button({
                id: "MenuSave_SelectNone",
                type: "push",
                label: "Select None",
                container: elSavingButtonP,
                onclick:{
                  fn: function(){
                      var arySavables = Dom.getElementsByClassName("savable", null, Dom.getElementsByClassName("Analyzer", "form")[0]);
                      Dom.removeClass(arySavables, "do-save");
                  },
                  obj:{},
                  scope: this
                }
              });
              */
              
              // Finished configuring body; set. //
              this.SavingDialog.setBody(elSavingBody);
              this.SavingDialog.render("IOForm");
              
              
              
              //Add handler to open the saving dialog
              Event.addListener("menu_save", "click", function(p_e){
                  Event.preventDefault(p_e);
                  this.SavingDialog.show();
              }, this, true);
              
              //When the saving dialog is open, consider the Analyzer to be in "Saving mode," visually.
              this.SavingDialog.beforeShowEvent.subscribe(function(){
                  Dom.addClass(Dom.getElementsByClassName("Analyzer", "form")[0], "savables-visible");
              }, this, true);
              
              this.SavingDialog.beforeHideEvent.subscribe(function(){
                  Dom.removeClass(Dom.getElementsByClassName("Analyzer", "form")[0], "savables-visible");
              }, this, true);
          }
      },
      
      /**
        Sets the drag-drop mask element to cover the region of the source lists.
        @param {Analyzer.DDProxy} p_oArgs <code>DDProxy</code> object that fired the event.
      */
      EnableDDMask: function(p_oArgs){
          //Get element associated with the DDTarget
          var elTarget = this.ddmask.getEl();
          
          var elSourceTree = Dom.get(this.cfg.getProperty("strCategoryTreeElId"));  //this.accordion.getRoot().children[0].getEl(); //
          if(!elSourceTree){
              Logger.log("Source tree not found.  Nop.", "error", "BaseApp.EnableDDMask");
          } else {
              var elDDArea = Dom.get("dragdroparea");
              //We need to collect two regions of information - the region is calculated absolutely, not w.r.t. any relatively-positioned container (at least in YUI 2.5.1).
              var yrSourceArea = Dom.getRegion(elSourceTree);
              var yrDDArea = Dom.getRegion(elDDArea);
              
              //Dom.setStyle(elSourceTree, "border", "1px solid pink");
              //Dom.setStyle(elDDArea, "border", "1px solid maroon");
              
              var ziOfDragEl = parseInt(Dom.getStyle(p_oArgs.getDragEl(), "z-index"), 10);
              //Logger.log("Z-index of tree:  " + Dom.getStyle(elSourceTree, "z-index"), "debug", "BaseApp.EnableDDMask");
              //Logger.log("Z-index of drag element:  " + ziOfDragEl + " (type " + typeof ziOfDragEl + ")", "debug", "BaseApp.EnableDDMask");
              
              if(ziOfDragEl === NaN){
                  Logger.log("Unable to retrieve drag element's z-index.  To remove the variable, please use the \"Remove\" context menu action.", "error", "BaseApp.EnableDDMask");
              } else {
                  var ziOfMask = ziOfDragEl - 1;
                  var iNewTop = yrSourceArea.top - yrDDArea.top;
                  
                  Dom.setStyle(elTarget, "display", "block");
                  
                  //var yrOldMask = Dom.getRegion(elTarget);
                  //Logger.log("DropMask Region:  " + YAHOO.lang.dump(yrOldMask), "debug", "BaseApp.EnableDDMask");
                  //Logger.log("TreeView Region:  " + YAHOO.lang.dump(yrSourceArea), "debug", "BaseApp.EnableDDMask");
                  //Logger.log("DragDrop Region:  " + YAHOO.lang.dump(yrDDArea), "debug", "BaseApp.EnableDDMask");
                  //Logger.log("New top:  " + iNewTop, "debug", "BaseApp.EnableDDMask");
                  
                  
                  //Resize mask (Note:  Must add a dimension, like "px")
                  Dom.setStyle(elTarget, "top", iNewTop + "px");
                  Dom.setStyle(elTarget, "left", (yrDDArea.left - yrSourceArea.left) + "px");
                  Dom.setStyle(elTarget, "height", (yrSourceArea.bottom - yrSourceArea.top) + "px");
                  Dom.setStyle(elTarget, "width", (yrSourceArea.right - yrSourceArea.left) + "px");
                  
                  //Z-position and display mask
                  Dom.setStyle(elTarget, "z-index", ziOfMask);
                  
                  //var yrNewMask = Dom.getRegion(elTarget);
                  //Logger.log("DropMask Region:  " + YAHOO.lang.dump(yrNewMask), "debug", "BaseApp.EnableDDMask");
              }
          }
      },
      
      /**
        Folds up the drag-drop mask for later use.
      */
      DisableDDMask: function(p_oArgs){
          var elTarget = this.ddmask.getEl();
          Dom.setStyle(elTarget, "z-index", "-1");
          Dom.setStyle(elTarget, "display", "none");
      },
      
      /**
        @param p_var {String | Analyzer.DDProxy} The Analyzer variable to move
        @param p_from {String | HTMLULElement} The list the variable is in before execution
        @param p_to {String | HTMLULElement} The list the variable is in after execution
      */
      MoveAnaVariable: function(p_var, p_from, p_to){
          //Normalize arguments
          var oAnaVar = (typeof p_var == typeof "") ? this.objDDProxies[ p_var ] : p_var ;
          var elFrom = Dom.get(p_from);
          var elTo = Dom.get(p_to);
          
          if(!oAnaVar){
              Logger.log("Could not find DDProxy object identified by '" + p_var + "'.", "error", "BaseApp.MoveAnaVariable");
          } else {
              elTo.appendChild(oAnaVar.getEl());
              var objEventInfo = {
                strDDId: oAnaVar.id,
                strDropTargetId: elTo.id,
                strStartParentId: elFrom.id
              };
              
              if(elTo.id == "ulDDFilters"){
                  Analyzer.DDProxy.DropOnFilter.fire(objEventInfo);
              }
              
              Analyzer.DDProxy.VariableMoved.fire(objEventInfo);
          }
      },
      
      /**
      @param {String} p_sID ID of element that will have a drag-drop group named after it. 
      */
      NominateDDInteractionGroup: function(p_sID){
          return "Interact_With_" + p_sID;
      },
      
      /**
        Modifies the <code>document.body</code> by adding the YUI CSS class "<code>yui-skin-sam</code>".
      */
      init: function(userConfig){
          var refAnalyzer = this;
          var intInitStepCount=0;
          
          Logger.log('Step ' + ++intInitStepCount, 'trace', 'BaseApp.init');
          Dom.addClass(document.body, "yui-skin-sam");
          
          Logger.log('Step ' + ++intInitStepCount, 'trace', 'BaseApp.init');
          this.initEvents();
          
          Logger.log('Step ' + ++intInitStepCount, 'trace', 'BaseApp.init');
          this.cfg = new Config(this);
          
          Logger.log('Step ' + ++intInitStepCount, 'trace', 'BaseApp.init');
          this.initDefaultConfig();
          
          Logger.log('Step ' + ++intInitStepCount, 'trace', 'BaseApp.init');
          //NOTE:  This same test happens after initInteractions, because the ConfigAppliedEvent has no interactions until that method runs.
          if(userConfig){
              this.cfg.applyConfig(userConfig, true);
              //this.ConfigAppliedEvent.fire();  //This should happen later in BaseApp.init.
          }
          
          //Set up Error Panel (which could be necessary for failed XMLHTTP requests)
          Logger.log('Step ' + ++intInitStepCount + ':  Error panel.', 'trace', 'BaseApp.init');
          this.initErrorPanel();
          
          Logger.log('Step ' + ++intInitStepCount, 'trace', 'BaseApp.init');
          this.tabView = new YAHOO.widget.TabView("AnalyzerInterfaceContainer");
          
          Logger.log('Step ' + ++intInitStepCount + ":  TreeView init", 'trace', 'BaseApp.init');
          this.accordion = new YAHOO.widget.TreeView(this.cfg.getProperty("strCategoryTreeElId"));
          this.accordion.maxAnim = 3;
          
          //Initialize the drag-drop targets
          Logger.log('Step ' + ++intInitStepCount, 'trace', 'BaseApp.init');
          var aryBoxes = Dom.getElementsByClassName("varbin", "ul", "dragdroparea");  //Without a root, this takes seconds.
          for(var i=0; i<aryBoxes.length; i++){
              this.objDDBoxes[ aryBoxes[i].id ] = new YAHOO.util.DDTarget(
                aryBoxes[i],
                Dom.hasClass(aryBoxes[i], "active") ? this.NominateDDInteractionGroup(aryBoxes[i].id) : this.NominateDDInteractionGroup("source")
              );
          }
          
          //Create the drop mask for the inactive variables area
          Logger.log('Step ' + ++intInitStepCount + ":  DDMask init", 'trace', 'BaseApp.init');
          var elDDMask = document.createElement("ul");
          Dom.addClass(elDDMask, "dropmask");
          Dom.addClass(elDDMask, "varbin");
          Dom.setStyle(elDDMask, "position", "absolute");
          Dom.setStyle(elDDMask, "display", "none");
          elDDMask.id = "DropMask_made" + (new Date()).valueOf();
          Dom.get("dragdroparea").appendChild(elDDMask);
          this.ddmask = new YAHOO.util.DDTarget(elDDMask, this.NominateDDInteractionGroup("source"));
          
          
          
          //Load metadata needed to view Analyzer
          Logger.log('Step ' + ++intInitStepCount, 'trace', 'BaseApp.init');
          for(var query in this.PreloadMetadataQueriesAndActions){
              if(this.PreloadMetadataQueriesAndActions.hasOwnProperty(query)){
                  this.MetadataTableStore[ query ] = null;
                  this.LoadAndHandleData(query, function(objTransport){
                      var strQuery = objTransport.argument.query;
                      refAnalyzer.MetadataTableStore[ strQuery ] = objTransport;  //Store for later use
                      
                      //Do any actions associated with the just-loaded query
                      refAnalyzer.PreloadMetadataQueriesAndActions[strQuery].call(refAnalyzer);
                      
                      refAnalyzer.MetadataLoadedEvent.fire(refAnalyzer.cfg.getProperty("MetadataTableURL"), strQuery);
                  });
              }
          }
          
          //Set up Variable Description Dialog
          Logger.log('Step ' + ++intInitStepCount + ':  Variable Description dialog.', 'trace', 'BaseApp.init');
          this.initVariableDescriptionPanel();
          
          Logger.log('Step ' + ++intInitStepCount + ':  Drop target metadata.', 'trace', 'BaseApp.init');
          this.initDDTargetMeta();
          //Logger.log("DDTargetMeta:  " + YAHOO.lang.JSON.stringify(this.DDTargetMeta), "debug", "BaseApp.init");
          
          //Initialize context menus
          Logger.log('Step ' + ++intInitStepCount + ':  Context menus.', 'trace', 'BaseApp.init');
          this.initContextMenus();
          
          Logger.log('Step ' + ++intInitStepCount + ':  Initial states of checkboxes.', 'trace', 'BaseApp.init');
          this.initCheckboxStates();
          
          //Initialize I/O
          this.initIO();
          
          //Initialize formatted values object
          Logger.log('Step ' + ++intInitStepCount + ':  Breakout variables data source.', 'trace', 'BaseApp.init');
          this.initFVDataSource();
          
          //Configure the events and component interactions
          Logger.log('Step ' + ++intInitStepCount + ':  Interactions.', 'trace', 'BaseApp.init');
          this.initInteractions();
          
          //NOTE:  This same test happened earlier in .init.  The ConfigAppliedEvent needs to fire after initInteractions is called.
          if(userConfig){
              this.ConfigAppliedEvent.fire();
          }
          
          this.InitEvent.fire(Analyzer.BaseApp);
          
          Logger.log('Finished.', 'trace', 'BaseApp.init');
      }
    };
    
    YAHOO.lang.augmentProto(Analyzer.BaseApp, YAHOO.util.EventProvider);
})();

