// ---------------------------------------------------------------------
// $Id: common.js 53289 2011-03-01 19:12:20Z eric $
// ---------------------------------------------------------------------
// Common functions, and common code to run on every Foundation page.
// ---------------------------------------------------------------------

// ---------------------------------------------------------------------
// Cookies
// ---------------------------------------------------------------------

function getCookie (name) {
  var start = document.cookie.indexOf(name + "=");
  var len = start + name.length + 1;
  if ((!start) && (name != document.cookie.substring(0, name.length))) return null;
  if (start == -1) return null;
  var end = document.cookie.indexOf( ";", len );
  if (end == -1) end = document.cookie.length;
  return unescape(document.cookie.substring(len, end));
}

function setCookie (name, value, expiredays) {
    var exdate = new Date();
    exdate.setDate(exdate.getDate()+expiredays);
    document.cookie = name + "=" + escape(value) +
    ((expiredays == null) ? "" : ";expires=" + exdate.toUTCString());
}

function deleteCookie (name) {
    setCookie(name, "", -1);
}

// ---------------------------------------------------------------------
// Popup Windows
// ---------------------------------------------------------------------

function popupWindow (name, url, w, h, l, t, statusbar) {
    var opts = "scrollbars,resizable";
    if (statusbar) opts += ",status";
    opts += ",width=" + w + ",height=" + h;
    opts += ",left=" + l + ",top=" + t;
    
    var popup = window.open(url, name, opts);
    popup.focus();
    return false;
};


// --------------------------------------------
// Star Ratings
// --------------------------------------------

function dnSetupStarRatings(selects, starOffImgSrc, starOnImgSrc) {
    // First, create a function for updating the rating spans.
    var updateRatingDisplay = function (ratingSpan, rating) {
        ratingSpan.select("img").each(function (img, i) {
            img.src = i + 1 <= rating ? starOnImgSrc : starOffImgSrc;
        });
    };
    
    $A(selects).each(function (sel) {
        // Use a span to hold the ratings star images.
        var ratingSpan = new Element("span", { "class": "dnStarRating" });
        ratingSpan.setStyle({ cursor: "pointer"});
        
        // Create the ratings span elements with the curent value.
        var curRating = sel.value ? parseInt(sel.value) : 0;
        for (var i = 1; i <= 5; i++) {
            ratingSpan.insert( new Element("img", {
                src: i <= curRating ? starOnImgSrc : starOffImgSrc,
                title: i == 1 ? "1 star" : i + " stars",
                alt: i  // Keep the rating value in the alt attribute.
            }));
        }
        
        // Attach event handlers to the star images.
        ratingSpan.select("img").each(function (img) {
            img.observe("click", function (e) {
                sel.value = img.alt;
                updateRatingDisplay(ratingSpan, parseInt(this.alt));
            });
        
            img.observe("mouseover", function (e) {
                if (this.dnStarRatingsMouseIn) return;
                this.dnStarRatingsMouseIn = true;
                updateRatingDisplay(ratingSpan, parseInt(this.alt));
            });
            
            img.observe("mouseout", function (e) {
                this.dnStarRatingsMouseIn = false;
                updateRatingDisplay(ratingSpan, parseInt(sel.value));
            })
        });
        
        // Hide the select and add the new ratings elements.
        $(sel).hide().insert({ after: ratingSpan });
    });
};


// ---------------------------------------------------------------------
// Fixup links to my public Profile page to go to MyProfile page.
// ---------------------------------------------------------------------

function fixupProfileLinks (profileUrl, myProfileUrl) {
    if (getCookie("memberOid")) {
        Event.observe(document, "dom:loaded", function () {
            var myOid = getCookie("memberOid");
            $$('a[href^="' + profileUrl + '"]').each(function (a) {
                if (a.href.match(new RegExp("Profile\\?oid=(?:oid(?:%3A|:)?)?" + myOid + "$"))) {
                    a.href = myProfileUrl;
                }
            });
        });
    }
}

/* animates the move to an anchor link */
/* xxx - should have a way to turn this behavior off (rel="noglide") */
function setupGlide() {
  var all_links = $$('a');
  all_links.each(function(thelink) {
      if (thelink.getAttribute('href') && thelink.getAttribute('href').match(/^#/) && $(thelink.getAttribute('href').split('#')[1]) && !thelink.getAttribute('onclick')) {
        thelink.onclick = function() {
          new Effect.ScrollTo(thelink.getAttribute('href').split('#')[1]);
          return false;
        }
      }
    });
}


// ---------------------------------------------------------------------
// Set up FancyZoom and FancyGlide
// ---------------------------------------------------------------------

Event.observe(window, "load", function () {
    /* Commented out until there's a solution that works with the flip book.
    $$("img").each(function (img) {
        var origWidth = img.readAttribute("data-orig-width");
        if (!origWidth) return;
        if (img.getWidth() >= parseInt(origWidth)) {
            img.up("a").insert({ after: img.remove() }).remove();
        }
    });
    */
    if (!window.setupZoom) return;
    setupZoom();
    setupGlide();
});


// ---------------------------------------------------------------------
// Functions from dom-prototype.js
// ---------------------------------------------------------------------

(function (window, document, undefined) {
// ---------------------------------------------------------------------
// Add a bunch of CSS code to the document.
// From http://yuiblog.com/blog/2007/06/07/style/
//
// Prototype doesn't have a function like this one yet!

function dnAddCss (cssCode) {
    var styleElement = document.createElement("style");
    styleElement.type = "text/css";

    if (styleElement.styleSheet) {
        styleElement.styleSheet.cssText = cssCode;
    } else {
        styleElement.appendChild(document.createTextNode(cssCode));
    }

    document.getElementsByTagName("head")[0].appendChild(styleElement);
};

// ---------------------------------------------------------------------
// Loads a JavaScript script by writing out a script tag, but checks
// that it hasn't already loaded it before doing so.

function dnLoadScript (url, async) {
    if (!window.dnLoadScript.seen) window.dnLoadScript.seen = { };
    if (window.dnLoadScript.seen[url]) return;
    window.dnLoadScript.seen[url] = true;

    if (async) {
        var script = document.createElement("script");
        script.setAttribute("src", url);
        script.setAttribute("async", "async");
        var someOtherScriptTag = document.getElementsByTagName("script")[0];
        someOtherScriptTag.parentNode.insertBefore(script, someOtherScriptTag);
    }
    else {
        document.write("<script src='"+url+"'></script>");
    }
}

// ---------------------------------------------------------------------
// Safari and Opera have problems with the img.complete property. To work
// around this, call dnImageCompleteWatch() on an img element before
// assigning it a (new) src. Then call dnImageComplete to test if the image
// has finished loading.
//
// Prototype doesn't seem to have anything for this yet.

function dnImageCompleteWatch (img) {
    if (img.complete != null) return img;
    img.dnImageComplete = false;
    img.onload = function (e) { img.dnImageComplete = true; };
    return img;
};

function dnImageComplete (img) {
    if (img.complete != null) return img.complete;
    if (img.dnImageComplete != null) return img.dnImageComplete;
    return false;
};

// ---------------------------------------------------------------------
// Create, show and hide iframe shields for a specified overlay to fix
// the z-index bug and windowed elements in IE6.
//
// Does scriptaculous have anything that does this?

function ieVersion () {
    if (!Prototype.Browser.IE) return undefined;
    if (!window.ScriptEngineMajorVersion) return 1;
    var verStr = ScriptEngineMajorVersion() + "." + ScriptEngineMinorVersion();
    return parseFloat(verStr);
};

function enableShieldedOverlayForIE (id) {
    var version = ieVersion();
    if (version == undefined || version > 5.6) return;

    var iframeShield = $(id + ":iframeShield");
    if (!iframeShield) {
        iframeShield = document.createElement("iframe");
        iframeShield.id = id + ":iframeShield";
        iframeShield.style.position = "absolute";
        var zIndex = dnGetStyle($(id), "zIndex");
        if (zIndex) iframeShield.style.zIndex = parseInt(zIndex);
        iframeShield.style.filter = "progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)";
        $(id).parentNode.insertBefore(iframeShield, $(id));
    }
    iframeShield.style.width = $(id).offsetWidth + "px";
    iframeShield.style.height = $(id).offsetHeight + "px";
    iframeShield.style.left = $(id).style.left;
    iframeShield.style.top = $(id).style.top;
    iframeShield.style.display = "block";
};

function disableShieldedOverlayForIE (id) {
    var version = ieVersion();
    if (version == undefined || version > 5.6) return;

    var iframeShield = $(id + ":iframeShield");
    if (iframeShield) iframeShield.style.display = "none";
};


// ---------------------------------------------------------------------
// DEPRICATED FUNCTIONS
//
// Every function below this point is depricated. Since you're already
// using Prototype, you should use the appropriate Prototype method
// to accomplish what these functions do. They're only still in here
// to provide backwards compatability.
// ---------------------------------------------------------------------


// ---------------------------------------------------------------------
// Get the computed style property of an element.
// From http://www.quirksmode.org/dom/getstyles.html
//
// This function is broken, and depricated! Use Prototype's
// Element.getStyle() instead, which properly works around browser
// incompatabilities.

function dnGetStyle (elem, prop) {
    var x = $(elem);
    if (x.currentStyle)
        var y = x.currentStyle[prop];
	else if (window.getComputedStyle)
        var y = document.defaultView.getComputedStyle(x,null).getPropertyValue(prop);
    return y;
};

// ---------------------------------------------------------------------
// Functions that have been rewritten to take advantage of Prototype,
// while still maintaining backwards compatability. Consider them all
// depricated.

function dnGetElementsByClassName(className, tag, elem){
    var selector = "." + className;
    if (tag) selector = tag + selector;
    if (elem) return $(elem).select(selector);
    else return $$(selector);
};

function dnAddEvent(obj, type, fn) {
    if (type == "dndomload")
        return Event.observe(document, "dom:loaded", fn);
    else if (obj && $(obj).observe)
        return $(obj).observe(type, fn);
    else
        return Event.observe(obj, type, fn);
};

function dnRemoveEvent(obj, type, fn) {
    return $(obj).stopObserving(type, fn);
};

function dnStopPropagation (e) {
    return Event.stop(e);
};

function dnContains (parent, child) {
    return $(child).descendantOf(parent);
};

function dnFindPos(obj) {
    return $(obj).cumulativeOffset();
};

function dnConcatNodeLists () {
    var result = [];
    $A(arguments).each(function (nodelist) {
        result.push($A(nodelist));
    });
    return result.flatten().compact();
};

// Exports
window["dnAddCss"] = dnAddCss;
window["dnLoadScript"] = dnLoadScript;
window["dnImageCompleteWatch"] = dnImageCompleteWatch;
window["dnImageComplete"] = dnImageComplete;
window["enableShieldedOverlayForIE"] = enableShieldedOverlayForIE;
window["disableShieldedOverlayForIE"] = disableShieldedOverlayForIE;
window["dnGetStyle"] = dnGetStyle;
window["dnGetElementsByClassName"] = dnGetElementsByClassName;
window["dnAddEvent"] = dnAddEvent;
window["dnRemoveEvent"] = dnRemoveEvent;
window["dnStopPropagation"] = dnStopPropagation;
window["dnContains"] = dnContains;
window["dnFindPos"] = dnFindPos;
window["dnConcatNodeLists"] = dnConcatNodeLists;

// End dom-prototype.js
})(window, document);


// ---------------------------------------------------------------------
// Foundation Namespace
// Wrapper function localizes some global symbols so they can be minified.
// ---------------------------------------------------------------------

(function (document, window, Math, undefined) {

var Foundation = {};

/**
 * URL class.
 * @param {string} url The URL string.
 * @param {Object=} params Query parameters.
 * @constructor
 */
function URL (url, params) {
    var urlComponents = url.match(/^([^\?])\?(.*)$/);
    if (urlComponents) {
        this.url = urlComponents[1];
        this.params = Object.extend(urlComponents[2].toQueryParams(), params);
    }
    else {
        this.url = url;
    }
}

/**
 * Convert URL to string.
 * @param {Object=} params Query parameters.
 * @return {string}
 */
URL.prototype.toString = function (params) {
    var string = this.url;
    if (this.params) {
        string += "?" + Object.extend(this.params, params).toQueryString();
    }
    return string;
};

/**
 * Provides observe and fire methods on subclasses.
 * @constructor
 */
Foundation.Observable = function () {
    /**
     * @type {Object.<string,Array.<function(string=, *=)>>}
     * @private
     */
    this.observers = {};
    
    /**
     * @type {boolean}
     * @protected
     */
    this.isObservable = true;
    
    /**
     * @type {string}
     * @private
     */
    this.identifier;
};

/**
 * @type {number}
 */
Foundation.Observable.identifierIncrement = 0;

/**
 * Generate an identifier string for this object.
 * @return {string}
 */
Foundation.Observable.prototype.identify = function () {
    if (!this.identifier) {
        this.identifier = "Foundation.Observable.id." + Foundation.Observable.identifierIncrement++;
    }
    return this.identifier;
};

/**
 * Register event observers.
 * @param {string} eventName
 * @param {function(string=, Foundation.Observable=)} callback
 * @return {Foundation.Observable}
 */
Foundation.Observable.prototype.observe = function (eventName, callback) {
    if (!this.observers[eventName]) this.observers[eventName] = [];
    this.observers[eventName].push(callback);
    return this;
};

/**
 * Register an event observer that ensures the callback is only called once
 * per execution segment on the main JavaScript thread. This is useful when
 * you have a bunch of "changed" events firing as a collection of values get
 * updated, and you only want to defer re-drawing the view until all the
 * data is finished changing.
 * @param {string} eventName
 * @param {function(string, Foundation.Observable=)} callback
 * @return {Foundation.Observable}
 */
Foundation.Observable.prototype.observeCoalesced = function (eventName, callback) {
    this.observe(eventName, this.fireCoalesced.bind(this, eventName, callback));
    return this;
};

/**
 * Fire callbacks registered by observers.
 * @param {string} eventName
 * @return {Foundation.Observable}
 */
Foundation.Observable.prototype.fire = function (eventName) {
    var observers = this.observers[eventName] || [];
    for (var i=0; i < observers.length; i++) {
        observers[i](eventName, this);
    }
    return this;
};

/**
 * @type {Object}
 */
Foundation.Observable.coalescedCallbacks = {};

/**
 * @type {boolean}
 */
Foundation.Observable.coalescedTimeoutIsSet = false;

/**
 * @param {string} eventName
 * @param {function(string, Foundation.Observable=)} callback
 * @return {Foundation.Observable}
 */
Foundation.Observable.prototype.fireCoalesced = function (eventName, callback) {
    if (! Foundation.Observable.coalescedCallbacks[this.identify()]) {
        Foundation.Observable.coalescedCallbacks[this.identify()] = {};
    }
    
    if (! Foundation.Observable.coalescedCallbacks[this.identify()][eventName]) {
        Foundation.Observable.coalescedCallbacks[this.identify()][eventName] = callback.curry(eventName, this);
    }
    
    if (! Foundation.Observable.coalescedTimeoutIsSet) {
        Foundation.Observable.coalescedTimeoutIsSet = true;
        setTimeout(function () {
            Foundation.Observable.coalescedTimeoutIsSet = false;
            for (var i in Foundation.Observable.coalescedCallbacks) {
                for (var j in Foundation.Observable.coalescedCallbacks[i]) {
                    Foundation.Observable.coalescedCallbacks[i][j]();
                }
            }
        }, 1);
    }
};

/**
 * Observable list.
 * @param {Array=} items
 * @constructor
 * @extends {Foundation.Observable}
 */
Foundation.Observable.Collection = function (items) {
    Foundation.Observable.call(this);
    
    /**
     * @type {Array}
     * @private
     */
    this.items = items || [];
    
    /**
     * @type {number}
     * @private
     */
    this.i = 0;
};
Object.extend(Foundation.Observable.Collection.prototype, Foundation.Observable.prototype);

/**
 * @param {*} val
 */
Foundation.Observable.Collection.prototype.push = function (val) {
    this.items.push(val);
    
    if (typeof val === "object" && val.isObservable) {
        val.observe("changed", this.fire.bind(this, "changed"));
    }
    
    this.fire("pushed");
    this.fire("changed");
};

/**
 * @param {*} val
 */
Foundation.Observable.Collection.prototype.remove = function (val) {
    for (var i=0; i < this.items.length; i++) {
        if (this.items[i] === val) {
            this.items.splice(i, 1);
            break;
        }
    }
    
    this.fire("removed");
    this.fire("changed");
};

Foundation.Observable.Collection.prototype.removeAll = function () {
    this.items = [];
    this.fire("removed");
    this.fire("changed");
};

Foundation.Observable.Collection.prototype.next = function () {
    return this.items[this.i++];
};

Foundation.Observable.Collection.prototype.rewind = function () {
    this.i = 0;
};


/**
 * Manage Foundation user logins.
 * @constructor
 * @extends {Foundation.Observable}
 */
Foundation.LoginManager = function () {
    Foundation.Observable.call(this);
    
    /**
     * @public
     * @type {boolean}
     */
    this.isAuthenticated = false;
    
    /**
     * @public
     * @type {Foundation.Member}
     */
    this.authenticatedMember = null;
    
    if (getCookie("login")) {
        this.isAuthenticated = true;
        this.authenticatedMember = new Member(getCookie("username"));
    }
    
    if (window.location.pathname.match(/\/Logout(\..*)?$/i)) {
        this.isAuthenticated = false;
        $(document).observe("dom:loaded", this.fire.bind(this, "logout"));
    }
};
Object.extend(Foundation.LoginManager.prototype, Foundation.Observable.prototype);

/**
 * Retreive the shared login manager.
 * @return {Foundation.LoginManager}
 */
Foundation.LoginManager.sharedLoginManager = function () {
    return Foundation.LoginManager._shared;
};

/**
 * Initialize the shared login manager.
 */
Foundation.LoginManager._shared = new Foundation.LoginManager();

/**
 * Login view controller.
 * @constructor
 */
Foundation.LoginViewController = function (loggedInView, loggedOutView, id) {
    this.loggedInView = new View(loggedInView);
    this.loggedOutView = new View(loggedOutView);
    this.id = id;
    
    var loginManager = Foundation.LoginManager.sharedLoginManager();
    loginManager.observe("login", this.updateViews.bind(this));
    loginManager.observe("logout", this.updateViews.bind(this));
    
    this.updateViews();
};

/**
 * Update the login view with the current login context.
 * @private
 */
Foundation.LoginViewController.prototype.updateViews = function () {
    var id = this.id;
    var loginManager = Foundation.LoginManager.sharedLoginManager();
    
    if (loginManager.isAuthenticated) {
        var member = loginManager.authenticatedMember;
        this.loggedInView["usernameLabel"].update(member.name);
        var hideThisView = this.loggedOutView;
        var showThisView = this.loggedInView;
    }
    else {
        var hideThisView = this.loggedInView;
        var showThisView = this.loggedOutView;
    }
    
    if (hideThisView.visible()) {
        hideThisView.fade({
            "duration": 0.3,
            "afterFinish": function () {
                hideThisView.contentView.writeAttribute("id", "");
                showThisView.contentView.writeAttribute("id", id);
                showThisView.appear({"duration": 0.3});
            }
        });
    }
    else {
        hideThisView.contentView.writeAttribute("id", "");
        showThisView.contentView.writeAttribute("id", id);
        showThisView.show();
    }
};

/**
 * Foundation.Envolve namespace.
 */
Foundation.Envolve = {};

/** @const */ Foundation.Envolve.INIT_SCRIPT = "http://d.envolve.com/env.nocache.js";
/** @const */ Foundation.Envolve.SERIAL_GLOBAL = "envoSn";
/** @const */ Foundation.Envolve.OPTIONS_GLOBAL = "envoOptions";
/** @const */ Foundation.Envolve.COMMAND_STRING_GLOBAL = "env_commandString";
/** @const */ Foundation.Envolve.RUN_COMMAND_GLOBAL = "env_executeCommand";
/** @const */ Foundation.Envolve.LOGIN_COMMAND_COOKIE = "foundation_envolve_login_cmd";

/**
 * Foundation integration for Envolve, a 3rd party chat plugin. Note that this
 * constructor does a document.write(), so be sure to only instantiate it during
 * page load. It's probably best to put at the bottom of the page.
 * See http://www.envolve.com/ for more info about Envolve.
 * @param {number} serial The serial number provided by Envolve (<code>envoSn</code>).
 * @param {Object=} options Options dictionary for Envolve.
 * @constructor
 */
Foundation.Envolve.SessionManager = function (serial, options) {
    var loginManager = Foundation.LoginManager.sharedLoginManager();
    loginManager.observe("login", this.login.bind(this));
    loginManager.observe("logout", this.logout.bind(this));
    
    window[Foundation.Envolve.SERIAL_GLOBAL] = serial;
    if (options) window[Foundation.Envolve.OPTIONS_GLOBAL] = options;
    
    if (loginManager.isAuthenticated) {
        this.login();
    }
    
    dnLoadScript(Foundation.Envolve.INIT_SCRIPT);
};

Foundation.Envolve.SessionManager.prototype.login = function () {
    var cachedLoginCommand = getCookie(Foundation.Envolve.LOGIN_COMMAND_COOKIE);
    if (cachedLoginCommand) {
        window[Foundation.Envolve.COMMAND_STRING_GLOBAL] = cachedLoginCommand;
    }
    else {
        this.runSignedCommand("login", function (signedCommandString) {
            setCookie(Foundation.Envolve.LOGIN_COMMAND_COOKIE, signedCommandString, null);
        });
    }
};

Foundation.Envolve.SessionManager.prototype.logout = function () {
    this.runSignedCommand("logout");
    deleteCookie(Foundation.Envolve.LOGIN_COMMAND_COOKIE);
};

/**
 * @param {string} command
 * @param {function(string=)=} callback
 */
Foundation.Envolve.SessionManager.prototype.runSignedCommand = function (command, callback) {
    new Ajax.Request("/gyrobase/Tools/Ajax/Envolve/SignedCommand", {
        "parameters": { "command": command },
        "onSuccess": function (response) {
            if (response.responseJSON["success"]) {
                var commandString = response.responseJSON["commandString"];
                var signature = response.responseJSON["signature"];
                var signedCommandString = signature + ";" + commandString;
                if (callback) callback(signedCommandString);
                window[Foundation.Envolve.RUN_COMMAND_GLOBAL](signedCommandString);
            }
        }
    });
};

// ---------------------------------------------------------------------
// Foundation Tools Sidebar and Footer
// Requires functions defined in tabs.js
// ---------------------------------------------------------------------

var Tools = Class.create({
    initialize: function (elem, options) {
        this.elem = $(elem);
        this.options = {
            orientation: "vertical"
        };
        Object.extend(this.options, options);
        
        var panels = this.elem.select("div.togglePanel");
        var toggles = dnSetupToggleGroupAnimated(
            this.elem.select("a.toggleActivator"),
            panels
        );
        
        // Horizontal tools chicanery
        if (this.options.orientation === "horizontal") {
            panels.each(function (panel, i) {
                panel.addClassName("horizontalToolsTogglePanel");
                panel.remove();
                $(document.body).insert(panel);
                panel.absolutize();
                
                var toggle = toggles.getElementFromMap(panel);
                var pos = toggle.up().cumulativeOffset();
                pos[1] += toggle.up().getHeight();
                panel.setStyle({ left: pos[0] + "px", top: pos[1] + "px" });
            });
        }
    
        // Setup ajax request and related animations for the panels.
        panels.invoke("observe", "dnPanel:afterShow", function () {
            var panel = $(this);
            var updaterDiv = panel.down(".ajaxUpdater");
            if (updaterDiv) new Ajax.Updater(updaterDiv, "/gyrobase/Macros/ToolsAjax", {
                parameters: {
                    macro: panel.readAttribute("data-toolsajaxmacro"),
                    object: panel.readAttribute("data-toolsoid"),
                    url: window.location
                },
                onComplete: function (t) {
                    panel.down(".loading").blindUp({ duration: 0.3 });
                    if (updaterDiv) updaterDiv.blindDown({ duration: 0.3 });
                }
            });
        });
        panels.invoke("observe", "dnPanel:afterHide", function () {
            var loading = $(this).down(".loading");
            if (loading) loading.show();
        
            var updater = $(this).down(".ajaxUpdater");
            if (updater) updater.hide();
        });
    }
});

Tools.addToList = function (formid) {
    var listPanel = $(formid).up("div.togglePanel");
    listPanel.down(".loading").blindDown({ duration: 0.3 });
    listPanel.down(".ajaxUpdater").blindUp({
        duration: 0.3,
        afterFinish: function () {
            $(formid).request({ onComplete: function (t) {
                  listPanel.down(".ajaxUpdater").update(t.responseText);
                  listPanel.down(".loading").blindUp({ duration: 0.3 });
                  listPanel.down(".ajaxUpdater").blindDown({ duration: 0.3 });
            }});
        }
    });
};

Tools.remindMe = function (formid) {
    var rmPanel = $(formid).up("div.togglePanel");
    rmPanel.down(".loading").blindDown({ duration: 0.3 });
    rmPanel.down(".ajaxUpdater").blindUp({
        duration: 0.3,
        afterFinish: function () {
            $(formid).request({ onComplete: function (t) {
                  rmPanel.down(".ajaxUpdater").update(t.responseText);
                  rmPanel.down(".loading").blindUp({ duration: 0.3 });
                  rmPanel.down(".ajaxUpdater").blindDown({ duration: 0.3 });
            }});
        }
    });
};

// Toggle Custom Reminder Times
Tools.showCustomRemindTime = function (oid) {
    if ($F('remindRelative'+oid) == 'other') {
        $('customRemindTime'+oid).show();
    } else {
        $('customRemindTime'+oid).hide();
    }
};

Tools.setup = function () {
    if (Tools.setup.alreadyRan) return;
    Tools.setup.alreadyRan = true;
    
    $(document).observe("dom:loaded", function () {
        $$("div.tools", "div.tinyTools").each(function (elem) {
            new Tools(elem);
        });
        $$("div.horizontalTools").each(function (elem) {
            new Tools(elem, { orientation: "vertical" });
        });
    });
};


// ---------------------------------------------------------------------
// Foundation.Ajax Class  --  Run components Ajaxily
// ---------------------------------------------------------------------

var FoundationAjax = Class.create(Ajax.Request, {
    initialize: function ($super, component, options) {
        options = options || {};
        
        var remoteComponent = component.match(/^(\/[^:]+):(\w+)$/);
        if (remoteComponent) {
            var url = remoteComponent[1];
            component = remoteComponent[2];
            options["parameters"] = options["parameters"] || {};
        }
        else {
            var url = window.location.toString().replace(/\?.*/, "");
            var params = window.location.search.substring(1).toQueryParams();
            if (options["parameters"]) {
                for (p in options["parameters"]) {
                    params[p] = options["parameters"][p];
                }
            }
            options["parameters"] = params;
        }
        
        options["parameters"]["ajaxComponent"] = component;
        $super(url, options);
    }
});


// ---------------------------------------------------------------------
// Foundation.Paginator Class  --  Page through results with Ajax
// ---------------------------------------------------------------------

var Paginator = Class.create({
    initialize: function (component, view, navlinksSel, pageParam) {
        this.component = component;
        this.view = view;
        this.navlinksSel = navlinksSel;
        this.pageParam = pageParam || "page";
        this.navigationLinkSeutp();
        this.inProgress = false;
    },
    
    navigationLinkSeutp: function () {
        var links = $(this.view).select(this.navlinksSel);
        links.invoke("observe", "click", this.requestPage.bind(this));
    },
    
    requestPage: function (clickEvent) {
        clickEvent.stop();
        if (this.inProgress) return;
        this.inProgress = true;
        
        var url = clickEvent.findElement("a").readAttribute("href");
        var linkParams = url.replace(/[^?]+\?/, "").toQueryParams();
        
        var requestParams = {};
        requestParams[this.pageParam] = linkParams[this.pageParam];
        
        this.spinner = new Spinner(this.view, { delay: 1 });
        new FoundationAjax(this.component, {
            method: "get",
            parameters: requestParams,
            onSuccess: this.updatePage.bind(this),
            onComplete: this.reset.bind(this)
        });
    },
    
    updatePage: function (r) {
        this.spinner.hide();
        this.spinner = null;
        
        new Effect.Opacity(this.view, {
            duration: 0.3,
            from: 1,
            to: 0,
            afterFinish: (function () {
                $(this.view).update(r.responseText);
                $(this.view).appear({ duration: 0.3 });
                this.navigationLinkSeutp();
            }).bind(this)
        });
    },
    
    reset: function () {
        this.inProgress = false;
        if (this.spinner) this.spinner.hide();
    }
});


// ---------------------------------------------------------------------
// Foundation.Spinner Class  --  Display a spinner centered over a div
// ---------------------------------------------------------------------

var Spinner = Class.create({
    initialize: function (elem, options) {
        this.content = $(elem);
        this.delay = options.delay || 0;
        this.width = options.width || 100;
        this.height = options.height || 100;
        this.img_src = options.img_src || "/foundation/images/loaders/spinner-white-on-black.gif";
        this.img_width = options.img_width || 32;
        this.img_height = options.img_height || 32;
        this.color = options.color || "#000000";
        this.borderRadius = options.borderRadius || 15;
        this.opacity = options.opacity || .75;
        
        if (this.delay) {
            var ms = Math.ceil(this.delay * 1000);
            this.timer = setTimeout(this.show.bind(this), ms);
        }
        else {
            this.show();
        }
    },
    
    show: function () {
        // Create the img and div elements for the spinner.
        var img = new Element("img", { src: this.img_src, alt: "Loading…" });
        this.spinner = (new Element("div")).insert(img);
        
        // Get position of the element we're placing the spinner over.
        var contentPos = $(this.content).cumulativeOffset();
        var contentDim = $(this.content).getDimensions();
        
        // Set the spinner img styles.
        img.setStyle({
            display: "block",
            position: "absolute",
            width: this.img_width + "px",
            height: this.img_height + "px",
            left: Math.floor((this.width / 2) - (this.img_width / 2)) + "px",
            top: Math.floor((this.height / 2) - (this.img_height / 2)) + "px"
        });
        
        // The spinner div styles.
        this.spinner.setStyle({
            display: "block",
            position: "absolute",
            backgroundColor: this.color,
            width: this.width + "px",
            height: this.height + "px",
            left: Math.floor(contentPos[0] + (contentDim.width / 2) - (this.width / 2)) + "px",
            top: Math.floor(contentPos[1] + (contentDim.height / 2) - (this.height / 2)) + "px",
            zIndex: "1000",
            borderRadius: this.borderRadius + "px",
            MozBorderRadius: this.borderRadius + "px",
            WebkitBorderRadius: this.borderRadius + "px"
        }).setOpacity(this.opacity);
        
        $(document.body).insert(this.spinner);
    },
    
    hide: function () {
        if (this.timer) clearTimeout(this.timer);
        if (this.spinner) this.spinner.remove();
    }
});


// ---------------------------------------------------------------------
// Foundation.View Class
// ---------------------------------------------------------------------

var View = Class.create({
    initialize: function (contentView) {
        if (contentView) {
            if (typeof contentView == "string" && contentView.match(/^\s*<\w+/)) {
                this.setViewElementWithHtml(contentView);
            }
            else {
                this.setViewElement(contentView);
            }
        }
    },
    
    setViewElement: function (contentView) {
        this.contentView = $(contentView);
        var members = this.contentView.select("[data-view-member]");
        for (var i=0; i < members.length; i++) {
            var name = members[i].readAttribute("data-view-member");
            this[name] = members[i];
        }
        this.draw();
    },
    
    setViewElementWithHtml: function (html) {
        this.setViewElement((new Element("div")).insert(html).down().remove());
    },
    
    setViewElementWithAjax: function (component, options) {
        var theirOnSuccess = options.onSuccess;
        options.onSuccess = (function (response) {
            this.setViewElementWithHtml(response.responseText);
            if (theirOnSuccess) theirOnSuccess(response);
        }).bind(this);
        new FoundationAjax(component, options);
    },
    
    draw: function () {
        // Default draw method doesn't do anything.
    },
    
    spinnerOptions: function () {
        return {};
    },
    
    spinnerElement: function () {
        return this.contentView;
    },
    
    showSpinner: function () {
        if (!this.spinner) {
            var element = this.spinnerElement();
            var options = this.spinnerOptions();
            this.spinner = new Spinner(element, options);
        }
    },
    
    hideSpinner: function () {
        if (this.spinner) {
            this.spinner.hide();
            this.spinner = undefined;
        }
    },
    
    show: function () {
        return this.contentView.show();
    },
    
    hide: function () {
        return this.contentView.hide();
    },
    
    visible: function () {
        return this.contentView.visible();
    },
    
    fade: function (opts) {
        return this.contentView.fade(opts);
    },
    
    appear: function (opts) {
        return this.contentView.appear(opts);
    },
    
    destroy: function () {
        this.hideSpinner();
    }
});


// ---------------------------------------------------------------------
// Modal View class
// ---------------------------------------------------------------------

var ModalView = Class.create(View, {
    initialize: function ($super, contentView, options) {
        if (ModalView.active) {
            throw "Attempted to create modal view when one is already active.";
        }
        ModalView.active = this;
        
        // Default options.
        this.options = Object.extend({
            width: 400,
            margin: 40
        }, options);
        
        // Background view to prevent interaction with other elements
        // on the page.
        this.smokeView = (new Element("div")).setStyle({
            position: "fixed",
            top: 0, right: 0, bottom: 0, left: 0,
            zIndex: 19000,
            backgroundColor: "black",
            opacity: 0
        });
                
        // Insert the smoke view into document and fade it in.
        $(document.body).insert(this.smokeView);
        new Effect.Opacity(this.smokeView, { duration: 0.3, to: 0.7 });
                
        // Init the component view.
        $super(contentView);
    },
    
    setViewElement: function ($super, contentView) {
        if (this.chromeView) this.chromeView.remove();
        
        // Chrome view to contain the contentView.
        this.chromeView = (new Element("div")).setStyle({
            position: "fixed",
            overflow: "auto",
            zIndex: 19000,
            width: this.options.width + "px",
            backgroundColor: "white",
            border: "solid 10px #222222",
            padding: "10px",
            opacity: 0
        });
        this.chromeView.insert(contentView);
        
        // Fade in the chrome and content view.
        $(document.body).insert(this.chromeView);
        new Effect.Opacity(this.chromeView, { duration: 0.3, to: 1 })
        
        // Officially set the contentView by calling superclass' setViewElement.
        $super(contentView);
        
        // Make sure draw() gets called when the window size changes.
        this._draw_bound = this.draw.bind(this);
        Event.observe(window, "resize", this._draw_bound);
    },
    
    draw: function ($super) {
        var margin = this.options.margin;
        var wDim = new WindowDimensions();
        var cDim = new Element.Layout(this.chromeView);
        
        // Make sure height doesn't overflow the window.
        if (cDim.get("margin-box-height") > wDim.height - (margin * 2)) {
            var verticalPad =   cDim.get("border-top")
                              + cDim.get("padding-top")
                              + cDim.get("padding-bottom")
                              + cDim.get("border-bottom");
            this.chromeView.setStyle({
                height: wDim.height - (margin * 2) - verticalPad
            });
            cDim = new Element.Layout(this.chromeView);
        }
        
        // Center the chromeView.
        this.chromeView.setStyle({
            top: Math.floor(wDim.height / 2) - Math.floor(cDim.get("margin-box-height") / 2) + "px",
            left: Math.floor(wDim.width / 2) - Math.floor(cDim.get("margin-box-width") / 2) + "px"
        });
        
        $super();
    },
    
    spinnerElement: function () {
        return this.smokeView;
    },
    
    spinnerOptions: function () {
        return { delay: 0.4 };
    },
    
    close: function () {
        new Effect.Parallel([
            new Effect.Opacity(this.smokeView, { sync: true, to: 0 }),
            new Effect.Opacity(this.chromeView, { sync: true, to: 0 })
        ], {
            duration: 0.3,
            afterFinish: this.destroy.bind(this)
        });
    },
    
    destroy: function ($super) {
        this.smokeView.remove();
        this.chromeView.remove();
        Event.stopObserving(window, "resize", this._draw_bound);
        ModalView.active = undefined;
        $super();
    }
});

// ---------------------------------------------------------------------
// Live User Input Validator
// ---------------------------------------------------------------------

var LiveValidator = Class.create(Form.Element.Observer, {
    initialize: function ($super, element, options) {
        this.options = Object.extend({
            className: "validatorResult",
            passedImage: "/foundation/images/icons/icon_approve.png",
            failedImage: "/foundation/images/icons/exclamation.png",
            validation: this.requiredValidation.bind(this),
            message: this.defaultMessage.bind(this)
        }, options);
        
        this.output = new Element("span", { "class": this.options.className });
        element.insert({ after: this.output });
        this.icon = new Element("img", { src: this.options.failedImage });
        this.output.insert(this.icon);
        this.message = new Element("span");
        this.output.insert(this.message);

        $super(element, 0.2, this.validate.bind(this));
        this.validate(element, this.getValue());
    },
    
    requiredValidation: function (value) {
        return (new String(value)).length ? true : false;
    },
    
    validate: function (element, value) {
        var isValid = this.options.validation(value);
        if (isValid === undefined) {
            this.setState(LiveValidator.State.DEFERRED);
        }
        else if (isValid) {
            this.setState(LiveValidator.State.PASSED);
        }
        else {
            this.setState(LiveValidator.State.FAILED);
        }
    },
    
    setState: function (state) {
        this.state = state;
        
        switch (state) {
            case LiveValidator.State.PASSED:
                this.icon.writeAttribute("src", this.options.passedImage);
                this.icon.setOpacity(1);
                break;
            case LiveValidator.State.FAILED:
                this.icon.writeAttribute("src", this.options.failedImage);
                this.icon.setOpacity(1);
                break;
            case LiveValidator.State.DEFERRED:
                this.icon.writeAttribute("src", this.options.failedImage);
                this.icon.setOpacity(0.5);
                break;
        }
        
        this.options.message(state);
    },
    
    defaultMessage: function (state) {
        var message;
        
        switch(state) {
            case LiveValidator.State.PASSED:
            case LiveValidator.State.FAILED:
                message = "";
                break;
            case LiveValidator.State.DEFERRED:
                message = "Checking…";
                break;
        }
        
        return message;
    }
});

LiveValidator.State = {
    DEFERRED: -1,
    FAILED: 0,
    PASSED: 1
};


// ---------------------------------------------------------------------
// Live User Input Validator With Ajax
// ---------------------------------------------------------------------

var LiveValidatorAjax = Class.create(LiveValidator, {
    initialize: function ($super, element, url, options) {
        this.ajaxUrl = url;
        this.ajaxOptions = options || {};
        this.changeCount = 1;
        $super(element, { validation: this.ajaxValidation.bind(this) });
    },
    
    ajaxValidation: function (value) {
        this.changeCount += 1;
        
        var opts = Object.extend({}, this.ajaxOptions);
        var name = this.element.readAttribute("name");
        var params = {}; params[name] = value;
        opts["parameters"] = Object.extend(params, opts["parameters"]);
        
        var currentCount = this.changeCount;
        opts["onSuccess"] = (function (response) {
            // Only do the validation if there isn't a more recent
            // validation in flight.
            if (currentCount == this.changeCount) {
                var valid = response.responseJSON.valid;
                var state = valid ? LiveValidator.State.PASSED
                                  : LiveValidator.State.FAILED;
                this.setState(state);
            }
        }).bind(this);
        
        setTimeout((function () {
            // Only kick off Ajax request if value hasn't changed.
            if (currentCount == this.changeCount) {
                new Ajax.Request(this.ajaxUrl, opts);
            }
        }).bind(this), 450);
        
        return undefined;
    }
});

// ---------------------------------------------------------------------

/**
 * Browser window inner dimensions.
 * @constructor
 */
function WindowDimensions () {
    if (window.innerHeight || window.innerWidth) {
        this.width = window.innerWidth;
        this.height = window.innerHeight;
    }
    else if (document.documentElement) {
        this.width = document.documentElement.clientWidth,
        this.height = document.documentElement.clientHeight
    }
    else {
        this.width = this.height = 0;
    }
}

// ---------------------------------------------------------------------

/**
 * Represenation of member object.
 * @constructor
 */
function Member (name) {
    this.name = name;
}

/** @const */ Member.LOGIN_TEMPLATE = "/gyrobase/Tools/Ajax/Login";

/**
 * Enum for different log in statuses.
 * @enum {number}
 */
Member.LogInStatus = {
    UNLINKED: -1,
    FAILED: 0,
    AUTHENTICATED: 1
};

/**
 * Static function to retreive the currently logged in member.
 * @return {Member}
 */
Member.sharedLoggedInMemeber = function () {
    var login = getCookie("login");
    if (!login) return undefined;
    var member = new Member(getCookie("username"));
    return member;
};

/**
 * Static function to attempt to log in with an email address and password.
 * @param {string} email Email address.
 * @param {string} password Password.
 * @param {function(Member.LogInStatus, ?string, Array.<string>=)} callback
 */
Member.logInWithEmailAndPassword = function (email, password, callback) {
    // XXX
};

/**
 * Static function to attempt to log in using a Facebook cookie.
 * @param {FacebookCookie} cookie The Facebook cookie belonging to the user/app.
 * @param {function(Member.LogInStatus, ?string, Array.<string>=)} callback
 */
Member.logInWithFacebookCookie = function (cookie, callback) {
    new Ajax.Request(Member.LOGIN_TEMPLATE, {
        parameters: {
            facebookCookiePayload: cookie.serialize(),
            facebookCookieSig: cookie.get("sig"),
            facebookLoginAttempt: "true"
        },
        onComplete: Member.handleLoginResponse.curry(callback)
    });
};

/**
 * Static function for handling the Ajax response from the log in template.
 * @protected
 * @param {function(Member.LogInStatus, ?string, Array.<string>=)} callback
 * @param {Ajax.Response} response Ajax response object.
 */
Member.handleLoginResponse = function (callback, response) {
    if (!response.responseJSON) {
        callback(Member.LogInStatus.FAILED, undefined, ["No response from server."]);
        return;
    }
    
    var status = response.responseJSON.status;
    var extra = response.responseJSON.extra;
    var errors = response.responseJSON.errors;
    
    if (status === "authenticated") {
        callback(Member.LogInStatus.AUTHENTICATED, extra);
        $(document).fire("foundation:login");
    }
    else if (status === "unlinked") {
        callback(Member.LogInStatus.UNLINKED, extra, errors);
    }
    else if (status === "failed") {
        callback(Member.LogInStatus.FAILED, extra, errors);
    }
    else {
        callback(Member.LogInStatus.FAILED, undefined, ["Unexpected response from server."]);
    }
};

/**
 * Static function for logging the user out of Foundation.
 */
Member.logOut = function () {
    if (Member.sharedLoggedInMemeber()) {
        deleteCookie("login");
        deleteCookie("username");
        deleteCookie("memberOid");
        $(document).fire("foundation:logout");
    }
};

// ---------------------------------------------------------------------

/**
 * Class for handling Facebook JS SDK events. Asynchronously loads
 * the Facebook SDK. Requires an empty <div> with an id of "fb-root"
 * to already be present in the document when called.
 * @constructor
 * @param {string} appId
 * @param {Object} options
 */
function FacebookAppDelegate (appId, options) {
    this.appId = appId ? appId : FacebookAppDelegate.sharedAppId();
    this.readyQueue = [];
    this.options = Object.extend({
        appId: this.appId,
        status: true,
        cookie: true,
        xfbml: true
    }, options);
    
    window["fbAsyncInit"] = this.init.bind(this, window["fbAsyncInit"]);
    var fbJS = document.location.protocol + "//connect.facebook.net/en_US/all.js";
    dnLoadScript(fbJS, true);
    
    FacebookAppDelegate["_shared_"+this.appId] = this;
}

/**
 * Static function to grab and return the Facebook app ID from the document.
 * @return {string}
 */
FacebookAppDelegate.sharedAppId = function () {
    return $$("meta[property=fb:app_id]").first().readAttribute("content");
};

/**
 * Static function to retrieve shared FacebookAppDelegate instance.
 * @param {string?} appId
 */
FacebookAppDelegate.sharedAppDelegate = function (appId) {
    if (!appId) appId = FacebookAppDelegate.sharedAppId();
    if (FacebookAppDelegate["_shared_"+appId]) return FacebookAppDelegate["_shared_"+appId];
    throw "No such FacebookAppDelegate with id: " + appId;
};

/**
 * Callback for when the Facebook SDK is finished loading.
 * @param {?function()=} anotherInitFunc
 */
FacebookAppDelegate.prototype.init = function (anotherInitFunc) {
    FB.init(this.options);
    if (anotherInitFunc) anotherInitFunc();
    for (var i=0; i < this.readyQueue.length; i++) {
        this.readyQueue[i]();
    }
    this.readyQueue = undefined;
};

/**
 * Ensure a function runs once FB JSDK is initialized. Runs the function in the
 * context of the FacebookAppDelegate object.
 * @param {function()} callback
 * @private
 */
FacebookAppDelegate.prototype.onready = function (callback) {
    callback = callback.bind(this);
    if (this.readyQueue) {
        this.readyQueue.push(callback);
    }
    else {
        callback();
    }
};

/**
 * Setup this FacebookAppDelegate to do Foundation authentication.
 */
FacebookAppDelegate.prototype.receiveAuthNotifications = function () {
    this.onready(function () {
        FB.Event.subscribe("auth.sessionChange", this.sessionChange.bind(this));
        
        // See if we already have a Facebook cookie. If we don't, we're done.
        try {
            var cookie = new FacebookCookie(this.appId);
        }
        catch (e) {
            return;
        }
        
        // If we've got a Facebook cookie but we're not logged in, let's
        // try refreshing the Facebook session. Unless this is the logout
        // page.
        var logoutPage = window.location.pathname.match(/\/Logout(\..*)?$/i);
        if (!logoutPage && !Member.sharedLoggedInMemeber()) {
            FB.getLoginStatus(this.sessionChange.bind(this));
        }
    });
};

/**
 * Log out the user from Facebook.
 */
FacebookAppDelegate.prototype.logOut = function () {
    this.onready(function () {
        FB.logout();
    });
};

/**
 * Handle a user logging in or logging out of Facebook.
 * @param {Object} response Facebook session change response.
 */
FacebookAppDelegate.prototype.sessionChange = function (response) {
    if (response.session) {
        // A user has logged in, and a new cookie has been saved. Attempt
        // to log in to Foundation.
        var cookie = new FacebookCookie(this.appId);
        Member.logInWithFacebookCookie(cookie, this.handleFoundationLogin.bind(this));
        
    } else {
        // The user has logged out of Facebook, and the cookie has been cleared.
        Member.logOut();
    }
};

/**
 * Handle the Foundation log in attempt.
 * @param {Member.LogInStatus} status Result of the log in attempt.
 * @param {?string} forms HTML for forms (from the extra field in the Login JSON response).
 * @param {Array.<string>=} errors Array of error messages.
 */
FacebookAppDelegate.prototype.handleFoundationLogin = function (status, forms, errors) {
    switch (status) {
        case Member.LogInStatus.AUTHENTICATED:
            $(document).fire("foundation:login");
            break;
        case Member.LogInStatus.UNLINKED:
            new FirstFacebookLogInViewController(forms);
            break;
        case Member.LogInStatus.FAILED:
            if (window.console) window.console.error(errors);
            break;
    }
};

// ---------------------------------------------------------------------

/**
 * Facebook cookie parser.
 * @constructor
 * @param {string} appId Facebook Appliction ID
 */
function FacebookCookie (appId) {
    var cookieStr = getCookie("fbs_" + appId);
    if (!cookieStr) throw "No Facebook app cookie available.";
    
    var cookieVals = cookieStr.replace(/^[\\"]+|[\\"]+$/g, "").split("&");
    this.keys = [];
    this.vals = {};

    for (var i=0; i < cookieVals.length; i++) {
        var pair = cookieVals[i].split("=");
        var key = decodeURIComponent(pair[0]);
        var val = decodeURIComponent(pair[1]);
        this.keys.push(key);
        this.vals[key] = val;
    }
}

/**
 * Facebook cookie value accessor.
 * @param {string} key
 * @return {string}
 */
FacebookCookie.prototype.get = function (key) {
    return this.vals[key];
};

/**
 * Serialize the Facebook cookie. This is sorted by key, because
 * we can't reliably do case-sensitive sorting in <ev>, which is necessary
 * to verify the signature.
 * @return {string}
 */
FacebookCookie.prototype.serialize = function () {
    var result = "";
    var sortedKeys = this.keys.sort();
    for (var i=0; i < sortedKeys.length; i++) {
        var key = sortedKeys[i];
        if (key !== "sig") {
            var encodedKey = encodeURIComponent(key);
            var encodedVal = encodeURIComponent(this.vals[key]);
            result += encodedKey + "=" + encodedVal + "&";
        }
    }
    return result.replace(/\&$/, "");
};

// ---------------------------------------------------------------------

/**
 * View controller for the first log in view.
 * @param {string} viewHtml HTML for the forms
 * @constructor
 */
function FirstFacebookLogInViewController (viewHtml) {
    FB.api("/me", this.handleUserInfoResponse.bind(this));
    this.view = new ModalView(viewHtml);
    
    this.view.newMemberForm.observe("submit", this.newMemberFormSubmit.bind(this));
    this.nameValidator = new LiveValidatorAjax(
        this.view.usernameInput,
        "/gyrobase/Tools/Ajax/ValidateUsernameAvailability",
        {}
    );
    this.view.existingMemberForm.observe("submit", this.existingMemberFormSubmit.bind(this));
}

/**
 * Update fields with decent defaults from the "/me" Graph response.
 * But don't overwrite a value a user has already typed in. 
 * @param {Object} response Facebook Graph API response object.
 * @private
 */
FirstFacebookLogInViewController.prototype.handleUserInfoResponse = function (response) {
    if (this.view.usernameInput.getValue() === "")
        this.view.usernameInput.setValue(response["name"]);
    if (this.view.emailInput.getValue() === "")
        this.view.emailInput.setValue(response["email"]);
    
    this.view.newMemberForm.down("input[name=name]").setValue(response["name"]);
    this.view.newMemberForm.down("input[name=loginEmail]").setValue(response["email"]);
    this.view.newMemberForm.down("input[name=confirmEmail]").setValue(response["email"]);
};

/**
 * Handle the new member form submission.
 * @private
 */
FirstFacebookLogInViewController.prototype.newMemberFormSubmit = function (e) {
    e.stop();
    this.view.newMemberFormSubmit.request({
        // "onSuccess": this.
    });
};

/**
 * Handle the existing member form submission.
 * @private
 */
FirstFacebookLogInViewController.prototype.existingMemberFormSubmit = function (e) {
    e.stop();
};

    
// ---------------------------------------------------------------------
// Exports
// ---------------------------------------------------------------------

window["Foundation"] = Foundation;
Foundation["LoginViewController"] = Foundation.LoginViewController;
Foundation["Tools"] = Tools;
Tools["setup"] = Tools.setup;
Foundation["Ajax"] = FoundationAjax;
Foundation["Paginator"] = Paginator;
Foundation["Spinner"] = Spinner;
Foundation["View"] = View;
Foundation["ModalView"] = ModalView;
Foundation["LiveValidator"] = LiveValidator;
LiveValidator["State"] = LiveValidator.State;
Foundation["LiveValidatorAjax"] = LiveValidatorAjax;
Foundation["WindowDimensions"] = WindowDimensions;
Foundation["Member"] = Member;
Member["sharedLoggedInMemeber"] = Member.sharedLoggedInMemeber;
Foundation["FacebookAppDelegate"] = FacebookAppDelegate;
FacebookAppDelegate["sharedAppDelegate"] = FacebookAppDelegate.sharedAppDelegate;
FacebookAppDelegate.prototype["receiveAuthNotifications"] = FacebookAppDelegate.prototype.receiveAuthNotifications;
FacebookAppDelegate.prototype["logOut"] = FacebookAppDelegate.prototype.logOut;

window["Foundation"]["Envolve"]["SessionManager"] = Foundation.Envolve.SessionManager;

// ---------------------------------------------------------------------
// End Foundation Namespace
// ---------------------------------------------------------------------
})(document, window, Math);


