

Script.namespace("com.civicscience.content");


com.civicscience.content.IContentRequest = new Interface();
com.civicscience.content.IContentRequest.method("abort");
com.civicscience.content.IContentRequest.method("getResponseHTML");
com.civicscience.content.IContentRequest.method("getURL");
com.civicscience.content.IContentRequest.method("send");

com.civicscience.content.IContentRequestHandler = new Interface();
com.civicscience.content.IContentRequestHandler.method("receivedResponse");
com.civicscience.content.IContentRequestHandler.method("abortedRequest");


// ajax based content request
com.civicscience.content.AjaxContentRequest = function(method, url) {

    var self = this;
    
    // getters for immutable request properties
    this.getMethod = function() { return method; };
    this.getURL = function() { return url; };
    this.isAsynchronous = function() { return isAsynchronous; };
    
    // prepare the private request object
    var request = AjaxUtil.newRequest();
    if (!request) {
        // not supported
        throw new Error("No supported XMLHttpRequest implementation.");
    }
    request.open(method, url, true);
    
    // request headers
    this.setRequestHeader = function(name, value) {
        request.setRequestHeader(name, value);
    };
    
    // private request body with getter and setter
    var body = null;
    this.getBody = function() { return body; };
    this.setBody = function(b) { body = b; };
    
    // reveived DOM content
    var html = null;
    this.getResponseHTML = function() {
        if (html == null) {
            throw new Error("ContentRequest not yet received.");
        }
        return html;
    };
    
    // private request state properties
    var aborted = false;
    
    // content request handler
    var requestHandler = null;
    
    // abort the request
    this.abort = function() {
        aborted = true;
        if (requestHandler != null) {
            request.abort();
            requestHandler.abortedRequest.apply(requestHandler, [self]);
        }
    };
    
    // send the request
    this.send = function(handler) {
        if (requestHandler != null) {
            throw new Error("ContentRequest has already been sent.");
        }
        com.civicscience.content.IContentRequestHandler.assert(handler);
        requestHandler = handler;
        if (aborted) {
            // already aborted
            handler.abortedRequest.apply(handler, [self]);
        } else {
            // send request
            request.onreadystatechange = function() {
                if (request.readyState == 4 && !aborted) {
                    html = request.responseText;
                    handler.receivedResponse.apply(handler, [self]);
                }
            };
            request.send(body);
        }
    };
    
}; // com.civicscience.content.AjaxContentRequest


com.civicscience.content.IframeFormContentRequest = function(form) {
    
    var construct = com.civicscience.content.IframeFormContentRequest;
    var self = this;
    
    // use one-up counter for iframe name
    if (construct.ifcounter == undefined) {
        construct.ifcounter = 0;
    }
    var nextIframeName = function() {
        return "com.civicscience.content.IframeFormContentRequest_target_" + construct.ifcounter++;
    };
    
    // form URL
    var url = form.action;
    this.getURL = function() {
        return url;
    };
    
    // loaded content
    var html = null;
    this.getResponseHTML = function() {
        if (html == null) {
            throw new Error("ContentRequest has not been loaded.");
        }
        return html;
    };
    
    // iff the request was aborted
    var aborted = false;

    // copy the original form in case its ruined before sending
    var fclone = form.cloneNode(true);
    fclone.name = "";
    fclone.id = "";
    fclone.style.display = "none";
    fclone.onsubmit = null;
    
    // IE doesn't clone file input control values so swap between the original
    // and the reference copy
    var inputs = form.getElementsByTagName("INPUT");
    var i;
    for (i = 0; i < inputs.length; i++) {
        var inp = inputs[i];
        if (inp.type == "file") {
            var cnp = fclone[inp.name];
            if (cnp != null && cnp.value != inp.value) {
                DOMUtil.swap(cnp, inp);
            }
        }
    }
    
    // target form submission to a hidden iframe
    var name = nextIframeName();
    try {
        // IE7 hackiest hack that ever hacked
        iframe = document.createElement("<iframe name=\"" + name + "\">");
    } catch (e) {
        iframe = document.createElement("IFRAME");
    }
    iframe.name = name;
    iframe.style.display = "none";
    fclone.target = name;
    
    var reqHandler = null;

    // send the request
    this.send = function(handler) {
        if (reqHandler != null) {
            throw new Error("ContentRequest has already been sent.");
        }
        com.civicscience.content.IContentRequestHandler.assert(handler);
        reqHandler = handler;
        if (aborted) {
            // already aborted
            handler.abortedRequest.apply(handler, [self]);
        } else {
            var onload = function() {
                var location = iframe.location;
                if (location == null) {
                    // needed for safari, but might work for others too...
                    location = iframe.contentWindow.location;
                }
                if (!aborted && location != "" && location != "about:blank") {
                    var ifdoc = iframe.contentDocument;
                    if (ifdoc == null) {
                        ifdoc = iframe.contentWindow.document;
                    }
                    var ifroot = ifdoc.documentElement;
                    var ifbody = ifroot.getElementsByTagName("BODY")[0] || ifroot;
                    html = ifbody.innerHTML;
                    fclone.parentNode.removeChild(fclone);
                    setTimeout(function() {
                        iframe.parentNode.removeChild(iframe);
                    }, 0);
                    handler.receivedResponse.apply(handler, [self]);
                }
            };
            Events.addListener(iframe, "load", onload);
            // submit the form
            var body = document.getElementsByTagName("BODY")[0];
            body.appendChild(fclone);
            body.appendChild(iframe);
            fclone.submit();
        }
    };

    // cancel the request
    this.abort = function() {
        aborted = true;
        if (reqHandler != null) {
            fclone.parentNode.removeChild(fclone);
            reqHandler.abortedRequest.apply(reqHandler, [self]);
        }
    };
    
}; // com.civicscience.IframeFormContentRequest


// Loads HTML string content into a content loader.
// The fact that this request implementation returns null for getURL() may make
// it incompatible with menus and history which listen for URL changes at the
// top-level #page_content content loader. I haven't tried it...
com.civicscience.content.HtmlContentRequest = function(html) {
    this.getURL = function() {
        return null;
    };
    this.getResponseHTML = function() {
        return html;
    };
    this.send = function(handler) {
        handler.receivedResponse.apply(handler, [this]);
    };
    this.abort = function() {
    };
}; // com.civicscience.content.HtmlContentRequest


// *deprecated* synonym for com.civicscience.content.AjaxContentRequest
ContentRequest = function(method, url) {
    com.civicscience.content.AjaxContentRequest.apply(this, [method, url]);
}; // ContentRequest


// a ContentLoadingMessage instance represents lazy-requested loading
// message content
function ContentLoadingMessage(url) {

    var self = this;
    var proxyNodes = [];
    var messageNodes = null;
    var messageRequest = null;
    
    // special case null URL means blank loading message
    if (!url) {
        messageNodes = [];
    }
    
    // handle receiving lazy-loaded message content
    var receiveMessage = function() {
        if (messageRequest.readyState == 4) {
            messageNodes = [];
            if (messageRequest.status == 200) {
                // create nodes from the message HTML
                var div = document.createElement("div");
                div.innerHTML = messageRequest.responseText;
                while (div.firstChild) {
                    messageNodes.push(div.removeChild(div.firstChild));
                }
                messageRequest = null;
            }
            // replace any proxy nodes with the message content
            for (var i = 0; i < proxyNodes.length; i++) {
                var parent = proxyNodes[i].parentNode;
                if (parent) {
                    // insert the message content
                    var j = messageNodes.length;
                    while (j > 0) {
                        parent.insertBefore(messageNodes[--j].cloneNode(true), proxyNodes[i]);
                    }
                    // delete the proxy node
                    parent.removeChild(proxyNodes[i]);
                }
            }
            proxyNodes = null;
        }
    };
    
    // replaces the children of the supplied element with the loading message
    this.replaceChildrenWithMessage = function(parent) {
        // get rid of existing children
        while (parent.firstChild) {
            parent.removeChild(parent.firstChild);
        }
        if (messageNodes) {
            // insert previously loaded message content
            for (var i = 0; i < messageNodes.length; i++) {
                parent.appendChild(messageNodes[i].cloneNode(true));
            }
        } else {
            // insert a proxy node to be replaced with lazy-loaded content
            var proxy = document.createComment("");
            proxyNodes.push(proxy);
            parent.appendChild(proxy);
            if (!messageRequest) {
                // initiate loading the content
                messageRequest = AjaxUtil.newRequest();
                if (messageRequest) {
                    messageRequest.open("GET", url, true);
                    messageRequest.onreadystatechange = function() {
                        receiveMessage.apply(self, []);
                    };
                    messageRequest.send(null);
                }
            }
        }
    };
    
} // ContentLoadingMessage


// new content listener interface
IContentLoaderListener = new Interface();
IContentLoaderListener.method("loadedContent");
IContentLoaderListener.method("unloadedContent");


// top-level content loaders
ContentLoader.rootLoaders = [];

// default loading message URL
ContentLoader.URL_DEFAULT_LOADING_MESSAGE = "/static/loading.html";


// manages loading content for document elements
ContentLoader.prototype = {
        
        
    // element managed by this loader
    element: undefined,
    
    // parent loader
    parentLoader: undefined,
    
    // objects registered to be notified of content events
    contentListeners: undefined,
    
    // objects registered to be notified about descendant loader events
    loaderListeners: undefined,
    
    // collection of active content requests
    activeRequests: null,
    
    // completed content request responsible for the current content
    currentRequest: null,
    
    
    getContentURL: function() {
        if (this.currentRequest) {
            return this.currentRequest.getURL();
        } else {
            return null;
        }
    }, // getContentURL
    
    
    // returns the current completed request or null if no request has been
    // sent or a request is currently in progress.
    getRequest: function() {
        return this.currentRequest;
    }, // getRequest
    
    
    isReady: function() {
        return this.activeRequests.length == 0;
    }, // isReady
    
    
    // load content for the element
    loadContent: function(contentRequest) {
        
        com.civicscience.content.IContentRequest.assert(contentRequest);
        
        // show a message while content loads
        var changeMinHeight = !this.element.style.minHeight;
        if (changeMinHeight) {
            this.element.style.minHeight = this.element.offsetHeight + "px";
        }
        var unloaded = document.createDocumentFragment();
        while (this.element.firstChild != null) {
            unloaded.appendChild(this.element.firstChild);
        }
        this.getLoadingMessage().replaceChildrenWithMessage(this.element);

        var i;
        
        // send notification before unloading the content
        for (i = 0; i < this.contentListeners.length; i++) {
            var f = this.contentListeners[i].unloadedContent;
            if (typeof f == "function") {
                f.apply(this.contentListeners[i], [this]);
            }
        }
        
        // abort active requests
        for (i = 0; i < this.activeRequests.length; i++) {
            this.activeRequests[i].abort();
        }
        
        // send new content request
        this.currentRequest = null;
        this.activeRequests.push(contentRequest);
        var self = this;
        contentRequest.send({
            receivedResponse: function(req) {
                self.receivedRequestedContent(req, changeMinHeight);
            },
            abortedRequest: function(req) {
                self.abortedContentRequest(req);
            }
        });
        
        // NYI bubble unloaded content
        ContentLoader.fireUnloadedContent(this, unloaded);
        
    }, // loadContent
    
    
    // receive content for the element
    receivedRequestedContent: function(contentRequest, clearMinHeight) {

        var i;
        var f;
        
        // HTML response from request
        var html = contentRequest.getResponseHTML();

        // separate script tags from the content
        var regexp = new RegExp("<script[^>]*>(.|\n)*?<\/script>");
        var scripts = [];
        while (true) {
            var match = regexp.exec(html);
            if (match == null) {
                break;
            }
            scripts.push(match[0]);
            html = html.substring(0, match.index) + html.substring(match.index + match[0].length);
        }
        
        // copy stylesheet declarations to head
        (function() {
            var head = document.documentElement.getElementsByTagName("HEAD");
            if (head.length != 0) {
                head = head[0];
                var regexp = new RegExp(/<link([^>]* rel="stylesheet" [^>]*)\/>/i);
                while (true) {
                    // extract a link tag
                    var m = regexp.exec(html);
                    if (m == null) {
                        break;
                    }
                    html = html.substring(0, m.index) + html.substring(m.index + m[0].length);
                    // attach to head
                    var link = m[1];
                    var elt = document.createElement("LINK");
                    elt.rel = "stylesheet";
                    m = link.match(/type="([^"]*)"/);
                    if (m != null) {
                        elt.type = m[1];
                    }
                    m = link.match(/href="([^"]*)"/);
                    if (m != null) {
                        elt.href = m[1];
                    }
                    m = link.match(/media="([^"]*)"/);
                    if (m != null) {
                        elt.med = m[1];
                    }
                    head.appendChild(elt);
                }
            }
        })();
        
        if (clearMinHeight) {
            // unfreeze element height locked in before loading content
            this.element.style.minHeight = "";
        }

        // prepare DOM content
        var nodes = [];
        var temp = document.createElement("div");
        temp.innerHTML = html;
        while (temp.hasChildNodes()) {
            nodes.push(temp.removeChild(temp.firstChild));
        }
        for (i = 0; i < this.contentListeners.length && nodes != null; i++) {
            f = this.contentListeners[i].loadedDOM;
            if (typeof f == "function") {
                if (f.apply(this.contentListeners[i], [this, nodes]) == false) {
                    nodes = null;
                }
            }
        }
        
        // set the element content
        while (this.element.hasChildNodes()) {
            this.element.removeChild(this.element.firstChild);
        }
        if (nodes != null) {
            for (i = 0; i < nodes.length; i++) {
                this.element.appendChild(nodes[i]);
            }
        }
        
        this.currentRequest = contentRequest;
        for (i = 0; i < this.activeRequests.length; i++) {
            if (this.activeRequests[i] === contentRequest) {
                this.activeRequests.splice(i, 1);
                break;
            }
        }
        
        // internal content handling from subsystems
        ContentLoader.initialize(this.element, this);
        FormSubmitter.loadedContent(this);
        
        // execute script tags extracted from the content
        for (i = 0; i < scripts.length; i++) {
            this.executeScriptTag(scripts[i]);
        }
                    
        // send notification of the new content
        for (i = 0; i < this.contentListeners.length; i++) {
            f = this.contentListeners[i].loadedContent;
            if (typeof f == "function") {
                f.apply(this.contentListeners[i], [this]);
            }
        }
        // NYI bubble loaded content
        ContentLoader.fireLoadedContent(this);
                
    }, // receivedRequestedContent
    
    
    abortedContentRequest: function(contentRequest) {
        for (var i = 0; i < this.activeRequests.length; i++) {
            if (this.activeRequests[i] === contentRequest) {
                this.activeRequests.splice(i, 1);
                break;
            }
        }
    }, // abortedContentRequest
    
    
    // subscribe a listener for notification of content events
    addContentListener: function(listener) {
        this.contentListeners.push(listener);
    }, // removeContentListener
    
    
    // unsubscribe a listener for notification of content events
    removeContentListener: function(listener) {
        var i = 0;
        for ( ; i < this.contentListeners.length
                && this.contentListeners[i] !== listener; i++);
        if (i < this.contentListeners.length) {
            this.contentListeners.splice(i, 1);
        }
    }, // removeContentListener
    
    
    // subscribe a listener for notification of descendant loader events
    addLoaderListener: function(listener) {
        this.loaderListeners.push(listener);
        // notify the listener of existing loaders immediately
        var f = listener.addedContentLoader;
        if (typeof f == "function") {
            var loaders = [];
            this.collectDescendantLoaders(this.element, loaders);
            for (var i = 0; i < loaders.length; i++) {
                f.apply(listener, [loaders[i]]);
            }        
        }
    }, // removeLoaderListener
    
    
    // unsubscribe a listener for notification of descendant loader events
    removeLoaderListener: function(listener) {
        var i = 0;
        for ( ; i < this.loaderListeners.length
                && this.loaderListeners[i] !== listener; i++);
        if (i < this.loaderListeners.length) {
            this.loaderListeners.splice(i, 1);
        }
    }, // removeLoaderListener
    
    
    // get the top-level loader for this loader family
    getRootLoader: function() {
        var loader = this;
        while (loader.parentLoader != null) {
            loader = loader.parentLoader;
        }
        return loader;
    }, // getRootLoader
    
    
    // notify listeners that a content loader was added
    dispatchAddedLoader: function(loader) {
        if (this.parentLoader) {
            // notify listeners registered with ancestral loaders first
            this.parentLoader.dispatchAddedLoader(loader);
        }
        // notify this instance's loader listeners
        for (var i = 0; i < this.loaderListeners.length; i++) {
            var f = this.loaderListeners[i].addedContentLoader;
            if (typeof f == "function") {
                f.apply(this.loaderListeners[i], [loader]);
            }
        }
    }, // dispatchAddedLoader
    
    
    // executes the script represented by the supplied <script></script> HTML.
    executeScriptTag: function(scriptTag) {
        // extract script text content and properties
        var match = new RegExp("<script([^>]*)").exec(scriptTag);
        var attributes = match[1];
        match = new RegExp("src=\"([^\"]*)\"").exec(attributes);
        if (match != null) {
            Script.inject(match[1]);
        }
    }, // executeScriptTag
    
    
    // load content for the supplied URL
    loadURL: function(url) {
        this.loadContent(new com.civicscience.content.AjaxContentRequest("GET", url, true));
    }, // loadURL
    
    
    // get loaders for descendant elements
    collectDescendantLoaders: function(element, results) {
        for (var i = 0; i < element.childNodes.length; i++) {
            var child = element.childNodes[i];
            if (child.nodeType == 1) {
                if (child.contentLoader) {
                    results.push(child.contentLoader);
                }
                this.collectDescendantLoaders(child, results);
            }
        }
    } // collectDescentantLoaders
    
        
}; // ContentLoader.prototype


// creates a new content loader instance
function ContentLoader(element, parentLoader) {
    
    if (parentLoader == null) {
        parentLoader = ContentLoader.getElementContentLoader(element);
    }
    
    this.contentListeners = [];
    this.loaderListeners = [];
    this.activeRequests = [];
    
    this.element = element;
    
    element.contentLoader = this;

    // ContentLoadingMessage instance
    var loadingMessage = null;
    switch ("") {
    case "":
        // inherit from parent or use default
        break;
    case "none":
        // suppress loading message
        loadingMessage = new ContentLoadingMessage(null);
        break;
    // NYI url(...) value
    default:
        throw new Error("Unsupported loadingMessage attribute value.");
    }
    this.getLoadingMessage = function() {
        if (loadingMessage != null) {
            return loadingMessage;
        } else if (this.parentLoader) {
            return this.parentLoader.getLoadingMessage();
        } else {
            return ContentLoader.getDefaultLoadingMessage();
        }
    };
    this.setLoadingMessage = function(msg) {
        this.loadingMessage = msg;
    };
    
    if (parentLoader) {
        this.parentLoader = parentLoader;
    } else {
        ContentLoader.rootLoaders.push(this);
    }
    
    if (element.tagName == "FORM") {
        // new content loader is attached to a form
        new FormSubmitter(element);
    }
    
    if (parentLoader) {
        parentLoader.dispatchAddedLoader(this);
    }
    
} // ContentLoader


ContentLoader._defaultLoadingMessage = null;


ContentLoader.getDefaultLoadingMessage = function() {
    if (!this._defaultLoadingMessage) {
        this._defaultLoadingMessage = new ContentLoadingMessage(this.URL_DEFAULT_LOADING_MESSAGE);
    }
    return this._defaultLoadingMessage;   
}; // ContentLoader.getDefaultLoadingMessage


// finds the nearest loader attached to the element or an ancestor
ContentLoader.getElementContentLoader = function(element) {
    while (!element.contentLoader && element.nodeType == 1) {
        element = element.parentNode;
    }
    return element.contentLoader;
}; // getElementContentLoader


// replaces the specified elements children with a loading message
ContentLoader.insertLoadingMessage = function(element) {
    var msg = null;
    var loader = this.getElementContentLoader(element);
    if (loader) {
        msg = loader.getLoadingMessage();
    } else {
        msg = this.getDefaultLoadingMessage();
    }
    msg.replaceChildrenWithMessage(element);
}; // ContentLoader.insertLoadingMessage


// set up content loader instances for the element and its descendants
ContentLoader.initialize = function(element, parentLoader) {
    var loader = null;
    if (!element.contentLoader && DOMUtil.hasClass(element, "content_target")) {
        // prepare a loader for this element
        loader = new ContentLoader(element, parentLoader);
    }
    var initialUrl = null;
    if (loader) {
        initialUrl = DOMUtil.getClassParameter(element, "initial_content_url");
    }
    if (initialUrl) {
        // load initial content
        loader.loadURL(initialUrl);
    } else {
        // recurse on child elements
        for (var i = 0; i < element.childNodes.length; i++) {
            if (element.childNodes[i].nodeType == 1) {
                ContentLoader.initialize(element.childNodes[i], loader);
            }
        }
    }
}; // ContentLoader.initialize


// additional methods with private data
(function() {

    // global content loader listeners
    var listeners = [];
    ContentLoader.addListener = function(l) {
        IContentLoaderListener.assert(l);
        listeners.push(l);
    };
    ContentLoader.removeListener = function(l) {
        var i;
        for (i = 0; i < listeners.length && listeners[i] !== l; i++);
        if (i < listeners.length) {
            listeners.splice(i, 1);
        }
    };
    ContentLoader.fireLoadedContent = function(loader) {
        var i;
        for (i = 0; i < listeners.length; i++) {
            listeners[i].loadedContent(loader);
        }
    };
    ContentLoader.fireUnloadedContent = function(loader, unloaded) {
        var i;
        for (i = 0; i < listeners.length; i++) {
            listeners[i].unloadedContent(loader, unloaded);
        }
    };
    ContentLoader.getRootLoaders = function() {
        var roots = [];
        var stack = [document.documentElement];
        while (stack.length != 0) {
            var elt = stack.pop();
            var cl = elt.contentLoader;
            if (cl) {
                roots.push(cl);
            } else {
                var i;
                var cn = elt.childNodes;
                var n = cn.length;
                for (i = 0; i < n; i++) {
                    var c = cn[i];
                    if (c.nodeType == 1) {
                        stack.push(c);
                    }
                }
            }
        }
        return roots;
    };
    
})();
    

// handles form submission without reloading the entire page
FormSubmitter.prototype = {
        

    // the form to submit
    formElement: undefined,
    
    // user submit handler function
    onSubmit: undefined,
    
    
    // submits the form.
    submit: function() {
        if (!this.isSubmitting()) {
            // check for a file input control
            var useIframe = false;
            var inputs = this.formElement.getElementsByTagName("INPUT");
            var i;
            for (i = 0; i < inputs.length && !useIframe; i++) {
                var inp = inputs[i];
                if (inp.type == "file"
                        && !inp.disabled
                        && inp.value.trim() != "") {
                    // successful file input control requires iframe submission
                    useIframe = true;
                }
            }
            try {
                // mark the form as being submitted
                this._setSubmitting(true);
                // schedule actual validation and submission to allow the
                // UI to update styles on the submitted form
                var self = this;
                var loader = ContentLoader.getElementContentLoader(this.formElement);
                setTimeout(function() {
                    try {
                        if (typeof self.onSubmit != "function" || self.onSubmit.apply(self.formElement, [])) {
                            // request new content
                            var request;
                            if (useIframe) {
                                request = new com.civicscience.content.IframeFormContentRequest(self.formElement);
                            } else {
                                request = new com.civicscience.content.AjaxContentRequest("POST", self.formElement.action);
                                request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                                request.setBody(self.getParameters());
                            }
                            if(loader == undefined){
                            	loader = new ContentLoader(document.getElementById("page_content"), null);
                            }
                            loader.loadContent(request);
                        }
                    } finally {
                        self._setSubmitting(false);
                    }
                }, 0);
            } catch (e) {
                this._setSubmitting(false);
            }
        }
    }, // submit
    

    // prepares application/x-www-form-urlencoded parameter string for
    // submitting the form.
    getParameters: function() {
        var names = [];
        var values = [];
        // load parameters from input elements
        var inputs = this.formElement.getElementsByTagName("input");
        for (var i = 0; i < inputs.length; i++) {
            if (inputs[i].disabled) {
                continue;
            }
            switch (inputs[i].type.toLowerCase()) {
            case "hidden":
            case "text":
            case "password":
                names.push(inputs[i].name);
                values.push(inputs[i].value);
                break;
            case "checkbox":
            case "radio":
                if (inputs[i].checked) {
                    names.push(inputs[i].name);
                    values.push(inputs[i].value);
                }
                break;
            }
        }
        // load parameters from text area elements
        var textAreas = this.formElement.getElementsByTagName("textarea");
        for (i = 0; i < textAreas.length; i++) {
            if (!textAreas[i].disabled) {
                names.push(textAreas[i].name);
                values.push(textAreas[i].value);
            }
        }
        // load parameters from select elements
        var selects = this.formElement.getElementsByTagName("select");
        for (i = 0; i < selects.length; i++) {
            if (selects[i].disabled || selects[i].selectedIndex == -1) {
                // not a successful control
            } else if (selects[i].multiple) {
                // allows multiple selection
                for (var j = 0; j < selects[i].options.length; j++) {
                    if (selects[i].options[j].selected) {
                        names.push(selects[i].name);
                        values.push(selects[i].options[j].value);
                    }
                }
            } else {
                // single selected value
                names.push(selects[i].name);
                values.push(selects[i].options[selects[i].selectedIndex].value);
            }
        }
        // encode the parameters
        var params = "";
        for (i = 0; i < names.length; i++) {
            if (params) {
                params += "&";
            }
            params += encodeURIComponent(names[i]) + "=" + encodeURIComponent(values[i]);
        }
        return params;
    } // getParameters

        
}; // FormSubmitter.prototype


// creates a new FormSubmitter instance for the specified form and target
// container element.
function FormSubmitter(form) {
    
    this.formElement = form;
    
    // intercept form submission
    var self = this;
    if (typeof form.onsubmit == "function") {
        this.onSubmit = form.onsubmit;
    }
    form.onsubmit = function() {
        try {
            self.submit();
            return false;
        } catch (e) {
            return false;
        }
    };
    
    // mark/unmark the form as submitted
    var isSubmitting = false;
    this.isSubmitting = function() { return isSubmitting; };
    this._setSubmitting = function(submitting) {
        isSubmitting = submitting;
        if (submitting) {
            DOMUtil.addClass(self.formElement, "submitting");
        } else {
            DOMUtil.removeClass(self.formElement, "submitting");
        }
    };
    
} // FormSubmitter


FormSubmitter.loadedContent = function(loader) {
    var forms = loader.element.getElementsByTagName("form");
    for (var i = 0; i < forms.length; i++) {
        if (!forms[i].formSubmitter) {
            forms[i].formSubmitter = new FormSubmitter(forms[i]);
        }
    }
}; // FormSubmitter.loadedContent

//initialize shell menu framework upon page load
EventHandling.executeOnLoad(function() {
    ContentLoader.initialize(document.body);
});

