/************************************************************
W3C mobileOK Checker
--------------------
Javascript methods to handle user
interaction in a report
 ************************************************************/


/************************************************************
 * Scoping global function that encapsulates the variables
 * and functions that do not need to be visible from an
 * external point of view.
 *
 * The function is run automatically when the file is
 * included.
 ************************************************************/
"use strict";
(function () {
    /************************************************************
     * Pointer to the global context under which this function
     * is executed. Most of the time, "this" should be "window".
     ************************************************************/
    var glob = this;


    /************************************************************
     * The Asynchronous poller polls the server for updates
     * and redirects the user to the resulting page when the
     * server is done processing the initial request.
     *
     * @constructor
     ************************************************************/
    var AsyncPoller = function () {
        /**
         * Poller is running.
         * @type {bool}
         * @private
         */
        this.polling = false;

        
        /**
         * Timer used for auto-refresh.
         * @private
         */
        this.windowTimer = null; 
   };


    /************************************************************
     * Initializes the asynchronous poller.
     *
     * - Sets the flag that tells the server that an asynchronous
     * poller is running.
     * - Starts the polling process if a request needs to be
     * processed.
     *
     * Must be called once when the poller is created.
     ************************************************************/
    AsyncPoller.prototype.init = function () {
        var self = this;

        // Sets the async parameter if possible
	$(".async").each(function() {
	    var enoughSupport = self.newXMLHttpRequest()
		&& glob.setInterval
		&& document.createElement
		&& ((document.importNode && document.documentElement.replaceChild)
		    || document.documentElement.outerHTML);
	    if (enoughSupport) {
		$(this).val("true");
	    }
            else {
                $(this).val("false");
            }
	});

	// Start polling if processing is running
	if ($("#still_running").size() > 0) {
            this.startPolling();
	}
    };


    /************************************************************
     * Creates an instance of an XMLHttpRequest object.
     *
     * @returns {XMLHttpRequest}
     * @private
     ************************************************************/
    AsyncPoller.prototype.newXMLHttpRequest = function () {
        var factories = [
	    function () { return new XMLHttpRequest() },
	    function () { return new ActiveXObject("Msxml2.XMLHTTP") },
	    function () { return new ActiveXObject("Msxml3.XMLHTTP") },
	    function () { return new ActiveXObject("Microsoft.XMLHTTP") },
	];
        var request = false;
	for (var i=0; i< factories.length; i++) {
	    try {
		request = factories[i]();
	    }
	    catch (e) {
		continue;
	    }
	    break;
	}
	return request;
    };


    /************************************************************
     * Sends an asynchronous HTTP request to check the progress
     * status of the task currently being processed on the server.
     *
     * @param {string} url the targeted URL.
     * @param {function} callback the function to call upon success.
     * @param {string} postdata the post data to send along with
     *   the request.
     * @return {bool} true when the request could be sent,
     *   false otherwise.
     * @private
     ************************************************************/
    AsyncPoller.prototype.sendRequest = function (url, callback, postdata) {
	var req = this.newXMLHttpRequest();
	if (!req) {
            return false;
        }

	var method = (postdata) ? "POST" : "GET";
        
	try {
	    req.open(method, url, true);
	    req.setRequestHeader("User-Agent", "W3C-mobileOK/XMLHttp1.0");
	    if (postdata) {
		req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            }

	    // Force text/xml because MSXML implementations prior than 6.0
	    // do not set responseXML when the Content-Type is not "text/xml".
	    req.setRequestHeader("Accept", "text/xml");
            
            // Binds success to callback function
            // TODO: handle errors
            req.onreadystatechange = function () {
		if (req.readyState != 4) {
                    return;
                }
                
                // Call the callback function with the response.
		callback(req);
	    }
            
            // Sends the asynchronous HTTP request and return
	    req.send(postdata);
        }
	catch(e) {
	    return false;
	}
        
	return true;
    };


    /************************************************************
     * Polls the server for progress.
     * 
     * @private
     ************************************************************/
    AsyncPoller.prototype.pollStatus = function () {
        var self = this;
        
        if (!this.polling) {
            this.polling = true;
            this.sendRequest("check?ac=poll&task=" + document.getElementById("task").value,
                             function (req) {
                                 return self.handlePollResponse(req)
                             },
                             null);
        }
    };


    /************************************************************
     * Processes the response sent by the server and updates the
     * document consequently.
     * 
     * @private
     ************************************************************/
    AsyncPoller.prototype.handlePollResponse = function(req) {
        var statuses = null;
        var status = null;
        var tasks = null;
        var task = null;
        var taskid = null;
        var href = null;
        var i = 0;
        
	try {
	    if (req.status != 200) {
		// An error occurred
		this.stopPolling();
		alert("Sorry, the server returned an error.");
	    }
	    else if (!req.responseXML) {
		// Empty or non-XML response received
		this.stopPolling();
		alert("Sorry, the server returned an empty or invalid response.\n");
		alert(req.responseText);
	    }
	    else {
                // Response looks good
		taskid = document.getElementById("task").value;
		tasks = req.responseXML.getElementsByTagName("task");
                
		for (i = 0; i < tasks.length; i++) {
		    if (tasks[i].getAttribute("id") === taskid) {
			task = tasks[i];
			break;
		    }
		}
                
		if (task) {
		    statuses = task.getElementsByTagName("status");
		    if (statuses.length > 0) {
			status = statuses[0].firstChild.nodeValue;
		    }
		}

		if (!status) {
		    // Unknown response format.
		    this.stopPolling();
		    if (task) {
			alert("task found but no status!");
		    }
		    else {
			alert("Sorry, the server returned an unexpected response.");
		    }
		    alert(req.responseText);
		}
		else if (status === "done") {
		    // Done, let's redirect the user to the results page
		    this.stopPolling();
		    href = document.location.href;
		    if (href.indexOf("?") != -1) {
			href = href.substring(0, href.indexOf("?"));
		    }
		    $("#docAddr").removeAttr("disabled");
		    href += "?" + $("input").not("#ac").serialize();
		    $("#docAddr").attr("disabled", "disabled");
		    document.location.href = href;
		}
		else if (status === "error") {
		    // An error occurred
		    this.stopPolling();
		    alert("An internal error occurred. Please try again in a moment.");
		    alert(req.responseText);
		}
	    }
	}
	catch (e) {
	    alert("Sorry, an unexpected error occurred.");
	    alert(e.message);
	    this.stopPolling();
	}

        // Reset polling flag
	this.polling = false;
    };


    /************************************************************
     * Starts polling server every 5 seconds.
     * 
     * @private
     ************************************************************/
    AsyncPoller.prototype.startPolling = function () {
        var self = this;
	this.windowTimer = glob.setInterval(
            function () {
                self.pollStatus();
            },
            5000);
    };

    
    /************************************************************
     * Stops polling server.
     * 
     * @private
     ************************************************************/
    AsyncPoller.prototype.stopPolling = function() {
	if (this.windowTimer) {
	    glob.clearInterval(this.windowTimer);
	}
    };




    /************************************************************
     * Controller for tabulation sections contained in the
     * document.
     *
     * Tabulation sections are identified based on the presence
     * of a "tabBar" class.
     *
     * @constructor
     ************************************************************/
    var TabulationSectionsHandler = function () {
    };


    /************************************************************
     * Hooks up the behavior of the tabulation sections to the
     * handler.
     *
     * @private
     ************************************************************/
    TabulationSectionsHandler.prototype.init = function () {
        var self = this;
        
        $(".tabBar").each(function (sectionIndex) {
            var tabSection = $(this);

            $(":not(select) .item", tabSection).each(function (tabIndex) {
                var tab = $(this);
                tab.click(function () {
                    self.displaySection(sectionIndex, tabIndex);
                });
            });


            $("select", tabSection).change(function (event) {
                var selOption = $("option:selected", $(this));
                var tabIndex = parseInt(selOption.attr("value"), 10);
                self.displaySection(sectionIndex, tabIndex);
            });
        });

        $(".tabBar :not(select) .item.selected").click();
        $(".tabBar select").change();
    };


    /************************************************************
     * Displays the section identified by the given index, and
     * hides all other sections.
     *
     * @param {number} sectionIndex The 0-based index of the tab
     *   section in the document.
     * @param {number} tabIndex The 0-based index of the tab in
     *   the tab section.
     * @private
     ************************************************************/
    TabulationSectionsHandler.prototype.displaySection = function (sectionIndex, tabIndex) {
        $(".tabBody").each(function (i) {
            if (i === sectionIndex) {
                var tabSection = $(this);
                $(".item", tabSection).each(function (k) {
                    var section = $(this);
                    var block = (section.css("display") === "block");
                    
                    if (k === tabIndex) {
                        if (block) {
                            return;
                        }
                        section.fadeIn(200);
                    }
                    else {
                        if (block) {
                            section.fadeOut(100);
                        }
                    }
                });
            }
        });

        $(".tabBar").each(function (i) {
            if (i === sectionIndex) {
               var tabSection = $(this);
                $(".item", tabSection).each(function (k) {
                    if (k === tabIndex) {
                        $(this).addClass("selected");
                    }
                    else {
                        $(this).removeClass("selected");
                    }
                });
            }
        });
    };




    /************************************************************
     * Controller for the expandable sections contained in the
     * document.
     *
     * Expandable sections are identified based on the presence
     * of a specific class that can be precised upon instantiation
     * of the class.
     *
     * @constructor
     ************************************************************/
    var ExpandableSectionsHandler = function (sectionName) {
        /**
         * The class to use to identify the expandable sections.
         * @type {string}
         * @private
         */
        this.sectionName = (sectionName ? sectionName : "expandable");
    };


    /************************************************************
     * Hooks up the behavior of the expandable sections to the
     * handler.
     *
     * @private
     ************************************************************/
    ExpandableSectionsHandler.prototype.init = function () {
        var self = this;

	// Apply the initialize to each 
	$("." + self.sectionName).each(function () {
	    // Expandable sections are closed by default
	    $(this).addClass("closed");
            
            // Flag heading and body with a specific CSS class
            // so that we do not have to use the '>' selector in CSS
            // (which is not supported in IE6).
            var title = $(this).children(".inner").children(".hd");
            var content = $(this).children(".inner").children(".bd");
            title.contents().wrapAll("<div class='expand_title'></div>");
            content.addClass("expand_content");
            
            title.click(function (event) {
                var href = null;
                var expandSection = true;
                if (event.target.nodeName === "A") {
                    href = $(event.target).attr("href");
                    if (href.indexOf("#") !== 0) {
                        expandSection = false;
                    }
                }
                if (expandSection) {
                    event.preventDefault();
                    self.toggle($(this).parents("." + self.sectionName).get(0));
                }
            });
        });
    };


    /************************************************************
     * Toggles the expandable section.
     *
     * @param {DOMNode} oEl The node to toggle
     * @private
     ************************************************************/
    ExpandableSectionsHandler.prototype.toggle = function (oEl) {
	$(oEl).toggleClass("closed");
    };





    /************************************************************
     * Controller for the sortable sections contained in the
     * document.
     *
     * Sortable sections are identified based on the presence
     * of a "sortKeys" class.
     *
     * @constructor
     ************************************************************/
    var SortableSectionsHandler = function () {
    };


    /************************************************************
     * Hooks up the behavior of the sortable sections to the
     * handler.
     *
     * @private
     ************************************************************/
    SortableSectionsHandler.prototype.init = function () {
        this.bind();
        
	// Sorts the selected column
        $(":not(select).sortKeys .selected").click();
        $("select.sortKeys").change();
    };


    /************************************************************
     * Binds or re-binds to the events that trigger the sort.
     *
     * @public
     ************************************************************/
    SortableSectionsHandler.prototype.bind = function () {
        var self = this;

        // Bind the "onclick" event of the column headers
	$(":not(select).sortKeys .sortKey").click(function (event) {
            var sortKey = $(this);
            var currentSortKey = sortKey.parent().find(".sortKey.selected");
            var sortOrder = 1;
            var sortData = sortKey.parents("div:first").find(".sortData");
            var splitReg = new RegExp("\\s+", "g");
            var classes = sortKey.attr("class").split(splitReg);
            var i = 0;
            var sortBy = null;
            var sortImage = null;
            
            for (i = 0; i < classes.length; i += 1) {
                if (classes[i].indexOf("sortBy") === 0) {
                    sortBy = classes[i].substring("sortBy".length).toLowerCase();
                }
            }
            
            if (sortBy) {
                event.preventDefault();
                
                // Don't swap sort order if click is triggered internally
                if (sortKey.hasClass("sortDesc")) {
                    if (event.originalEvent) {
                        sortOrder = 1;
                    }
                    else {
                        sortOrder = -1;
                    }
                }
                else if (sortKey.hasClass("sortAsc")) {
                    if (event.originalEvent) {
                        sortOrder = -1;
                    }
                    else {
                        sortOrder = 1;
                    }
                }
                if (currentSortKey.size() > 0) {
                    currentSortKey
                        .removeClass("selected")
                        .removeClass("sortAsc")
                        .removeClass("sortDesc")
                        .children("img").remove();
                }
                
                sortKey
                    .addClass("selected")
                    .addClass((sortOrder === 1) ? "sortAsc" : "sortDesc")
                    .append("<img src='images/ico-sort-"
                            + ((sortOrder === 1) ? "asc" : "desc")
                            + ".png' alt='' with='16' height='16' />");
                
                // NB: the alternative text is set afterwards to force browsers
                // to display the image first (the text would be displayed
                // for a fraction of seconds if the following was directly
                // included in the markup in the image.
                sortKey.find("img").attr("alt", "currently sorted by "
                                         + ((sortOrder === 1) ? "ascending" : "descending")
                                         + " order");
                
                self.sortData(sortData, sortBy, sortOrder);
            }
        });

        // Bind the "onclick" event of the column headers
	$("select.sortKeys").change(function (event) {
            var sortKey = $("option:selected", $(this));
            var currentSortKey = sortKey.parent().find(".sortKey.selected");
            var sortOrder = -1;
            var sortData = sortKey.parents("div:first").find(".sortData");
            var splitReg = new RegExp("\\s+", "g");
            var classes = sortKey.attr("class").split(splitReg);
            var i = 0;
            var sortBy = null;
            var sortImage = null;
            
            for (i = 0; i < classes.length; i += 1) {
                if (classes[i].indexOf("sortBy") === 0) {
                    sortBy = classes[i].substring("sortBy".length).toLowerCase();
                }
            }
            
            if (sortBy) {
                event.preventDefault();
                
                if (sortKey.hasClass("sortAsc")) {
                    sortOrder = 1;
                }
                if (currentSortKey.size() > 0) {
                    currentSortKey.removeClass("selected");
                }
                
                sortKey.addClass("selected");
                
                self.sortData(sortData, sortBy, sortOrder);
            }
        });
    };

    
    /************************************************************
     * Sorts the data of the sortable section
     *
     * @param {DOMNode} sortData Direct parent of the data to sort
     * @param {string} sortBy Sort key
     * @param {number} sortOrder Sorting order
     *   (1 for ASC, -1 for DESC)
     * @private
     ************************************************************/
    SortableSectionsHandler.prototype.sortData = function (sortData, sortBy, sortOrder) {
        var rows = sortData.children().get();

        // Pre-compute sort keys
        $.each(rows, function(index, row) {
            var sortCell = $(row).find("." + sortBy);
            if (sortCell) {
                if (sortCell.children("*[class^='sort-']").size() === 1) {
                    row.sortKey = sortCell.children("*[class^='sort-']").attr("class").substring("sort-".length);
                }
                else if ((sortCell.children().size() === 1)
                         && (sortCell.children("img").size() === 1)) {
                    row.sortKey = sortCell.children("img").attr("alt");
                }
                else {
                    row.sortKey = sortCell.text();
                }
                row.sortKey = row.sortKey.toLowerCase();
            }
            else {
                row.sortKey = "";
            }
        });
        
        // Sort rows
        rows.sort(function(row1, row2) {
            if (row1.sortKey < row2.sortKey) {
                return (sortOrder === 1) ? -1 : 1;
            }
            else if (row1.sortKey > row2.sortKey) {
                return (sortOrder === 1) ? 1 : -1;
            }
            return 0;
        });
        
        // Update the list
        $.each(rows, function(index, row) {
            sortData.append(row);
            row.sortKey = null;
        });

        this.refreshEvenRows(sortData);
    };

    
    /************************************************************
     * Refreshes even rows in the list to keep alternate styles
     * in order.
     *
     * @param {DOMNode} sortData Sorted data direct parent.
     * @private
     ************************************************************/
    SortableSectionsHandler.prototype.refreshEvenRows = function (sortData) {
        $(sortData).children().removeClass('even');
        $(sortData).children(":even").addClass('even');
    };





    /************************************************************
     * The AppliedMediaTypeHandler controls the media type that
     * is currently being applied, and maintains the flag based
     * on user's actions.
     *
     * @param {function (string)} switchDisplayModeCallback Optional
     *   callback function called when media type is switched to
     *   complete the page with specific media-type dependent
     *   behavior.
     * @constructor
     ************************************************************/
    var AppliedMediaTypeHandler = function (switchDisplayModeCallback) {
        /**
         * Flag that identifies the media type that is currently being applied.
         * This flag is set by checking specific CSS properties.
         * @type {string}
         * @private
         */
        this.appliedMediaType = null;

        /**
         * Callback function called when display mode is switched from
         * one media type to another, so that calling application may
         * add specific media type dependent behavior
         * @type {function (string)}
         * @private
         */
        this.switchDisplayModeCallback = switchDisplayModeCallback;

        /**
         * Timer associated with resize event. A timer is used so that
         * the switch gets made only once when resizing is over.
         * @private
         */
        this.resizeTimer = null;
    };


    /************************************************************
     * Initializes the handler (binds to the resize event to
     * detect changes in applied media when browser supports
     * CSS media queries)
     *
     * @public
     ************************************************************/
    AppliedMediaTypeHandler.prototype.init = function () {
        var self = this;
        
        $(window).bind('resize', function() {	
	    if (self.resizeTimer) {
                clearTimeout(self.resizeTimer);
            }
	    self.resizeTimer = setTimeout(
                function () {
                    self.detectAppliedMedia();
                },
                100);
	});

        // Run the first detection manually
        this.detectAppliedMedia();
    };



    /************************************************************
     * Detects and updates the media type currently applied
     * based on specific CSS properties.
     *
     * @private
     ************************************************************/
    AppliedMediaTypeHandler.prototype.detectAppliedMedia = function () {
        var switchNeeded = (this.appliedMediaType === null);
        var mediaTypeToApply = null;

        if ($(".w3cLogo").css("display") === 'none') {
            switchNeeded = switchNeeded
                || (this.appliedMediaType !== "handheld");
            mediaTypeToApply = "handheld";
        }
        else {
            switchNeeded = switchNeeded
                || (this.appliedMediaType !== "screen");
            mediaTypeToApply = "screen";
        }
        
        if (switchNeeded) {
            this.switchDisplayMode(mediaTypeToApply);
        }
    };


    /************************************************************
     * Switches the display mode to the given media type, and
     * updates the internal flag that follows the applied media
     * type consequently.
     *
     * @param {string} mediaType The media type to switch to.
     * @public
     ************************************************************/
    AppliedMediaTypeHandler.prototype.switchDisplayMode = function (mediaType) {
        // TODO: if "views" menu, update styles

        // Pass the hand on to the callback method if defined
        if (this.switchDisplayModeCallback) {
            this.switchDisplayModeCallback(mediaType);
        }

        // Update the internal flag
        this.appliedMediaType = mediaType;
    }



    /************************************************************
     * The Checker application controls the UI of the W3C
     * mobileOK Checker. It is mainly used to control the
     * behavior of the 
     ************************************************************/
    var CheckerApplication = function () {
        /**
         * Handler that monitors the applied media type and triggers
         * the switch when needed.
         * @type {AppliedMediaTypeHandler}
         * @private
         */
        this.appliedMediaTypeHandler = null;

        /**
         * Asynchronous server poller
         * @type {AsyncPoller}
         * @private
         */
        this.asyncPoller = null;

        /**
         * Handler for the tabulation sections
         * @type {TabulationSectionsHandler}
         * @private
         */
        this.tabulationSectionsHandler = null;

        /**
         * Handler for the expandable sections
         * @type {ExpandableSectionsHandler}
         * @private
         */
        this.expandableSectionsHandler = null;

        /**
         * Handler for the sortable sections
         * @type {SortableSectionsHandler}
         * @private
         */
        this.sortableSectionsHandler = null;
    };



    /************************************************************
     * Initializes the application.
     *
     * In particular, this function adds the "js" class to the
     * body element of the HTML document to activate Javascript
     * CSS styles.
     ************************************************************/
    CheckerApplication.prototype.init = function () {
        var self = this;

        // Activate script-related CSS styles.
        $("body").addClass("js");

        // Activate sections that should only appear when Javascript is on
        $(".jsActivated").removeClass("jsActivated");

        // Prepare the handler that monitors the applied media type
        // This call will in turn complete the report with additional
        // information as required by the applied media type.
        this.appliedMediaTypeHandler = new AppliedMediaTypeHandler(function (mediaType) {
            self.switchDisplayMode(mediaType);
        });
        this.appliedMediaTypeHandler.init();

	// Prepare asynchronous poller
        // (started by the call to init if necessary)
        this.asyncPoller = new AsyncPoller();
        this.asyncPoller.init();
	
        // Prepare tabs behavior
        this.tabulationSectionsHandler = new TabulationSectionsHandler();
        this.tabulationSectionsHandler.init();

        // Prepare expandable sections behavior
        this.expandableSectionsHandler = new ExpandableSectionsHandler();
        this.expandableSectionsHandler.init();

        // Prepare sortable sections behavior
        this.sortableSectionsHandler = new SortableSectionsHandler();
        this.sortableSectionsHandler.init();

        // Prepare report
        this.initReport();
    };


    /************************************************************
     * Completes the report with Javascript-computed elements:
     * - add expand all / collapse all menu, if not on a mobile
     * device
     * - add best practice to the detailed report, if not on a
     * mobile device
     ************************************************************/
    CheckerApplication.prototype.completeReport = function () {
    };



    /************************************************************
     * Prepares the report for display:
     * - expand score, details, why and how subsections
     * - expand global warning sections
     ************************************************************/
    CheckerApplication.prototype.initReport = function () {
        var expandableSectionsHandler = this.expandableSectionsHandler;

        // Expand score, details, why and how subsections by default
        $("#score.expandable,.expandable.why,.expandable.how").filter(".closed").each(function () {
            expandableSectionsHandler.toggle($(this));
        });
        
        // Expand warning sections
        $(".expandable.closed > .inner > .hd.warningSection").each(function () {
            expandableSectionsHandler.toggle($(this).parents(".expandable").get(0));
        });

        $("a[href^='#line-']").click(function (event) {
            // Expand source code section if needed
            $("#source.closed").each(function () {
                expandableSectionsHandler.toggle($(this));
            });
            $("#source-main.closed").each(function () {
                expandableSectionsHandler.toggle($(this));
            });

            // Note we don't prevent default action so that user
            // may now go to the appropriate line number in the source code
        });
    };


    /************************************************************
     * Switches the display mode to the given media type.
     *
     * @param {string} mediaType The media type to switch to.
     * @private
     ************************************************************/
    CheckerApplication.prototype.switchDisplayMode = function (mediaType) {
        var self = this;
        var currSortNode = null;
        var currSortKey = "sortBySeverity";
        var currSortOrder = "sortDesc";
        var currValidationNode = null;
        var currValidationIndex = 0;

        currSortNode = $(".sortKeys .sortKey.selected");
        if (currSortNode.size() === 1) {
            if (currSortNode.hasClass("sortBySeverity")) {
                currSortKey = "sortBySeverity";
            }
            else if (currSortNode.hasClass("sortByCat")) {
                currSortKey = "sortByCat";
            }
            else if (currSortNode.hasClass("sortByDesc")) {
                currSortKey = "sortByDesc";
            }
            else {
                currSortKey = "sortByBp";
            }
            if (currSortNode.hasClass("sortAsc")) {
                currSortOrder = "sortAsc";
            }
        }

        currValidationNode = $("#validationMenu .item.selected[value]");
        if (currValidationNode.size() === 1) {
            currValidationIndex = parseInt(currValidationNode.attr("value"), 10);
        }
        else {
            currValidationNode = $("#validationMenu .item.selected a[href]");
            if (currValidationNode.attr("href") === "#validate_by_input") {
                currValidationIndex = 2;
            }
            else if (currValidationNode.attr("href") === "#validate_by_upload") {
                currValidationIndex = 1;
            }
            else {
                currValidationIndex = 0;
            }
        }

        if (mediaType === 'handheld') {
            // Replace "validation by" choices menu
            $("legend").hide();
            $("#validationMenu").remove();
            $("#fields").before("<div id='validationMenu' class='bar tabBar'>"
                                + "<form action='' method='get'><p>Validate by <select>"
                                + "<option class='item' value='0'>URI</option>"
                                + "<option class='item' value='1'>File Upload</option>"
                                + "<option class='item' value='2'>Direct Input</option>"
                                + "</select>"
                                + "</form>"
                                + "</div>");
            $("#validationMenu .item:eq(" + currValidationIndex + ")")
                .addClass("selected")
                .attr("selected", "selected");
            if (this.tabulationSectionsHandler) {
                this.tabulationSectionsHandler.init();
            }

            // Remove expand menu if it has been generated before
            $(".expand_menu").remove();
        
            
            $("#msgHeader").replaceWith("<form id='msgHeader' class='line msgHeader' action='' method='get'><p>Sort by <select class='sortKeys'>"
                                        + "<option class='sortKey sortBySeverity sortAsc'>Severity (asc.)</option>"
                                        + "<option class='sortKey sortBySeverity sortDesc'>Severity (desc.)</option>"
                                        + "<option class='sortKey sortByCat sortAsc'>Category (asc.)</option>"
                                        + "<option class='sortKey sortByCat sortDesc'>Category (desc.)</option>"
                                        + "<option class='sortKey sortByDesc sortAsc'>Description (asc.)</option>"
                                        + "<option class='sortKey sortByDesc sortDesc'>Description (desc.)</option>"
                                        + "</select></p></form>");
            if (currSortKey) {
                $("#msgHeader .sortKey." + currSortKey + "." + currSortOrder)
                    .addClass("selected")
                    .attr("selected", "selected");
            }
            if (this.sortableSectionsHandler) {
                this.sortableSectionsHandler.init();
            }
        }
        else if (mediaType === 'screen') {
            // Add "validation by" choices menu
            if ($("#fields").size() === 1) {
                $("legend").hide();
                $("#validationMenu").remove();
                $("#fields").before("<div id='validationMenu' class='bar tabBar'>"
                                    + "<ul class='inner'>"
                                    + "<li class='item'><a href='#validate_by_uri' shape='rect'><span>Validate by</span> URI</a></li>"
                                    + "<li class='item'><a href='#validate_by_upload' shape='rect'><span>Validate by</span> File Upload</a></li>"
                                    + "<li class='item'><a href='#validate_by_input' shape='rect'><span>Validate by</span> Direct Input</a></li>"
                                    + "</ul>"
                                    + "</div>");
                $("#validationMenu .item:eq(" + currValidationIndex + ")").addClass("selected");
                if (this.tabulationSectionsHandler) {
                    this.tabulationSectionsHandler.init();
                }
            }

            // Add expand all / collapse all links
	    $("#report").before("<div class=\"expand_menu\">"
			        + "<a id=\"expand_all\" href=\"#expand_all\">Expand all</a>"
			        + " | "
                                + "<a id=\"expand_one\" href=\"#expand_one\">Expand one level</a>"
			        + " | "
                                + "<a id=\"collapse_one\" href=\"#collapse_one\">Collapse one level</a>"
			        + " | "
			        + "<a id=\"collapse_all\" href=\"#details\">Collapse all</a>"
			        + "</div>");
	    $("#expand_all").click(function (event) {
	        event.preventDefault();
                $("#report .expandable")
                    .filter(".closed")
                    .each(function () {
                        self.expandableSectionsHandler.toggle($(this));
                    });
            });
            
            $("#collapse_all").click(function (event) {
	        event.preventDefault();
                $("#report .expandable")
                    .not(".closed")
                    .each(function () {
                        self.expandableSectionsHandler.toggle($(this));
                    });
            });
            
            $("#expand_one").click(function (event) {
                event.preventDefault();
                $("#report .expandable")
                    .filter(".closed")
                    .filter(function (index) {
                        if ($(this).parents(".closed").size() > 0) {
                            return false;
                        }
                        return true;
                    })
                    .each(function () {
                        self.expandableSectionsHandler.toggle($(this));
                    });
            });
            
            $("#collapse_one").click(function (event) {
	        event.preventDefault();
                $("#report .expandable")
                    .not(".closed")
                    .filter(function (index) {
                        if ($(".expandable:not(.closed)", this).size() > 0) {
                            return false;
                        }
                        return true;
                    })
                    .each(function () {
                        self.expandableSectionsHandler.toggle($(this));
                    });
            });

            $("#msgHeader").replaceWith("<ul id='msgHeader' class='line msgHeader sortKeys'>"
                                        + "<li id='sortBySeverity' class='unit min1of10 sortKey sortBySeverity sortDesc'><a href='#sortBySeverity'><abbr title='Severity'>Sev.</abbr></a></li>"
                                        + "<li id='sortByCat' class='unit min1of10 sortKey sortByCat'><a href='#sortByCat'><abbr title='Category'>Cat.</abbr></a></li>"
                                        + "<li id='sortByDesc' class='unit min7of10 sortKey sortByDesc'><a href='#sortByDesc'>Description</a></li>"
                                        + "<li id='sortByBp' class='unit min1of10 lastUnit sortKey sortByBp'><a href='#sortByBp'>Best Practice</a></li>"
                                        + "</ul>");
            if (currSortKey) {
                $("#msgHeader .sortKey." + currSortKey)
                    .removeClass("sortAsc")
                    .removeClass("sortDesc")
                    .addClass(currSortOrder)
                    .addClass("selected");
            }

            if (this.sortableSectionsHandler) {
                this.sortableSectionsHandler.init();
            }
        }
        else {
            // TODO: handle print
        }
    };



    /************************************************************
     * Export the class to the external world.
     ************************************************************/
    glob.org = glob.org || {};
    glob.org.w3c = glob.org.w3c || {};
    glob.org.w3c.mwi = glob.org.w3c.mwi || {};
    glob.org.w3c.mwi.mobileok = glob.org.w3c.mobileok || {};
    glob.org.w3c.mwi.mobileok.CheckerApplication = CheckerApplication;


    /************************************************************
     * End of the scoping function (note the global context needs
     * to be provided explicitly because we're on strict mode.
     ************************************************************/
}).call(this);



$(document).ready(function () {
    var checkerApplication = new org.w3c.mwi.mobileok.CheckerApplication();
    checkerApplication.init();
});

