YAHOO.namespace("com.askhys");
(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;
    }
    
    YAHOO.com.askhys.AnalyzerApp = function(userConfig){
        this.init(userConfig);
    };
    
    var
      Dom = YAHOO.util.Dom,
      Event = YAHOO.util.Event,
      CustomEvent = YAHOO.util.CustomEvent,
      Analyzer = YAHOO.com.lgan.Analyzer,
      AnalyzerApp = YAHOO.com.askhys.AnalyzerApp,
      Logger = YAHOO.com.lgan.Logger || YAHOO
    ;
    
    var EVENT_TYPES = {
    };
    
    var DEFAULT_CONFIG = {
      SURVEYYEAR_INPUT:{
        key: "elSurveyYear",
        value: "cboYear"
      }
    };
    
    YAHOO.lang.extend(
      AnalyzerApp,
      YAHOO.com.lgan.Analyzer.BaseApp,
      {
        /**
          Loads everything from the DEFAULT_CONFIG object.  Appends these configurations to the ones defined in the superclass.
        */
        initDefaultConfig: function(){
            Logger.log('Called.', 'trace', 'AnalyzerApp.initDefaultConfig');
            
            AnalyzerApp.superclass.initDefaultConfig.call(this);
            
            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 + '.', 'trace', 'AnalyzerApp.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', 'AnalyzerApp.initDefaultConfig');
        },
        /* */
        
        /**
          Overrides superclass method.
        */
        onDataSourcesLoaded: function(){
            Logger.log("Called.","trace","AnalyzerApp.onDataSourcesLoaded");
            var objEnvelope = this.MetadataTableStore["ds=datasource"].responseJSON;
            var aryYears = [];
            for(var i = parseInt(objEnvelope.data[0].MinFromYear, 10), iMax = parseInt(objEnvelope.data[0].MaxFromYear, 10); i<=iMax; i+=2){
                aryYears.push(i);
            }
            aryYears.reverse();
            var elTimeBound_FY = Dom.get(this.cfg.getProperty("elTimeBound_FY"));
            var elTimeBound_TY = Dom.get(this.cfg.getProperty("elTimeBound_TY"));
            var elSelYear = Dom.get(this.cfg.getProperty("elSurveyYear"));
            
            if(!elTimeBound_FY){Logger.log("Couldn't find elTimeBound_FY.","warning","AnalyzerApp.onDataSourcesLoaded")} else {elTimeBound_FY.innerHTML = aryYears[0];}
            if(!elTimeBound_TY){Logger.log("Couldn't find elTimeBound_TY.","warning","AnalyzerApp.onDataSourcesLoaded")} else {elTimeBound_TY.innerHTML = aryYears[aryYears.length-1];}
            
            if(!elSelYear){
                Logger.log("Couldn't find elSel_FY.","warning","AnalyzerApp.onDataSourcesLoaded");
            } else {
                elSelYear.options.length = 0;
                for(var j=0; j<aryYears.length; j++){
                    elSelYear.options[elSelYear.options.length] = new Option(
                      aryYears[j],
                      aryYears[j],
                      false,
                      j==0  //"To Year" should have last year selected by default (last year is latest, at top of list).
                    );
                }
            }
            
            //Notify that the dates have changed
            this.DateChangedEvent.fire();
            
            Logger.log("Finished.","trace","AnalyzerApp.onDataSourcesLoaded");
        },
        
        initVarDescriptionDataSource: function(){
          this.ydsVarDescriptions = new YAHOO.util.DataSource(
            "/var/fetchvardesc.asp?",
            {
              maxCacheEntries:256,  //We won't need this many
              responseType: YAHOO.util.DataSource.TYPE_JSON,
              responseSchema: {
                resultsList: "data",
                fields: ["VarName", "VarNotes", "Description_HTML", "Description_RAW", "Year"],  //Recall that these are the only fields passed forward from the raw response.
                metaFields: {
                  replyCode: "replyCode",
                  replyText: "replyText"
                }
              }
            }
          );
        },
        
        /**
          @property ydsVarDescriptions
          @type {YAHOO.util.DataSource}
        */
        ydsVarDescriptions: null,
        
        /**
          Displays the Description panel by requesting the long description from the server, using <code>this.ydsVarDescriptions</code>.  Overrides parent method.
          @param {Analyzer.DDProxy} p_anavar
        */
        ShowDescription: function(p_anavar){
            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", "AnalyzerApp.ShowDescription._xhrSuccess");
                } else {
                    if(!this.VariableDescriptionPanel){
                        Logger.log("No VariableDescriptionPanel available.", "debug", "AnalyzerApp.ShowDescription._xhrSuccess");
                    } else {
                        //Set body
                        var rs = oResponse.results, aryHTML = [];
                        
                        //Most descriptions are the same across years.  Build an array of year-list/description pairs.
                        var aryPairs = [];
                        for(var i=0, blnMatched; i<rs.length; i++){
                            blnMatched = false;
                            for(var j=0; j<aryPairs.length; j++){
                                if(rs[i].Description_HTML == aryPairs[j].description){
                                    //Matching description - append a year, log the notes
                                    aryPairs[j].years.push(rs[i].Year);
                                    if(rs[i].VarNotes) aryPairs[j].notes[ rs[i].Year+"" ] = rs[i].VarNotes;
                                    blnMatched = true;
                                    break;
                                }
                            }
                            if(!blnMatched){
                                aryPairs.push({
                                  years: [rs[i].Year],
                                  description: rs[i].Description_HTML,
                                  notes: {}
                                });
                                if(rs[i].VarNotes) aryPairs[j].notes[ rs[i].Year+"" ] = rs[i].VarNotes;
                            }
                        }
                        Logger.log(YAHOO.lang.JSON.stringify(aryPairs), "debug", "AnalyzerApp.ShowDescription._xhrSuccess");
                        
                        function _EnglishList(aryVals){
                            var retval = [];
                            for(var i=0; i<aryVals.length; i++){
                                retval.push(aryVals[i]);
                                switch(aryVals.length - i){
                                    case 1:
                                        //Nop.
                                    break;
                                    case 2:
                                        retval.push(" and ");
                                    break;
                                    default:
                                        retval.push(", ");
                                    break;
                                }
                            }
                            return retval.join("");
                        }
                        
                        for(var i=0, aryNotes; i<aryPairs.length; i++){
                            aryNotes = [];
                            for(var key in aryPairs[i].notes){if(YAHOO.lang.hasOwnProperty(aryPairs[i].notes, key)){
                                aryNotes.push("Note for " + key + ":  " + aryPairs[i].notes[key]);
                            }}
                            aryHTML.push([
                              '<dt>Description for ' + _EnglishList(aryPairs[i].years) + '</dt>',
                              '<dd>',
                              '  <p>',
                              aryNotes.join("<br />"),
                              '  </p>',
                              //rs[i].VarNotes ? '<div class="Notes">Note:  <blockquote>' + rs[i].VarNotes + '</blockquote></div>' : '',
                              aryPairs[i].description,
                              '</dd>'
                            ].join("\n"));
                        }
                        this.VariableDescriptionPanel.setBody(["<div>", aryHTML.join("\n"), "</div>"].join("\n"));
                        
                        var strTitle = "Analysis variable description";
                        var meta = this.IndexedMetadata["breakout_variables"][ oRequest ];
                        if(!meta){
                            Logger.log("Unable to find indexed breakout-variable metadata for ''", "error", "AnalyzerApp.ShowDescription._xhrSuccess");
                        } else {
                            strTitle = "Description for '" + meta.VarLabel + "'";
                        }
                        this.VariableDescriptionPanel.setHeader(Analyzer.BaseApp.BreakLongTitle(strTitle));
                        
                        this.VariableDescriptionPanel.render(document.body);
                        this.VariableDescriptionPanel.show();
                    }
                }
            };
            var _xhrFailure = function(oRequest, oResponse){
                Logger.log("Called.", "trace", "AnalyzerApp.ShowDescription._xhrFailure");
                this.HTTPErrorEvent.fire({
                  "strRequest": oRequest,
                  "oResponse": oResponse
                });
                Logger.log("Finished.", "trace", "AnalyzerApp.ShowDescription._xhrFailure");
            };
            
            this.ydsVarDescriptions.sendRequest(
              "VarID=" + this.ExtractDDDOMVarID(p_anavar.id),
              {
                success: _xhrSuccess,
                failure: _xhrFailure,
                scope: this  //Adjust execution scope
              }  
            );
        },
        
        /**
          Overrides parent method.
          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){
            var _xhrFailure = function(oRequest, oResponse){
                Logger.log("Called.", "trace", "AnalyzerApp.UpdateDescriptionPreview._xhrFailure");
                this.HTTPErrorEvent.fire({
                  "strRequest": oRequest,
                  "oResponse": oResponse
                });
                Logger.log("Finished.", "trace", "AnalyzerApp.UpdateDescriptionPreview._xhrFailure");
            };
            Logger.log("About to request the variable descriptions.", "debug", "AnalyzerApp.UpdateDescriptionPreview");
            //This is the inactive variable list.  Our preview is comprised of, FOR HYS, the remotely-retrieved variable description for this year.
            
            this.ydsVarDescriptions.sendRequest(
              "VarID=" + this.ExtractDDDOMVarID(p_oArgs.baseid),
              {
                scope: this,
                argument: p_oArgs,
                failure: _xhrFailure,
                success: function(oRequest, oResponse, oArgument){
                    Logger.log("Called.", "trace", "AnalyzerApp.UpdateDescriptionPreview._xhrSuccess");
                    
                    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){
                        var elContainer = Dom.get(oArgument.newid);
                        if(!elContainer){
                            Logger.log("Preview not found.", "debug", "AnalyzerApp.UpdateDescriptionPreview._xhrSuccess");
                        } else {
                            var curBaseParent = Dom.get(oArgument.baseid).parentNode;
                            if(!curBaseParent){
                                //This shouldn't happen...
                                Logger.log("Variable to preview has no parent node.", "error", "AnalyzerApp.UpdateDescriptionPreview._xhrSuccess");
                            } else {
                                if(Dom.hasClass(curBaseParent, "active")) {
                                    Logger.log("The variable has moved since the description loaded - aborting and going down the other path.", "debug", "AnalyzerApp.UpdateDescriptionPreview._xhrSuccess");
                                    oArgument.parent = Dom.get(this.cfg.getProperty("ListPreviews")[ curBaseParent.id ]);
                                    this.UpdateActiveVarPreview(oArgument);
                                } else {
                                    var strNewHTML = '[' + oArgument.metaBV.VarName + ']';
                                    var recCurYear = null;
                                    var elYearIn = Dom.get(this.cfg.getProperty("elSurveyYear"));
                                    if(!elYearIn){
                                        Logger.log("Year input not found.  Unable to pick a year's description.", "error", "AnalyzerApp.UpdateDescriptionPreview._xhrSuccess");
                                    } else {
                                        var iCurYear = parseInt(elYearIn.value, 10);
                                        if(!YAHOO.lang.isNumber(iCurYear)){
                                            Logger.log("The year value was not interpretable as a number:  '" + elYearIn.value + "'", "error", "AnalyzerApp.UpdateDescriptionPreview._xhrSuccess");
                                        } else {
                                            for(var i=0; i<oResponse.results.length; i++){
                                                if(oResponse.results[i].Year == iCurYear){
                                                    recCurYear = oResponse.results[i];
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                    if(!recCurYear){
                                        Logger.log("Current year has no description for this variable.", "warning", "AnalyzerApp.UpdateDescriptionPreview._xhrSuccess");
                                        strNewHTML = '<p class="mini-header">' + strNewHTML + '</p>';
                                        strNewHTML += '<p class="question">This variable has no description in this year.</p>'
                                    } else {
                                        //TODO:  Are there multiple formats available?
                                        
                                        strNewHTML += " Survey question and responses for " + recCurYear.Year + ":";
                                        strNewHTML = '<p class="mini-header">' + strNewHTML + '</p>';
                                        strNewHTML += recCurYear.Description_HTML;
                                    }
                                    elContainer.innerHTML = strNewHTML;
                                    
                                    
                                    this.PreviewRenderedEvent.fire(oArgument.baseid);
                                }
                            }
                        }
                    }
                    Logger.log("Finished.", "trace", "AnalyzerApp.UpdateDescriptionPreview._xhrSuccess");
                }
              }  
            );
            Logger.log("Sent the variable descriptions request.", "debug", "AnalyzerApp.UpdateDescriptionPreview");
        },

        /**
          Adds additional interactions not found in the superclass.
        */
        initInteractions: function(){
            AnalyzerApp.superclass.initInteractions.call(this);
            
            //There aren't reflections in the HYS Analyzer.
            this.DateChangedEvent.unsubscribe(this.ReflectDate);
            
            //Topic updates when the grade or year is changed.
            Event.addListener(this.cfg.getProperty("elSurveyYear"), "change", function(p_e){
                this.UpdateTopicDropdown();
            }, this, true);
            Event.addListener("cboGrade", "change", function(p_e){
                Logger.log("Comparing grades for primary/secondary variable swap.", "trace", "AnalyzerApp.cboGrade.onChange");
                var elGrade = Dom.get("cboGrade");
                if(!elGrade){
                    Logger.log("Expected to find 'cboGrade'.", "error", "AnalyzerApp.cboGrade.onChange");
                } else {
                    //Call the topic update (resetting form) iff the new grade category is different from the old grade category.
                    var objPrimarySelector = {
                      "6": true,
                      "8": false,
                      "10": false,
                      "12": false
                    };
                    Logger.log("this.LastRememberedGrade: " + this.LastRememberedGrade + " + (" + typeof(this.LastRememberedGrade) + "); elGrade.value: " + elGrade.value + " (" + typeof(elGrade.value) + ").", "debug", "AnalyzerApp.cboGrade.onChange");
                    if(objPrimarySelector[this.LastRememberedGrade] == objPrimarySelector[elGrade.value]){
                        //Nop
                    } else {
                        this.UpdateTopicDropdown();
                    }
                    
                    //Reset state
                    this.initLastRememberedGrade();
                }
                Logger.log("Compared grades for primary/secondary variable swap.", "trace", "AnalyzerApp.cboGrade.onChange");
            }, this, true);
            
            //Enforce selection order
            this.DDListChangedEvent.subscribe(this.EnforceDropOrder, this, true);
        },
        
        /**
          State memory, compared to value of Grade dropdown when the browser notes a change.
          @property LastRememberedGrade
        */
        LastRememberedGrade: null,
        
        /**
          Initialization and re-initialization method for the property LastRememberedGrade.
        */
        initLastRememberedGrade: function(){
            var elGrade = Dom.get("cboGrade");
            if(!elGrade){
                Logger.log("Expected to find 'cboGrade'.", "error", "AnalyzerApp.initLastRememberedGrade");
            } else {
                this.LastRememberedGrade = elGrade.value;
            }
        },
        
        /**
          Adds additional events not found in the superclass.
        */
        initEvents: function(){
            AnalyzerApp.superclass.initEvents.call(this);
        },
        
        /**
          Overrides parent's method.  Sets hidden input for analysis topic, based on grade and year.
          Currently assumes that there is only one analysis topic (cross-tab), with variants for grades and years.
        */
        UpdateTopicDropdown: function(){
            Logger.log("Called.", "trace", "AnalyzerApp.UpdateTopicDropdown");
            var objEnvelope = this.MetadataTableStore["ds=analtopic"].responseJSON;
            
            var elInput = Dom.get(this.cfg.getProperty("elTopicInput"));
            if(!elInput){
                Logger.log("Unable to find input looking for '" + this.cfg.getProperty("elTopicInput") + "'.", "error", "AnalyzerApp.UpdateTopicDropdown");
            } else {
                var elInputYear = Dom.get(this.cfg.getProperty("elSurveyYear"));
                if(!elInputYear){
                    Logger.log("Unable to find input looking for '" + this.cfg.getProperty("elSurveyYear") + "'.", "error", "AnalyzerApp.UpdateTopicDropdown");
                } else {
                    var elInputGrade = Dom.get("cboGrade");
                    if(!elInputGrade){
                        Logger.log("Unable to find input looking for 'cboGrade'.", "error", "AnalyzerApp.UpdateTopicDropdown");
                    } else {
                        var strYear = elInputYear.value + "";
                        var strPrisec = (elInputGrade.value == "6") ? "pri" : "sec";
                        
                        //This next part assumes we're only using one analysis topic (with grade and year variants).
                        //Would this be better handled by indexing the metadata first?  Maybe later when there are more topics.
                        for(var i=0, imax=objEnvelope.data.length, strTName; i<imax; i++){
                            strTName = objEnvelope.data[i].TopicName;
                            if(strTName == ["xtab", strPrisec, strYear].join("_")){
                                elInput.value = strTName;
                                break;
                            }
                        }
                        Logger.log("Set analysis topic to '" + elInput.value + "'", "debug", "AnalyzerApp.UpdateTopicDropdown");
                    }
                }
                
                this.TopicChangedEvent.fire();
            }
            Logger.log("Finished.", "trace", "AnalyzerApp.UpdateTopicDropdown");
        },
        
        /**
          Ensures that variables are dropped in the order Column, Row, Other.  (This doesn't handle variables being removed somewhere in that process, though.)
        */
        EnforceDropOrder: function(){
            Logger.log("Called.", "trace", "AnalyzerApp.EnforceDropOrder");
            var outtype = GetRadioVal("optOutputType");
            
            var elFirst, elSecond;
            switch(outtype){
                case "datatable":
                    elFirst = Dom.get("ulDDRows");
                    elSecond = Dom.get("ulDDColumns");
                break;
                case "datachart":
                    elFirst = Dom.get("ulDDAxis");
                    elSecond = Dom.get("ulDDGroups");
                break;
                default:
                    Logger.log("Unexpected value for outtype:  '" + outtype + "'.", "error", "AnalyzerApp.EnforceDropOrder");
                break;
            }
            if(!(elFirst && elSecond)){
                Logger.log("Elements not found.  Nop.", "error", "AnalyzerApp.EnforceDropOrder");
                Logger.log([elFirst, elSecond], "debug", "AnalyzerApp.EnforceDropOrder");
            } else {
                var blnAllowSecond = elFirst.childNodes.length > 0;
                var blnAllowMisc = blnAllowSecond && elSecond.childNodes.length > 0; //Short-circuits (doesn't run second test if !blnAllowRow)
                
                var objToggleAssocs = this.cfg.getProperty("DragDrop_Toggle_Associations");
                if(!objToggleAssocs){
                    Logger.log("Error loading the drag-drop toggle associations.", "error", "AnalyzerApp.EnforceDropOrder");
                } else {
                    //Filters and tables shouldn't be able to turn on if there isn't a second var in place.
                    var elToggle_Tables =  Dom.get(objToggleAssocs["ulDDTables"]);
                    var elToggle_Filters = Dom.get(objToggleAssocs["ulDDFilters"]);
                    
                    if(!(elToggle_Tables && elToggle_Filters)){
                        Logger.log("Unable to find filter and/or table toggles.", "error", "AnalyzerApp.EnforceDropOrder");
                    } else {
                        var blnAllowSubsetting = this.cfg.getProperty("AllowSubsetting");
                        
                        //Enable or disable checks as necessary
                        elToggle_Tables.disabled  = !blnAllowMisc;
                        elToggle_Filters.disabled = !(blnAllowMisc && blnAllowSubsetting);
                        
                        var _fClassify = function(p_aryEls, p_bAllow){
                            //Maybe style the interaction lists as disabled and in invalid states
                            for(var i=0, aryTargets = Dom.get(p_aryEls); i<aryTargets.length; i++){
                                if(p_bAllow){
                                    Dom.removeClass(aryTargets[i], "disabled");
                                    Dom.removeClass(aryTargets[i], "invalid");
                                } else {
                                    Dom.addClass(aryTargets[i], "disabled");
                                    if(aryTargets[i].childNodes.length > 0){
                                        Dom.addClass(aryTargets[i], "invalid");
                                    } else {
                                        Dom.removeClass(aryTargets[i], "invalid");
                                    }
                                }
                            }
                        };
                        
                        _fClassify(["ulDDColumns","ulDDGroups"], blnAllowSecond);
                        _fClassify(["ulDDTables"], blnAllowMisc);
                        _fClassify(["ulDDFilters"], blnAllowMisc && blnAllowSubsetting);
                        
                        
                        //Maybe style the extra lists as disabled
                        _fClassOp = blnAllowMisc ? Dom.removeClass : Dom.addClass ;
                        _fClassOp(["ulDDTables","ulDDFilters"], "disabled");
                        
                        //Update drop targets' targetability
                        var rules = {
                          enabled:{
                            "ulDDRows": outtype == "datatable",
                            "ulDDColumns": blnAllowSecond && outtype == "datatable",
                            "ulDDAxis": outtype == "datachart",
                            "ulDDGroups":blnAllowSecond && outtype == "datachart",
                            "ulDDTables":  blnAllowMisc && !!(Dom.get(objToggleAssocs["ulDDTables"]).checked),
                            "ulDDFilters": blnAllowSubsetting && blnAllowMisc && !!(Dom.get(objToggleAssocs["ulDDFilters"]).checked)
                          },
                          forcible:{
                            "ulDDTables":blnAllowMisc,
                            "ulDDFilters":blnAllowSubsetting && blnAllowMisc
                          }
                        };
                        
                        //Logger.log(YAHOO.lang.dump(rules), "debug", "AnalyzerApp.EnforceDropOrder");
                        this.initDDTargetMeta(rules);
                    }
                }
            }
            Logger.log("Finished.", "trace", "AnalyzerApp.EnforceDropOrder");
        },
        
        init: function(userConfig){
            //One metadata revision - HYS has no SubTopics, so don't bother fetching it.
            delete this.PreloadMetadataQueriesAndActions["ds=subtopic"];
            
            //Only supply userConfig to the lowest subclass - some configuration options aren't available in superclasses.
            AnalyzerApp.superclass.init.call(this);
            
            if(userConfig){
                this.cfg.applyConfig(userConfig, true);
                this.ConfigAppliedEvent.fire();
            }
            
            this.initVarDescriptionDataSource();
            
            this.initLastRememberedGrade();
            
            //Initialize new query rules
            delete this.SubmissionRules["Dates make sense"];  //Single-date input in this Analyzer
            this.EnforceDropOrder();
            this.SubmissionRules["Drop order is valid"] = function(p_bLoud){
                var retval = "Drop-order validation check did not finish.";
                
                var outtype = GetRadioVal("optOutputType");
                
                var elFirst, elSecond;
                switch(outtype){
                    case "datatable":
                        elFirst = Dom.get("ulDDRows");
                        elSecond = Dom.get("ulDDColumns");
                    break;
                    case "datachart":
                        elFirst = Dom.get("ulDDAxis");
                        elSecond = Dom.get("ulDDGroups");
                    break;
                    default:
                        Logger.log("Unexpected value for outtype:  '" + outtype + "'.", "error", "AnalyzerApp.SubmissionRule");
                    break;
                }
                
                var
                  elTables = Dom.get("ulDDTables"),
                  elFilters = Dom.get("ulDDFilters")
                ;
                
                if(!(elFirst && elSecond && elTables && elFilters)){
                    Logger.log("Not all elements were found.  Nop.", "error", "AnalyzerApp.SubmissionRule");
                    Logger.log([elFirst, elSecond, elTables, elFilters], "debug", "AnalyzerApp.SubmissionRule");
                } else {
                    var iChildTally_Primary = elFirst.childNodes.length;
                    var iChildTally_Secondary = elSecond.childNodes.length;
                    var iChildTally_Misc = elTables.childNodes.length + elFilters.childNodes.length;
                    
                    var blnFailed = (iChildTally_Secondary > 0 && iChildTally_Primary == 0) || (iChildTally_Misc > 0 && iChildTally_Secondary == 0);
                    
                    if(blnFailed){
                        retval = "There must be a row variable to use a column variable, and a column variable to use any additional variables.";
                    } else {
                        retval = "Ok";
                    }
                }
                return retval;
            };
        }
      }
    );
})();

//Initialization left to the analyzer ASP page due to initial configuration requirements.

