function UI(inConfig) {
    this.config = $.extend({
        resizeLayout:true,
        minBodyHeaderHeight:78
    }, inConfig);
    
    //be sure to do nothing here that involves templates!!
    if(this.config.resizeLayout) {
        this.resizeLayout();
    }
    this.calculateLayoutSettings();
    this.currentPanel = "none";
    this.statusMessageTimeout = false;
    this.vars = {htmlTimeout:{}};
    this.windows = {
        googleVerify:{
            opts:"location=0,status=0,width=647,height=450,scrollbars=1",
            url:"google_sync/get_access_token__contacts.php",
            window:false
        },
        externalPayments: {
            opts:"location=0,status=0,width=775,height=600,scrollbars=1",
            url:"http://google.com",
            window:false
        },
        help:{
            opts:"location=0,status=0,width=780,height=450,scrollbars=1,resizable=1",
            url:"help/",
            window:false
        }

    }
    this.modalArgs = false;
    this.modalTimeout = false;
    this.aTileListStr = "";
}



UI.prototype.aTile = function(inAddress, inMode) {
    var tpE = {
        content:""
    };
    inAddress.typeList = ab.get("addressTypeList");

    tpE.tileID		= inAddress.id;
    tpE.tileClass = this.aTileAddressTypeToClass(ab.addressTypeByID[inAddress.type__id].name);

    switch(inMode) {
        //passive tile content with active tile click
        case "ab" :
            tpE.tileTitle	= "send " + inAddress.name + " @ " + ab.addressTypeByID[inAddress.type__id].name + " a letter";
            tpE.controlsTop = ui.btnNewLTRSml({
                style:"float:right",
                text:"new letter"
            });
            tpE.onclickTile = "ui.stopPropagation(Event); lman.newLetter({recipientList:[" + inAddress.id + "]})";
            tpE.content = $.snippet("address_tile_content", inAddress);
            break;
        case "lmanABList" :
            tpE.tileID  = inMode + "__" + inAddress.id;
            tpE.controlsTop = ui.btnGrySml({
                style:"float:right",
                text:"add",
                title:"add this address to your letter"
            });
            tpE.onclickTile = "lman.addRecipient(" + inAddress.id + ")";
            tpE.content = $.snippet("address_tile_content", inAddress);
            break;
        case "lmanRecipient" :
            tpE.tileID  = inMode + "__" + inAddress.id;
            tpE.controlsTop = ui.btnGrySml({
                style:"float:right",
                text:"remove",
                title:"remove this address from your letter"
            });
            tpE.onclickTile = "lman.removeRecipient(" + inAddress.id + ")";
            tpE.content = $.snippet("address_tile_content", inAddress);
            break;
        //active tile content - tile itself is only a visual wrapper for data
        case "abEdit" :
            tpE.controlsTop = ui.btnGrySml({
                style:"float:right",
                text:"remove",
                title:"remove this address from your contact",
                onclick:"ab.removeAddress("+ inAddress.id + ")"
                });
            tpE.content = $.snippet("address_tile_content_edit", inAddress);
            break;
        case "lmanEdit" :
            tpE.controlsTop = "";
            tpE.content = $.snippet("address_tile_content_edit", inAddress);
            break;
        case "readOnly" :
        default:
            //return("ui.aTile: 'inMode does not match a defined mode (" + inMode + ")");
            tpE.content = $.snippet("address_tile_content", inAddress);
    }

    return($.snippet("address_tile", tpE));
}

UI.prototype.aTileAddressTypeToClass = function(inType) {
    switch(inType) {
        case "home" :
            return "addressTileBlu";
        case "work" :
            return "addressTileGrn";
        default :
            return "addressTilePnk";
    }
}

UI.prototype.aTileChangeClass = function(inID, inType) {
    $("#editContactAddressPanel #aTile_" + inID).attr("className", ui.aTileAddressTypeToClass(ab.addressTypeByID[inType].name));
}


UI.prototype.aTileList = function(inAddressList, inMode, inNoAddrSuffix) {
    this.aTileListStr = "";
    if(inAddressList.length == 0) {
        this.aTileListStr += "<i>There are no addresses";
        if(typeof inNoAddrSuffix == "string") this.aTileListStr += " for " + inNoAddrSuffix;
        this.aTileListStr += "</i>"
    }
    for(var i in inAddressList) if(inAddressList.hasOwnProperty(i))
        this.aTileListStr += ui.aTile(inAddressList[i], inMode);

    return this.aTileListStr
}

UI.prototype.displayMain = function(inPanel) {
    if(document.getElementById("contentPanel_" + inPanel) != null) {
        $("#mainContentPanel").attr("className", inPanel);
        $("#mainMenuTR").attr("className", inPanel);
        this.currentPanel = inPanel;
    }
}

/* UI.prototype.displayAccountPanel = function() {
	/*this.displayMain('account');
	var tpd = {}; //template data
	for(i in tk.settings) {
		if(tk.settings.hasOwnProperty(i)) {
			var setting = tk.settings[i];
			if(!tpd.hasOwnProperty(setting.group_name)) tpd[setting.group_name] = [];
			tpd[setting.group_name].push(setting);
		}
	}
	tpd.state = ab.stateList;
	$("#contentPanel_account").snippet("account_panel", tpd);
	tk.rmi_getReturnAddress(this.displayAccountPanel_cb);*
} */


UI.prototype.displayAccountPanel_cb = function(e) {
    if(!ui.ro(e)) return(false);
    $.fillForm(e.data, "return_address__");
}	
	
UI.prototype.lockInterface = function(inMessage, inTimeout) {
    $.modal(inMessage);
}

/**
 * btnElements
 * text, title, onclick
 */
UI.prototype.btnNewLTRSml = function(btnElements) {
    btnElements.tblClass = "btnTblNewLtrSML";
    return($.snippet("button", btnElements));
}

UI.prototype.btnClrSml = function(btnElements) {
    btnElements.tblClass = "btnTblClrSML";
    return($.snippet("button", btnElements));
}

UI.prototype.btnRedLrg = function(btnElements) {
    btnElements.tblClass = "btnTblRedLRG";
    return($.snippet("button", btnElements));
//{text:"new contact", title:"create a new contact", onclick:"$.modal.close(); setTimeout('ab.newContactEditor()', 560)"}
}

UI.prototype.btnRedMed = function(btnElements) {
    btnElements.tblClass = "btnTblRedMED";
    return($.snippet("button", btnElements));
}

UI.prototype.btnRedSml = function(btnElements) {
    btnElements.tblClass = "btnTblRedSML";
    return($.snippet("button", btnElements));
}

UI.prototype.btnGryLrg = function(btnElements) {
    btnElements.tblClass = "btnTblGreyLRG";
    return($.snippet("button", btnElements));
}

UI.prototype.btnGryMed = function(btnElements) {
    btnElements.tblClass = "btnTblGreyMED";
    return($.snippet("button", btnElements));
}

UI.prototype.btnGrySml = function(btnElements) {
    btnElements.tblClass = "btnTblGreySML";
    return($.snippet("button", btnElements));
}

UI.prototype.btnGryYelSml = function(btnElements) {
    btnElements.tblClass = "btnTblGryYelSML";
    return($.snippet("button", btnElements));
}

UI.prototype.escapeHTML = function (inString) {                                       
    return(                                                                 
        inString.replace(/&/g,'&amp;').                                         
        replace(/>/g,'&gt;').
        replace(/</g,'&lt;').
        replace(/"/g,'&quot;').
        replace(/'/g,'&#44;')
        );
};

/**
 *	Creates pager HTML with small grey buttons
 */
UI.prototype.pagerGrySml = function(pgrElements, bNeedsConfig) {	
    pgrElements.tblClass = "btnTblGreySML"
    if(typeof(bNeedsConfig) == "boolean" && bNeedsConfig) {
        pgrElements = ui.paginationData(pgrElements.pageNum, pgrElements.pageLen, pgrElements.count, pgrElements);
    }
    if(pgrElements.pageCount < 2) return "";
    return($.snippet("pager__buttons", pgrElements));
}

/**
 * inGroupID
 * inButtonList =   ['button1 text', 'button2 text', 'button3 text'.....]
 *                  or
 *                  [   {text:'button1 text, funcVal:'onclick value', onclick:'some string to be appended to the onclick button event'},
 *                      {same for button 2..... etc }
 *                  ]
 * inConfig =       {func: fully qualified name of function to call onclick (will be populated by either button text, or funcVal if available)
 *			   btnClass: class of all buttons when not active
 *			   btnClassActive: class of button that is active
 *			   defaultBtn: number or object - (the index in inButtonList or the object in inButtonList)
 *				}
 */
UI.prototype.btnGroup = function(inGroupID, inButtonList, inConfig) {
    inConfig = $.extend({
        btnClass:"btnTblClrSML",
        btnClassActive:"btnTblGreySML",
        defaultBtn:10
    }, inConfig);
    //$.log(typeof inButtonList, true);
    if(typeof inButtonList != "object" || inButtonList.constructor != Array ) inButtonList = [10, 20, 40];
    var outstr      = "<div id='btnGroup_" + inGroupID + "' style='display:inline;'>";
    var onClick     = "";
    var onClickVal  = "";
    var buttonText  = "";
    var buttonTitle = "";
    for(var i =0; i < inButtonList.length; i++) {
        buttonText = inButtonList[i];
        onClickVal = "'" + inButtonList[i] + "'";
        if(typeof inButtonList[i] == "object") {
            buttonText  = inButtonList[i].text;
            buttonTitle  = inButtonList[i].title;
            onClickVal  = (inButtonList[i].funcVal)? "'" + inButtonList[i].funcVal + "'" : "'" + buttonText + "'";
        }
        onClick  = (inButtonList[i].onclick)? inButtonList[i].onclick + ";" : "";
        onClick  += "ui.btnGroupSetActiveBtn('" + inGroupID + "', '" + i + "', '" + inConfig.btnClass + "','" + inConfig.btnClassActive + "');";

        if(inConfig.func) onClick += inConfig.func + "(" + onClickVal + ");";
        if(typeof inConfig.defaultBtn == "number") {
            var btnClass = (i == inConfig.defaultBtn)? inConfig.btnClassActive : inConfig.btnClass;
        } else {
            var btnClass = (inButtonList[i] == inConfig.defaultBtn)? inConfig.btnClassActive : inConfig.btnClass;
        }
        outstr += $.snippet("button", {
            id:"btnGroup_" + inGroupID + "_" + i,
            tblClass:btnClass,
            text:buttonText,
            title:buttonTitle,
            onclick:onClick
        }) + " ";
    }
    return outstr += "</div>";
}

/**
 * Sets the 'active' button for a button group
 */
UI.prototype.btnGroupSetActiveBtn = function(inGroupID, inBtnID, inDefaultClass, inActiveClass) {
    $("#btnGroup_" + inGroupID + " table").attr("className", inDefaultClass);
    $("#btnGroup_" + inGroupID + "_" + inBtnID).attr("className", inActiveClass);
}


/**
 * Creates and displays a modal
 * @param inTPLName     name of the template to be placed within the modal's content area
 * @param inTPE         {} to be used populating the template
 *                      special, optional, attributes are
 *                      'modalTitle'    title for the modal, placed in upper left corner
 *                      'modalTopRight' controls for the top right corner of the modal.. default close icon is replaced by these.
 * @param inConfigName  name of the configuration to be used when creating the modal (configs are located in objects.js)
 */
UI.prototype.modal = function(inTPLName, inTPE, inConfigName) {
    //$.log("calling modal with tplname:" + (inTPLName)?inTPLName: this.modalArgs.tplName);
    if(typeof inConfigName == "undefined") inConfigName = "defaultConfig";
    clearTimeout(this.modalTimeout);
    if(typeof(inTPLName) != "undefined") {
        this.modalArgs = {
            tplName:inTPLName,
            tpE:inTPE,
            configName:inConfigName
        };
    }
    if(!this.modalArgs) {
        $.log("ui.modal called with no arguments. ui.modalArgs is not populated with arguments. no modal will be shown");
        return;
    }
    
    var modalConfig = this.modalConfig(this.modalArgs.configName);
    var tplE = {
        "content":$.snippet(this.modalArgs.tplName, this.modalArgs.tpE),
        "title":(typeof this.modalArgs.tpE.modalTitle == "string")? this.modalArgs.tpE.modalTitle : "",
        "topRight":(typeof this.modalArgs.tpE.modalTopRight == "string")? this.modalArgs.tpE.modalTopRight : null
    };


    if($("#modal-container:visible").length > 0) {
        $('#modalTitle').fadeOut(100);
        $('#modalContent').fadeOut(50, function(){
            this.innerHTML = "" ;
            var left = parseInt((ui.settings.bodyWidth - modalConfig.containerCss.width) /2);
            $("#modal-container").animate({
                width:modalConfig.containerCss.width,
                height:modalConfig.containerCss.height,
                "left":left
            }, 500, function() {
                $("#modalTopRight").snippet("modal__top_right", tplE);
                $('#modalTitle').html(tplE.title).fadeIn(100, function() {
                    $("#modalContent").css("height", parseInt($(".modal-content").innerHeight()) - 4).css("width", parseInt($(".modal-content").innerWidth()) - 8);
                    if(ui.modalArgs.tplName == "modal__lman__edit_recipients") $('#modalContent').css("overflow-y", "scroll");
                    else $('#modalContent').css("overflow-y", "hidden");
                    $('#modalContent').html(tplE.content).fadeIn(100);
                }
            );
            });
        });
        //$.log(modalConfig, true);
    } else {
        $.modal($.snippet("modal__wrapper", tplE), this.modalConfig(this.modalArgs.configName));
        $("#modal-container").draggable({
            cancel:"#modalContent"
        });
        /*
        switch(this.modalArgs.tplName) {
            case "modal__lman__edit_recipients" :
                $.log("trying to make the modal scroll")
                $("#modalContent").css("overflow-y", "scroll");
        }*/
    }
        if(this.modalArgs.tpE.modalOverflowY) {
            $("#modalContent").css("overflow-y", "scroll");
        } else {
            $("#modalContent").css("overflow-y", "");
        }

    this.modalArgs = false;
}


//UI.prototype.modalScrollY = funciton(set)

/**
 *  Launches the account settings panel
 *  @param string inConfig  if string then is the panel name to display initially
 */
UI.prototype.modalAccount = function(inConfig) {
    alert("ui.modalAccount() is a bad function call. use modalAccount in tk");
}


UI.prototype.modalClose = function(inTime) {
    inTime = (typeof(inTime) == "number")?inTime:0
    setTimeout(function() {$.modal.close()}, inTime);
}


UI.prototype.modalConfig = function(inConfigName) {
    if(typeof(modalConfig[this.setting("layoutRez")][inConfigName]) == "undefined")
        $.log("ui.modalConfig: modalConfig" + this.setting("layoutRez") + "." + inConfigName + "is undefined");
    return(modalConfig[this.setting("layoutRez")][inConfigName]);
}


UI.prototype.modalContactUs = function() {
    ui.modal('modal__contact_us', {
        modalTitle:"Contact Us"
    }, "contact");
    if(tk.loggedIn() && !tk.driveBy()) {
        setTimeout(function() {
            $("#contact_us__email").val(tk.userData.email)
            }, 1000);
    }
}


UI.prototype.modalSetContent = function(data) {
    if(typeof data == "object") {
        $("#modalTitle").html(data.modalTitle);
        $("#modalContent").html(data.content);
        if(data.modalOverflowY) {
            $("#modalContent").css("overflow-y", "scroll");
        } else {
            $("#modalContent").css("overflow-y", "");
        }
    } else 
        $("#modalContent").html(data);
}


/**
 * inMessage = message for the login section of this modal (for registration)
 */
UI.prototype.modalLogin = function(inMessage) {
    tpE = {
        state:ab.stateList
        }
    if(typeof(inMessage) == "string") tpE.message = inMessage;
    tpE.modalTitle = "<span style='color:white'>Log In</span>"
    this.modal("modal__login", tpE, "login");
    $("#login__email").listen({keyCode:{
        13:function() {
            $('#login__password').focus();
        }
        }});
    $("#login__password").listen({keyCode:{
        13:function() {
            if(($('#login__password').val().length > 0) && $('#login__email').val().length > 0) tk.login();
        }
        }});
    $("#modal_register_return_address_title").tooltip({
        position:["center", "right"]
        });
}
/**
 * inMessage = message for the login section of this modal (for registration)
 */
/*
UI.prototype.modalLoginRegister = function(inMessage) {
    tpE = {state:ab.stateList}
    if(typeof(inMessage) == "string") tpE.message = inMessage;
    tpE.modalTitle = "<span style='color:white'>Log In</span>"
    this.modal("modal_login_register", tpE, "loginRegister");
    $("#login__email").listen({13:function() {
            $('#login__password').focus();
    }});
    $("#login__password").listen({13:function() {
            if(($('#login__password').val().length > 0) && $('#login__email').val().length > 0) tk.login();
    }});
    $("#modal_register_return_address_title").tooltip({position:["center", "right"]});
} */
/**
 * inMessage = message for the login section of this modal (for registration)
 */
UI.prototype.modalRegister = function(inMessage) {
    tpE = {
        state:ab.stateList
        }
    if(typeof(inMessage) == "string") tpE.message = inMessage;
    tpE.modalTitle = "<span style='color:white'>Register</span>"
    this.modal("modal__register", tpE, "register");
    $("#register__password0").listen({keyCode:{
        13:function() {
            $('#register__password1').focus();
        },
        onFailed:function() {
            if($('#register__password0').val().length < slVars.passwordMinLen) {
                $("#register__password0_message").snippet("txt_error", {val:"&nbsp;short"});
            } else {
                $("#register__password0_message").snippet("txt_ok", {val:"&nbsp;good"});
            }
        }
        }});
    $("#register__password1").listen({keyCode:{
        13:function() {
            if($('#register__password1').val() == $('#register__password1').val()) $('#register__address__name').focus();
        },
        onFailed:function() {
            if($('#register__password0').val().substring(0, $('#register__password1').val().length) != $('#register__password1').val()) {

                $("#register__password1_message").snippet("txt_error", {val:"&nbsp;wrong"});
            } else if( ($('#register__password0').val() == $('#register__password1').val()) && ($('#register__password1').val().length >= slVars.passwordMinLen) ){
                $("#register__password1_message").snippet("txt_ok", {val:"&nbsp;ok"});
            } else {
                $("#register__password1_message").html("");
            }
        }
        }});
    $("#modal_register_return_address_title").tooltip({
        position:["center", "right"]
        });
}


UI.prototype.ni = function(inMessage) {
    if(typeof(inMessage) == undefined) {
        inMessage = "";
    }
    ui.statusMessage(inMessage + " not implemented", "error");
}


UI.prototype.paginationData = function(inPageNum, inPageLen, inCount, inExtra) {
    var data = {};
    if(typeof(inExtra) != "undefined") {
        data = inExtra;
    }
    data.pageCount = Math.ceil(inCount/inPageLen);
	
    data.pageNumList = [];
    for(var i = 0; i < data.pageCount; i++) {
        var li = {
            "num":i
        };
        if(i == inPageNum) li.selected = 'selected';
        data.pageNumList.push(li);
    }
	
    data.prev = 0;
    if((inPageNum - 1) > -1) {
        data.prev = inPageNum - 1;
    }
	
    data.next = data.pageCount - 1;
    if((inPageNum + 1) < data.next) {
        data.next = inPageNum + 1;
    }
    return(data);
}

UI.prototype.calculateLayoutSettings = function() {
    if(!this.hasOwnProperty("settings")) this.settings = {};
    this.settings.bodyWidth = parseInt($("body").innerWidth());
    this.settings.bodyHeight = parseInt($("body").innerHeight());
    this.settings.layoutRez = "hirez";
    if(this.settings.bodyHeight < 768) {
        this.settings.layoutRez = "lorez";
    }

    //$.log("layoutrez: " +this.layoutRez);
    if(this.settings.layoutRez == "lorez") {
        //this.settings.headerHeight = 125; //old, easy version
        this.settings.headerHeight = (Math.floor(this.settings.bodyHeight * .162) > this.config.minBodyHeaderHeight)? Math.floor(this.settings.bodyHeight * .162): this.config.minBodyHeaderHeight;
    } else {
        this.settings.headerHeight = 165;
    }
    this.settings.fckHeight = this.settings.bodyHeight - (this.settings.headerHeight + 22 + 20);
}

UI.prototype.setClass = function(inContext, inClassExt) {
    switch(inContext)  {
        case "home_scrollbox" :
            $("#home_scrollboxNavi").attr("className", "home_scrollboxNavi_" + inClassExt);
            $("#home_scrollable").attr("className", "home_scrollbox_" + inClassExt);
            break;
        default :
            $("#" + inTarget).attr("className", inClassExt);
    }
}

UI.prototype.setting = function(inName) {
    return(this.settings[inName]);
}

UI.prototype.resizeLayout = function() {
    this.calculateLayoutSettings();
	
    $("#headerTD").css("height", this.setting("headerHeight"));
    var smLeft = parseInt((this.setting("bodyWidth") - 350)/2) + 55;
    $("#statusMessagePanel").css("left", smLeft); //the number is set in main.css #statusMessage declaration

    //set fck editor area heights
    for(var i in letterControl) if(letterControl.hasOwnProperty(i)) {
        //var letterPanelHeight = $("#editLetter_panelContent_" + i).innerHeight();
        $("#fck_" + i + "___Frame").css("height", this.setting("fckHeight"));
    }
}

UI.prototype.ro = function(e, messageAlways) {
    if(!e.ok) {
        if(e.data != "notloggedin")
            this.statusMessage(e.message, "warning");
        else {
            this.statusMessage(e.message, "warning");
            this.modalLogin();
        }
    } else if (typeof(messageAlways) != "undefined" && messageAlways == true) {
        this.statusMessage(e.message);
    }
    return(e.ok);
}


UI.prototype.setLoginElements = function() {
    //tk.log("UI.prototype.setLoginElements needs some reworking!");
    //tk.log(tk.userType());
    switch(tk.userType()) {
        case "nouser" :
        case "driveby" :
            $("#topMenu").snippet("topMenu_loggedOut");
            break;
        case "loggedin" :
            this.statusMessage("you are logged in", "green");
            $("#topMenu").snippet("topMenu_loggedIn");
    }
}

UI.prototype.htmlTimeout = function(inID, inHTML, inPlacement, inTimeout) {
    this.vars.htmlTimeout[inID] = {
        html:inHTML,
        placement:inPlacement,
        delay: (typeof inTimeout == "number")? inTimeout : 500
    }
    this.htmlTimeoutCall(inID);
}

UI.prototype.htmlTimeoutCall = function(inID) {
    if(!this.vars.htmlTimeout.hasOwnProperty(inID)) return;

    if($(inID).length > 0) {
        switch(this.vars.htmlTimeout[inID].placement) {
            case "html" :
                $(inID).html(this.vars.htmlTimeout[inID].html);
                break;
            case "append" :
                $(inID).append(this.vars.htmlTimeout[inID].html);
                break;
            case "prepend" :
                $(inID).prepend(this.vars.htmlTimeout[inID].html);
                break;
        }
        delete this.vars.htmlTimeout[inID];
    } else {
        this.vars.htmlTimeout[inID].timeout = setTimeout("ui.htmlTimeoutCall('" + inID + "')", this.vars.htmlTimeout[inID].delay);
    }
}





UI.prototype.statusMessage = function(e, inConfig) {
    if(e == false) {
        $('#statusMessagePanel').fadeOut(500);
        return(true);
    }
    var obj = {
        "message":e
    };
    switch(typeof(inConfig)) {
        case "string" :
            inConfig = {
                messageType:inConfig
            };
            break;
        case "undefined" :
            inConfig = {
                messageType:"default"
            };
    }
        
    if(this.statusMessageTimeout) {
        clearTimeout(this.statusMessageTimeout);
    }
    switch(inConfig.messageType) {

        case "error" :
            obj.cssClass = "error";
            $("#statusMessagePanel").snippet("status_message_default", obj).fadeIn(300);
            this.statusMessageTimeout = setTimeout("$('#statusMessagePanel').fadeOut(1500);", 4000);
            break;
        case "green" :
            obj.cssClass = "green";
            $("#statusMessagePanel").snippet("status_message_default", obj).fadeIn(300);
            this.statusMessageTimeout = setTimeout("$('#statusMessagePanel').fadeOut(750);", 3000);
            break;
        case "warning" :
            obj.cssClass = "warning";
            $("#statusMessagePanel").snippet("status_message_default", obj).fadeIn(300);
            this.statusMessageTimeout = setTimeout("$('#statusMessagePanel').fadeOut(750);", 3000);
            break;
        default :
            obj.cssClass = "default";
            $("#statusMessagePanel").snippet("status_message_default", obj).fadeIn(300);
            this.statusMessageTimeout = setTimeout("$('#statusMessagePanel').fadeOut(750);", 2000);
    }
    delete(obj);
	
}


UI.prototype.stopPropagation = function(e) {
    //$.log($.inspect(e));
    if (e.stopPropagation) {
        e.stopPropagation();
    } else {
        e.cancelBubble = true;
    }
}

UI.prototype.window = function(windowName, opts) {
    if(this.windows.hasOwnProperty(windowName)) {
        var win = this.windows[windowName];
        //if(!win.winObj || win.) {
        var urlExtra = "";
        var url = win.url;
        switch(typeof opts) {
            case "string" :
                urlExtra = opts;
                break;
            case "object" :
                if(opts.hasOwnProperty("url")) url = opts.url;
                if(opts.hasOwnProperty("urlExtra")) urlExtra = opts.urlExtra;
                break;
        }
        win.winObj = window.open(url + urlExtra, windowName, win.opts);
        if(win.winObj == null || typeof(win.winObj) == "undefined") {
            alert("You must allow popups to continue.");
            win.winObj = false;
        }
        return win.winObj;
    }
    //$.log("couln't find" + windowName, "ui.window");
    return(false);
}
