// jQuery Genome ID Selection Helper Plugin
jQuery.genome = function(jel_selector, jel_scope) {
    if(typeof(jel_selector) != 'string') {
        return jQuery(jel_selector);
    }

    jel_selector = jel_selector.replace(/\[/g, '\\[').replace(/\]/g, '\\]');

    var useScope = false;
    if(typeof(jel_scope) != 'undefined') {
        useScope = true;
    }

	var jel = null;
    if(useScope) {
        jel = jQuery(jel_selector, jel_scope);
    } else {
        jel = jQuery(jel_selector);
    }

	if(! jel.size() && typeof(jel_selector) == 'string') {
        if(jel_selector.substr(0, 1) != '#') {
            if(useScope) {
		        jel = jQuery('#' + jel_selector, jel_scope);
            } else {
                jel = jQuery('#' + jel_selector);
            }
        }
	}
	return jel;
};

// Ensure that the MMI "namespace" is available.
if(typeof(window['MMI']) === 'undefined') {
    window.MMI = {};
}

if(typeof(window.MMI['Exception']) === 'undefined') {
	window.MMI.Exception = function(message, code, method) {
		if(typeof(code) == 'undefined') {
			code = 0;
		}
		if(typeof(method) == 'string') {
			return 'MMI.Exception [' + code + '] ' + message + ' in ' + method;
		} else {
			return 'MMI.Exception [' + code + '] ' + message + ' in `unknown function`';
		}
	};
}

if(typeof(window.MMI['Util']) === 'undefined') {
    window.MMI.Util = {};
}

MMI.Util = {
    config: {
		dump: true
		,traceDumps: true
        ,log: true
        ,traceLogs: false
        ,trace: true
        ,group: true
        ,collapseGroups: true
        ,_hasConsole: false
        ,_hasConsoleDump: false
        ,_hasConsoleLog: false
        ,_hasConsoleTrace: false
        ,_hasConsoleGroup: false
        ,_hasConsoleGroupCollapsed: false
        ,_defaultArgumentDetailLevel: 2
    }
    ,jsDir: ''
    ,imageDir: ''
    ,cssDir: ''
    ,_reservations: {
        'locks': {},
        'oldValues': {}
    }
    // scope for now is always window - later I would like to set it up where you can reserve a variable name in any scope.
    /** Reserve
     *
     * @param string varName The name of the variable to reserve.
     */
    ,reserve: function(varName) {
        if(typeof(MMI.Util._reservations['locks'][varName]) == 'undefined') {
            MMI.Util._reservations['locks'][varName] = true;
            MMI.Util._reservations['oldValues'][varName] = window[varName];
            window[varName] = undefined; // Set this value to a clean value.
        } else {
            throw MMI.Exception('The variable `' + varName + '` is already reserved. Please release the reservation before using this variable name.', 0, 'MMI.Util.reserve()');
        }
    }
    ,release: function(varName) {
        if(typeof(MMI.Util._reservations['locks'][varName]) != 'undefined' && MMI.Util._reservations['locks'][varName] === true) {
            window[varName] = MMI.Util._reservations['oldValues'][varName];
            MMI.Util._reservations['locks'][varName] = undefined;
            MMI.Util._reservations['oldValues'][varName] = undefined;
        }
    }
	,__init: function() {
		MMI.Util.config._hasConsole = (typeof(window.console) != 'undefined' && window.console != null && window.console);
		if(MMI.Util.config._hasConsole) {
			MMI.Util.config._hasConsoleDump = MMI.Util.isFunction(window.console.dir);
			MMI.Util.config._hasConsoleLog = MMI.Util.isFunction(window.console.log);
			MMI.Util.config._hasConsoleTrace = MMI.Util.isFunction(window.console.trace);
			MMI.Util.config._hasConsoleGroup = (MMI.Util.isFunction(window.console.group) && MMI.Util.isFunction(window.console.groupEnd));
			MMI.Util.config._hasConsoleGroupCollapsed = (MMI.Util.isFunction(window.console.groupCollapsed) && MMI.Util.isFunction(window.console.groupEnd));
		}

        MMI.Util.jsDir = MMI.Util.__DIR__();
        MMI.Util.imageDir = MMI.Util.jsDir + '/utilities/images';
        MMI.Util.cssDir = MMI.Util.jsDir + '/utilities/css';
	}
    ,__FILE__: function() {
        var tag = document.getElementsByTagName('script');
        var url = tag[tag.length-1];
        if ('src' in url && url.src != '') {
            return url.src;
        } else {
            return window.location.href;
        }
    }
    /* Behaves like the __DIR__ magic constant in PHP. */
    ,__DIR__: function() {
        return MMI.Util.dirname(MMI.Util.__FILE__());
    }
    ,dirname: function(path) {
        return path.replace(/\\/g, '/').replace(/\/[^\/]*\/?$/, '');
    }
    ,basename: function(path, suffix) {
        var b = path.replace(/^.*[\/\\]/g, '');
        if (typeof(suffix) == 'string' && b.substr(b.length-suffix.length) == suffix) {
            b = b.substr(0, b.length-suffix.length);
        }
        return b;
    }
    ,_strrchr: function(haystack, needle) {
        var pos = 0;

        if (typeof needle !== 'string') {
            needle = String.fromCharCode(parseInt(needle, 10));
        }
        needle = needle.charAt(0);
        pos = haystack.lastIndexOf(needle);
        if (pos === -1) {
            return false;
        }

        return haystack.substr(pos);
    }
    ,substringReplace: function(str, replace, start, length) {
        if(start < 0) { // start position in str
            start = start + str.length;
        }
        length = length !== undefined ? length : str.length;
        if(length < 0) {
            length = length + str.length - start;
        }
        return str.slice(0, start)+replace.substr(0, length)+replace.slice(length)+str.slice(start+length);
    }
    ,'stringReplace': function(search, replace, subject, count) {
        var i = 0, j = 0, temp = '', repl = '', sl = 0, fl = 0,
                f = [].concat(search),
                r = [].concat(replace),
                s = subject,
                ra = r instanceof Array, sa = s instanceof Array;
        s = [].concat(s);
        if (count) {
            this.window[count] = 0;
        }

        for (i=0, sl=s.length; i < sl; i++) {
            if (s[i] === '') {
                continue;
            }
            for (j=0, fl=f.length; j < fl; j++) {
                temp = s[i]+'';
                repl = ra ? (r[j] !== undefined ? r[j] : '') : r[0];
                s[i] = (temp).split(f[j]).join(repl);
                if (count && s[i] !== temp) {
                    this.window[count] += (temp.length-s[i].length)/f[j].length;}
            }
        }
        return sa ? s : s[0];
    }
    ,getFileExtension: function(path) {
        return MMI.Util._strrchr(path, '.');
    }
    ,inArray: function(needle, haystack, argStrict) {
        var key = '', strict = !!argStrict;

        if (strict) {
            for (key in haystack) {
                if (haystack[key] === needle) {
                    return true;
                }
            }
        } else {
            for (key in haystack) {
                if (haystack[key] == needle) {
                    return true;
                }
            }
        }
        return false;
    },
    'arrayMerge': function() {
        var args = Array.prototype.slice.call(arguments), retObj = {}, k, j = 0, i = 0, retArr = true;

        for (i=0; i < args.length; i++) {
            if (!(args[i] instanceof Array)) {
                retArr=false;
                break;
            }
        }

        if (retArr) {
            retArr = [];
            for (i=0; i < args.length; i++) {
                retArr = retArr.concat(args[i]);
            }
            return retArr;
        }
        var ct = 0;

        for (i=0, ct=0; i < args.length; i++) {
            if (args[i] instanceof Array) {
                for (j=0; j < args[i].length; j++) {
                    retObj[ct++] = args[i][j];
                }
            } else {
                for (k in args[i]) {
                    if (args[i].hasOwnProperty(k)) {
                        if (parseInt(k, 10)+'' === k) {
                            retObj[ct++] = args[i][k];
                        } else {
                            retObj[k] = args[i][k];
                        }
                    }
                }
            }
        }
        return retObj;
    }
    ,includeCss: function(url) {
        jQuery('head').append('<link rel="stylesheet" type="text/css" href="'+url+'" />');
    }
    /*
     * Borrowed straight from Scriptaculous.
     */
    ,includeJs: function(url) {
        jQuery('head').append('<script type="text/javascript" src="' + url + '"><\/script>');
    }
    ,isChecked: function(jel) {
        return jel.is(':checked');
    }
    ,trim: function(str, charlist) {
        // version: 1006.1915
        var whitespace, l = 0, i = 0;
        str += '';

        if (!charlist) {
            // default list
            whitespace = " \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000";
        } else {
            // preg_quote custom list
            charlist += '';
            whitespace = charlist.replace(/([\[\]\(\)\.\?\/\*\{\}\+\$\^\:])/g, '$1');
        }

        l = str.length;
        for (i = 0; i < l; i++) {
            if (whitespace.indexOf(str.charAt(i)) === -1) {
                str = str.substring(i);
                break;
            }
        }

        l = str.length;
        for (i = l - 1; i >= 0; i--) {
            if (whitespace.indexOf(str.charAt(i)) === -1) {
                str = str.substring(0, i + 1);
                break;
            }
        }

        return whitespace.indexOf(str.charAt(0)) === -1 ? str : '';
    }
	,_hasConsole: function() {
		return MMI.Util.config._hasConsole;
	}
	,_hasConsoleDump: function() {
		return MMI.Util.config._hasConsoleDump;
	}
	,_hasConsoleLog: function() {
		return MMI.Util.config._hasConsoleLog;
	}
	,_hasConsoleTrace: function() {
		return MMI.Util.config._hasConsoleTrace;
	}
	,_hasConsoleGroup: function() {
		return MMI.Util.config._hasConsoleGroup;
	}
	,_hasConsoleGroupCollapsed: function() {
		return MMI.Util.config._hasConsoleGroupCollapsed;
	}
	,_getConsoleActionMode: function(argLen, outputType) {
		if(argLen && MMI.Util.config[outputType] && MMI.Util._hasConsole()) {
			if(argLen > 1 && MMI.Util.config.group && MMI.Util._hasConsoleGroup()) {
				return 2; // Use grouping
			}
			return 1; // No grouping
		}
		return false; // Do not log to console
	}
    ,_getDefaultArgumentDetailLevel: function() {
        return MMI.Util.config._defaultArgumentDetailLevel;
    }
	,group: function(groupLabel) {
		if(MMI.Util.config.group && MMI.Util._hasConsoleGroup()) {
			if(MMI.Util.config.collapseGroups && MMI.Util._hasConsoleGroupCollapsed()) {
				console.groupCollapsed(groupLabel);
			} else {
				console.group(groupLabel);
			}
		}
	}
	,groupEnd: function() {
		if(MMI.Util.config.group && MMI.Util._hasConsoleGroup()) {
			console.groupEnd();
		}
	}
	,dump: function() {
		var mode = MMI.Util._getConsoleActionMode(arguments.length, 'dump');
		if(mode) {
			var doTrace = false;
			if(MMI.Util.config.traceDumps && MMI.Util._hasConsoleTrace()) {
				doTrace = true;
			}
			switch(mode) {
				case 1:
					if(doTrace) {
						MMI.Util.group('Dump Trace:');
					}
					var arg = arguments[0];
					if(typeof(arg) == 'object') {
						console.dir(arg);
					} else {
						console.log(arg);
					}
					if(doTrace) {
						console.trace();
						MMI.Util.groupEnd();
					}
					break;
				case 2:
					if(doTrace) {
						MMI.Util.group('Dump Trace:');
					}
					MMI.Util.group('Dump to console:');
					for(var key=0; key < arguments.length; key++) {
						var arg = arguments[key];
						if(typeof(arg) == 'object') {
							console.dir(arg);
						} else {
							console.log(arg);
						}
					}
					MMI.Util.groupEnd();
					if(doTrace) {
						console.trace();
						MMI.Util.groupEnd();
					}
					break;
			}
		}
	}
	,dumpXml: function() {

	}
	,plainLog: function() {
		var originalSetting = MMI.Util.config.traceLogs;
		MMI.Util.config.traceLogs = false;
		MMI.log.apply(this, arguments);
		MMI.Util.config.traceLogs = originalSetting;
	}
    ,'log': function() {
        var mode = MMI.Util._getConsoleActionMode(arguments.length, 'log');
    	if(mode) {
            // ignore actual mode value - we don't do grouping for this method.
            if(MMI.Util._hasConsoleLog() && arguments.length) {
                for(var i=0; i < arguments.length; i++) {
                    console.log(arguments[i]);
                }
            }
        }
    }
    /**
     *
     * @param args
     * @param detailLevel
     * @param methodName
     */
    ,'logArgs': function(args, detailLevel, methodName) {
        var mode = MMI.Util._getConsoleActionMode(arguments.length, 'log');
    	if(mode) {
            if(MMI.Util._hasConsoleLog() && typeof(args) != 'undefined' && typeof(args['callee']) != 'undefined' && typeof(args['length']) != 'undefined') {
                methodName = methodName ? methodName : 'unknown';
                // Log the callee
                var callee = args.callee.toString();
                detailLevel = (typeof(detailLevel) == 'number') ? detailLevel : MMI.Util._getDefaultArgumentDetailLevel;
                if(detailLevel > 0) {
                    console.log(methodName + callee.slice(9, callee.indexOf(')')+1) + ' :: Received [' + args.length + '] Arguments');
                    if(detailLevel > 1) {
                        console.log(args);
                    }
                } else {
                    // Modify args.callee and log it
                    console.log(methodName + callee.slice(9, callee.indexOf('(')) + ' :: Received [' + args.length + '] Arguments');
                }
            }
        }
    }
    ,glog: function() {
    	var mode = MMI.Util._getConsoleActionMode(arguments.length, 'log');
    	if(mode) {
    		var doTrace = false;
			if(MMI.Util.config.traceLogs && MMI.Util._hasConsoleTrace()) {
				doTrace = true;
			}
			switch(mode) {
				case 1:
					if(doTrace) {
						MMI.Util.group('Log Trace:');
					}
					console.log(arguments[0]);
					if(doTrace) {
						console.trace();
						MMI.Util.groupEnd();
					}
					break;
				case 2:
					if(doTrace) {
						MMI.Util.group('Log Trace:');
					}
					MMI.Util.group('Console Log Block:');
					for(var key=0; key < arguments.length; key++) {
						console.log(arguments[key]);
					}
					MMI.Util.groupEnd();
					if(doTrace) {
						console.trace();
						MMI.Util.groupEnd();
					}
					break;
			}
    	}
    }
    ,trace: function() {
    	if(MMI.Util.config.trace && MMI.Util._hasConsoleTrace()) {
    		// Use console.log() for any arguments supplied directly to this method
    		var groupped = false;
    		if(MMI.Util.config.group && MMI.Util._hasConsoleGroup()) {
    			groupped = true;
    			console.group('Stack trace:');
    		}
    		MMI.Util.log.apply(this, arguments);
    		console.trace(); // Do trace output.
    		if(groupped) {
    			console.groupEnd();
    		}
    	}
    },
    triggerDownload: function(url) {
        clog('MMI.Util.triggerDownload('+url+')');
        if(arguments.length > 1) {
            var itemId = arguments[0];
            var fieldName = arguments[1];
            url = '/download/'+itemId+'/'+fieldName;
        }
        MMI.Util.browserPopup(url, 'mmi_download_'+MMI.Util.getRandomString(), 10, 10, {
            resizable: 0,
            menubar: 0,
            toolbar: 0,
            status: 0,
            titlebar: 0,
            scrollbars: 0,
            fullscreen: 0
        });
        jQuery(window).focus();
    },
    browserPopup: function(url, window_name, width, height, x_pos, y_pos, options) {
		if(!options) {
			options = {
				resizable: 1,
				menubar: 1,
				toolbar: 1,
				status: 1,
				titlebar: 1,
				scrollbars: 1,
				fullscreen: 0
			};
		}
		// If illegal values are set for the top and left positions then center the window.
		if(window.innerWidth) {
			options.width = width || window.innerWidth - 50;
			options.height = height || window.innerHeight - 50;

			if(isNaN(x_pos) || x_pos < 0) {
				options.x_pos = (window.innerWidth - options.width) / 2;
			}

			if(isNaN(y_pos) || y_pos < 0) {
				options.y_pos = ((window.innerHeight - options.height) / 4) - 50;
			}
		}
		else {
			options.width = width || window.screen.width - 50;
			options.height = height || window.screen.height - 50;

			if(isNaN(x_pos) || x_pos < 0) {
				options.x_pos = (parseInt(window.screen.width) - options.width) / 2;
			}
			if(isNaN(y_pos) || y_pos < 0) {
				options.y_pos = ((parseInt(window.screen.height) - options.height) / 2) - 50;
			}
		}

        var key;

		var total_options = 0;
		for(key in options) {
			total_options++;
		}

		var window_attributes_str = '';
		var att_counter = 0;
		for(key in options) {
			value = options[key];
			if(value === true) {
				value = '1';
			}
			else if(value === false || value === null || value === undefined) {
				value = '0';
			}
			window_attributes_str += key + '=' + value;
			if(att_counter < (total_options)) {
				window_attributes_str += ',';
			}
			att_counter++;
		}

        function replaceAll(target, replacement, string) {
            var match_index = string.indexOf(target);
            while(match_index != -1) {
                string = string.replace(target, replacement);
                match_index = string.indexOf(target);
            }
        }

		window_name = window_name ? replaceAll('-', '_', window_name) : '_blank';
		window_name = window_name ? replaceAll(' ', '_', window_name) : '_blank';

		popup_window = window.open(url, window_name, window_attributes_str);

		return popup_window;
	},
    overlay: function(content, options) {
        // @mmi-log MMI.log('MMI.Util.overlay()');
        var overlayId = false;
        var optionsType = typeof(options);
        if(optionsType == 'string') {
            overlayId = options;
        } else if(optionsType == 'object' && typeof(options['overlayId']) == 'string') {
            overlayId = options['overlayId'];
        }
        var overlay = null;
        if(overlayId) {
            overlay = jQuery.genome('#' + overlayId);
            if(overlay.size()) {
                return overlayId;
            }
        } else {
            overlayId = "mmi-utilOverlay-" + MMI.Util.getRandomString();
        }
        // <div class="ui-widget-overlay" style="width: 1848px; height: 932px; z-index: 1008;"></div>

        var settings = {
            'contentSettings': {
                'class': 'mmi-utilOverlayContent'
                ,'width': 300
                ,'height': 0
            }
        };

        if(optionsType == 'object') {
            jQuery.extend(true, settings, options);
        }

        var w = jQuery(window);
        var wWidth = w.width();
        var wHeight = w.height();

        if(content) {
            var contentMode = '';
            var contentType = typeof(content);
            if(contentType == 'undefined') {
                contentMode = 'none';
                content = '';
            } else if (contentType == 'boolean') {
                contentMode = 'default';
                content = '<img src="'+MMI.Util.imageDir+'/infinite-barber-grayscale.gif" />';
            } else {
                contentMode = 'custom';
            }

            // Create the content style
            contentStyle = 'width: ' + settings.contentSettings.width + 'px;';
            if(settings.contentSettings.height) {
                contentStyle = contentStyle + ' height: ' + settings.contentSettings.height + 'px;';
            }
        } else {
            content = '';
            contentStyle = 'display: none;';
        }

        overlay = jQuery('<div id="'+overlayId+'" class="mmi-utilOverlay" style="width: '+wWidth+'px; height: '+wHeight+'px; z-index: 8000;"><div class="mmi-utilOverlayInner"></div><div id="'+overlayId+'_content" class="'+settings['contentSettings']['class']+'" style="'+contentStyle+'">'+content+'</div></div>').appendTo('body');
        var jel_content = jQuery('#' + overlayId + '_content');
        var contentHeight = jel_content.outerHeight(false);
        var contentTopMargin = Math.floor(overlay.outerHeight(false) / 2) - Math.ceil(contentHeight / 2);
        jel_content.css({
            'margin-top': contentTopMargin + 'px'
        });
        return overlayId;
    }
    /**
     * Creates an alert dialog using the jQuery UI Dialog widget.
     *
     * @todo Add ability to provide scope for callbacks
     *
     * @example MMI.Util.alert('<img src="/triangle.png" /> Warning: Sample alert box.', 'Sample Warning', {'Close Sample': function() { jQuery(this).dialog('destroy'); }}, {'width': 400, 'height': 300});
     *
     * By default the alert dialog shows a single button with the text "OK" on
     * it and when clicked, the dialog is automatically closed and destroyed
     * (removed from the DOM).
     *
     * The "buttons" argument accepts any of the following:
     * 		string		=> A custom label for the "OK" button.
     * 		function	=> A custom callback function to run when the "OK"
     * 					   button is clicked.
     * 		object		=> A custom buttons object to pass directly to the
     * 					   jQuery UI dialog "buttons" property.
     *
     * @param string message The string (plain text or HTML) to use for the
     * 						 dialog content.
     * @param string title [optional] The title to use on the alert dialog.
     * 								  (Defaults to "Alert")
     * @param mixed buttons [optional] See the documentation above for details
     * 								   on what to provide for this argument.
     * @param object opts [optional] Properties of this are passed through
     * 								 directly as options to the jQuery UI Dialog
     * 								 widget. Options passed here can override
     * 								 options set up internally within this
     * 								 method. NOTE: dialogData = an object that you want to set as the data on the element use to generate the dialog.
     * @return string The unique ID of the DIV element used to create the jQuery UI Dialog.
     */
    ,alert: function(message, title, buttons, opts) {
    	if(typeof(title) == 'undefined') {
    		title = 'Alert';
    	}
    	// buttons can be an object to pass directly to the jQuery Dialog buttons property or a callback function to use for the OK button or some text to use instead of OK that will use the default callback function.
    	var buttonsObject = {};
    	if(typeof(buttons) != 'object') {
    		var buttonCallback = false;
    		var buttonText = false;
    		if(typeof(buttons) == 'function') {
    			buttonCallback = buttons;
    		} else {
    			if(typeof(buttons) == 'string' || typeof(buttonText) == 'number') {
    				buttonText = buttons;
    			}
    		}
    		if(buttonText === false) {
        		buttonText = 'OK';
        	}
    		if(buttonCallback === false) {
    			buttonCallback = function() { var jel = jQuery(this); jel.dialog('destroy'); jel.remove(); };
    		}
    		buttonsObject[buttonText] = buttonCallback;
    	} else {
    		buttonsObject = buttons;
    	}
    	var randomId = MMI.Util.getRandomString();

    	var dialogOpts = {
			'modal': true,
    	    'resizable': false,
    	    'title': title,
    	    'autoOpen': true,
    	    'buttons': buttonsObject
    	};

    	if(typeof(opts) == 'object') {
    		for(var key in opts) {
    			var value = opts[key];
    			dialogOpts[key] = value;
    		}
    	}

    	var jelDialog = jQuery('<div id="'+randomId+'">'+message+'</div>');
        jelDialog.dialog(dialogOpts);
    	return randomId;
    }
    ,_loginPrompt_buttonLogin: function() {

    }
    ,_loginPrompt_buttonCancel: function() {

    }
    ,_loginPrompt_buttonRegister: function() {
        MMI.Util.alert('NOTE: There is no registration feature available at this time.');
    }
    ,loginPrompt: function(message, title, forceLogin, opts) {
        if(typeof(title) == 'undefined') {
            title = 'Promt';
        }

        var inputId = MMI.Util.getRandomString();

        var loginButton = false;
        var loginLabel = 'Log In';
        var cancelButton = false;
        var cancelLabel = 'Cancel';
        if(forceLogin === true) {
            loginButton = function() {
                var jel = jQuery(this)
                jel.dialog('destroy');
                jel.remove();
            };
        } else {
            loginButton = function() { };
            cancelButton = function() {
                var jel = jQuery(this)
                jel.dialog('destroy');
                jel.remove();
            };
        }

        if(typeof(opts) != 'undefined') {
            if(typeof(opts['beforeclose']) == 'undefined') {
                opts['beforeclose'] = function() {
                    return false; // Do not allow the dialog window to be closed.
                };
            }
        } else {
            var opts = {};
            opts['beforeclose'] = function() {
                return false; // Do not allow the dialog window to be closed.
            };
        }

        var promptMsg = "<div>" + message + "</div><div>Username: <input type='text' id='mmiUtilLoginPromptUsernameBox-"+inputId+"' /></div><div>Password: <input type='text' id='mmiUtilLoginPromptPasswordBox-"+inputId+"' /></div>";

    	var buttons = {};
    	buttons[loginLabel] = loginButton;
        if(cancelButton) {
    	    buttons[cancelLabel] = cancelButton;
        }
    	return MMI.Util.alert(promptMsg, title, buttons, opts);
    }
    // cancelCallback can be false which prevents that buttons from being added to the button stack.
    ,prompt: function(message, title, okCallback, cancelCallback, opts) {

    }
    /**
     * Creates a confirm dialog using the jQuery UI Dialog widget.
     *
     * @example MMI.Util.confirm('Are you sure you want to delete this user?', 'Confirm: Delete User', {'label': 'Confirm Delete', 'function': function() { doDelete(); jQuery(this).dialog('destroy'); }}, null, {'width': 400, 'height': 300});
     *
     * By default the alert dialog shows a single button with the text "OK" on
     * it and when clicked, the dialog is automatically closed and destroyed
     * (removed from the DOM).
     *
     * The "okCallback" argument accepts any of the following:
     * 		string		=> A custom label for the "Cancel" button.
     * 		function	=> A custom callback function to run when the "OK"
						   button is clicked.
     * 		object		=> A custom object with the following properties:
     * 			@prop function function A custom callback function to run when
     * 								    the "OK" button is clicked.
     * 			@prop string label [optional] A custom label for the "OK" button
     *
     * The "cancelCallback" argument accepts any of the following:
     * 		string		=> A custom label for the "Cancel" button.
     * 		function	=> A custom callback function to run when the "Cancel"
     * 					   button is clicked.
     * 		object		=> A custom object with the following properties:
     * 			@prop function function [optional] A custom callback function to
     * 											   run when the "Cancel" button
     * 											   is clicked.
     * 			@prop string label [optional] A custom label for the "Cancel"
     * 										  button.
     *
     * @param string message The string (plain text or HTML) to use for the
     * 						 dialog content.
     * @param string title The title to use on the alert dialog. (Defaults to
     * 					   "Confirm"). Pass null or undefined to skip this argument.
     * @param mixed okCallback See the documentation above for details on what
     * 						   to provide for this argument.
     * @param mixed cancelCallback [optional] See the documentation above for
     * 							              details on what to provide for this argument.
     * @param object opts [optional] Properties of this are passed through
     * 								 directly as options to the jQuery UI Dialog
     * 								 widget. Options passed here can override
     * 								 options set up internally within this
     * 								 method.
     * @return string The unique ID of the DIV element used to create the jQuery UI Dialog.
     */
    ,confirm: function(message, title, okCallback, cancelCallback, opts) {
    	if(typeof(title) == 'undefined') {
    		title = 'Confirm';
    	}

    	var okFunction = false;
    	var okLabel = false;
    	if(okCallback && typeof(okCallback) == 'object') {
    		// Only set the function if it is defined
    		if(typeof(okCallback['function']) == 'function') {
    			okFunction = okCallback['function'];
    		} else {
    			// Throw an exception if there is no OK callback defined.
    			throw MMI.Exception('Requires a custom "OK" callback function', 0, 'MMI.Util.confirm()');
    		}
    		// Only set the label if it is defined
    		if(typeof(okCallback['label']) != 'undefined') {
    			okLabel = okCallback['label'];
    		} else {
    			okLabel = 'OK';
    		}
    	} else if (typeof(okCallback) == 'function') {
    		okFunction = okCallback;
    		okLabel = 'OK';
    	} else {
    		throw MMI.Exception('Requires a custom "OK" callback function', 1, 'MMI.Util.confirm()');
    	}

    	var cancelFunction = false;
    	var cancelLabel = false;
    	if(cancelCallback && typeof(cancelCallback) == 'object') {
    		// Only set the function if it is defined
    		if(typeof(cancelCallback['function']) == 'function') {
    			cancelFunction = cancelCallback['function'];
    		} else {
    			// Set the cancel function to an empty function by default.
    			cancelFunction = function() { var jel = jQuery(this); jel.dialog('destroy'); jel.remove(); };
    		}
    		// Only set the label if it is defined
    		if(typeof(cancelCallback['label']) != 'undefined') {
    			cancelLabel = cancelCallback['label'];
    		} else {
    			cancelLabel = 'Cancel';
    		}
    	} else if (typeof(cancelCallback) == 'function') {
    		cancelFunction = cancelCallback;
    		cancelLabel = 'Cancel';
    	} else if (typeof(cancelCallback) == 'string') {
    		cancelFunction = function() { var jel = jQuery(this); jel.dialog('destroy'); jel.remove(); };
    		cancelLabel = cancelCallback;
    	} else {
    		cancelFunction = function() { var jel = jQuery(this); jel.dialog('destroy'); jel.remove(); };
    		cancelLabel = 'Cancel';
    	}

    	if(okLabel == cancelLabel) {
    		throw MMI.Exception('The labels for both the OK and Cancel buttons are identical', 0, 'MMI.Util.confirm()');
    	}

    	var buttons = {};
    	buttons[okLabel] = okFunction;
    	buttons[cancelLabel] = cancelFunction;
    	return MMI.Util.alert(message, title, buttons, opts);
    }
    ,namespace: function() {
        var o, d, dMod, v, v2, vKey, vKey2;
        for (var vKey=0; vKey < arguments.length; vKey++) {
            v = arguments[vKey];
            d = v.split('.');
            o = window[d[0]] = window[d[0]] || {};
            dMod = d.slice(1);
            for(vKey2 in dMod) {
                v2 = dMod[vKey2];
                o = o[v2] = o[v2] || {};
            }
        }
        return o;
    }
    ,isFunction: function(obj) {
        try {
            return (typeof(obj) === 'function' || toString.call(obj) === "[object Function]");
        } catch (e) {
            return false;
        }
    }
    ,isArray: function(obj) {
        try {
            return ((obj instanceof Array) || (toString.call(obj) === "[object Array]"));
        } catch(e) {
            return false;
        }
    }
    ,stripTags: function(str, allowed_tags) {
        var key = '', allowed = false;
        var matches = [];
        var allowed_array = [];
        var allowed_tag = '';
        var i = 0;
        var k = '';
        var html = '';

        var replacer = function (search, replace, str) {
            return str.split(search).join(replace);
        };

        // Build allowes tags associative array
        if (allowed_tags) {
            allowed_array = allowed_tags.match(/([a-zA-Z0-9]+)/gi);
        }

        str += '';

        // Match tags
        matches = str.match(/(<\/?[\S][^>]*>)/gi);

        // Go through all HTML tags
        for (key in matches) {
            if (isNaN(key)) {
                // IE7 Hack
                continue;
            }

            // Save HTML tag
            html = matches[key].toString();

            // Is tag not in allowed list? Remove from str!
            allowed = false;

            // Go through all allowed tags
            for (k in allowed_array) {
                // Init
                allowed_tag = allowed_array[k];
                i = -1;

                if (i != 0) { i = html.toLowerCase().indexOf('<'+allowed_tag+'>');}
                if (i != 0) { i = html.toLowerCase().indexOf('<'+allowed_tag+' ');}
                if (i != 0) { i = html.toLowerCase().indexOf('</'+allowed_tag)   ;}

                // Determine
                if (i == 0) {
                    allowed = true;
                    break;
                }
            }

            if (!allowed) {
                str = replacer(html, "", str); // Custom replace. No regexing
            }
        }

        return str;
    }
    ,isPlainObject: function(obj) {
        // Must be an Object.
        // Because of IE, we also have to check the presence of the constructor property.
        // Make sure that DOM nodes and window objects don't pass through, as well
        try {
            if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) {
                return false;
            }

            // Not own constructor property must be Object
            if ( obj.constructor
                && !hasOwnProperty.call(obj, "constructor")
                && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf") ) {
                return false;
            }

            // Own properties are enumerated firstly, so to speed up,
            // if last one is own, then all properties are own.

            var key;
            for ( key in obj ) {}

            return key === undefined || hasOwnProperty.call( obj, key );
        } catch(e) {
            return false;
        }
    }
    ,extend: function() {
        // copy reference to target object
        var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy;

        // Handle a deep copy situation
        if ( typeof target === "boolean" ) {
            deep = target;
            target = arguments[1] || {};
            // skip the boolean and the target
            i = 2;
        }

        // Handle case when target is a string or something (possible in deep copy)
        if ( typeof target !== "object" && !this.isFunction(target) ) {
            target = {};
        }

        // If only one argument is passed in, then return that object unmodified.
        if ( length === i ) {
            return target;
        }

        for ( ; i < length; i++ ) {
            // Only deal with non-null/undefined values
            if ( (options = arguments[ i ]) != null ) {
                // Extend the base object
                for ( name in options ) {
                    src = target[ name ];
                    copy = options[ name ];

                    // Prevent never-ending loop
                    if ( target === copy ) {
                        continue;
                    }

                    // Recurse if we're merging object literal values or arrays
                    if ( deep && copy && ( this.isPlainObject(copy) || this.isArray(copy) ) ) {
                        var clone = src && ( this.isPlainObject(src) || this.isArray(src) ) ? src
                            : this.isArray(copy) ? [] : {};

                        // Never move original objects, clone them
                        target[ name ] = this.extend( deep, clone, copy );

                    // Don't bring in undefined values
                    } else if ( copy !== undefined ) {
                        target[ name ] = copy;
                    }
                }
            }
        }

        // Return the modified object
        return target;
    }
    /* Ok, binding is really really simple. It works by accepting a function to
     * bind and returning an encapsulated version of that function that, when
     * run, uses apply() to propagate the correct scope to the bound function.
     *
     * Added the boundArgs argument to contain an array or object with arguments
     * need to be used if the function is called in such as way that arguments
     * cannot normally be passed in.
     */
    ,bind: function(fn, scope, boundArgs) {
        /* Create and return an anonymous function that encapsulates the bound
         * function and acts like a proxy to maintain scope and pass through
         * arguments without altering them. */
        return function() {
            if(typeof(fn) != 'function') {
                // @todo Log arguments.callee?
                throw 'The function provided does not exist.';
            }
            /* The "arguments" variable contains an array of arguments passed
             * to this anonymous function. It is then used to pass those
             * directly on to the bound function. */
            var args = arguments;
             /* If boundArgs is an object, use it as a complete override for the arguments */
            if(typeof(boundArgs) === 'object') {

                args = jQuery.merge(boundArgs, args);
            }

            return fn.apply(scope, args);
        };
    }
    ,randomize: function(length) {
        var chars = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
        var stringLength = (typeof(length) != 'undefined') ? length : 8;
        var randomString = '';
        for (var i=0; i < stringLength; i++) {
            var rNum = Math.floor(Math.random() * chars.length);
            randomString += chars.substring(rNum, rNum+1);
        }
        return randomString;
    }
    ,getRandomString: function(length) {
        var chars = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
        var stringLength = length ? length : 8;
        var randomString = '';
        for (var i=0; i < stringLength; i++) {
            var rNum = Math.floor(Math.random() * chars.length);
            randomString += chars.substring(rNum, rNum+1);
        }
        return randomString;
    },
    getRandomValue: function(values) {
        return values[MMI.Util.getRandomKey(values)];
    },
    getRandomKey: function(object) {
        var keys = [];
        for(var key in object) {
            if(object.hasOwnProperty(key)) {
                keys[keys.length] = key;
            }
        }
        var randomKey = Math.floor(Math.random() * keys.length);
        return keys[randomKey];
    },
    'range' : function(low, high, step) {
        var matrix = [];    var inival, endval, plus;
        var walker = step || 1;
        var chars  = false;

        if ( !isNaN( low ) && !isNaN( high ) ) {        inival = low;
            endval = high;
        } else if ( isNaN( low ) && isNaN( high ) ) {
            chars = true;
            inival = low.charCodeAt( 0 );        endval = high.charCodeAt( 0 );
        } else {
            inival = ( isNaN( low ) ? 0 : low );
            endval = ( isNaN( high ) ? 0 : high );
        }
        plus = ( ( inival > endval ) ? false : true );
        if ( plus ) {
            while ( inival <= endval ) {
                matrix.push( ( ( chars ) ? String.fromCharCode( inival ) : inival ) );            inival += walker;
            }
        } else {
            while ( inival >= endval ) {
                matrix.push( ( ( chars ) ? String.fromCharCode( inival ) : inival ) );            inival -= walker;
            }
        }

        return matrix;
    }
    ,scrollElementToBottom: function(e){
    		jQuery(e).animate({ scrollTop : jQuery(e).attr("scrollHeight") }, 1000);
    }
    ,scrollElementToTop: function (e){
    	jQuery(e).animate({ scrollTop : 0 }, 1000);
    }
    ,makeHideShow: function(jel_target, jel_swapTrigger) {
        // @mmi-log MMI.log('MMI.Util.makeHideShow()');
        // @mmi-log MMI.log(jel_target);
        // @mmi-log MMI.log(jel_swapTrigger);
        jel_target = jQuery(jel_target);
        jel_swapTrigger = jQuery(jel_swapTrigger);
        if(jel_target.size() && jel_swapTrigger.size()) {
            jel_trigger = jQuery('<img id="'+jel_swapTrigger.attr('id')+'" src="/images/core/mmi/containers/hide_show/spacer.gif" />');
            jel_swapTrigger.replaceWith(jel_trigger);
            jel_trigger.data('mmi_hide_show_target', jel_target)
                    .addClass('mmi-containers-hide_show-plus')
                    .bind('click', MMI.Util.swapHideShow);
            jel_target.hide().data('mmi_hide_show_state', 'closed');
        } else {
            // @mmi-log MMI.log('You must pass both a jel_target and jel_swapTrigger to MMI.Util.makeHideShow()');
        }
    }
    ,swapHideShow: function(event) {
        var jel = jQuery(this);
        var jel_target = jel.data('mmi_hide_show_target');
        if(jel_target.data('mmi_hide_show_state') == 'closed') {
            //jel_target.show('blind', { }, 500).data('mmi_hide_show_state', 'open');
            jel_target.show().data('mmi_hide_show_state', 'open');
            jel.removeClass('mmi-containers-hide_show-plus')
                .addClass('mmi-containers-hide_show-minus');
        } else {
            //jel_target.hide('blind', { }, 500).data('mmi_hide_show_state', 'open').data('mmi_hide_show_state', 'closed');
            jel_target.hide().data('mmi_hide_show_state', 'open').data('mmi_hide_show_state', 'closed');
            jel.removeClass('mmi-containers-hide_show-minus')
                .addClass('mmi-containers-hide_show-plus');
        }
    }
};
MMI.Util.__init();

MMI.log = MMI.Util.log;
window.clog = MMI.Util.log;
window.clarg = MMI.Util.logArgs;
MMI.trace = MMI.Util.trace;
MMI.dump = MMI.Util.dump;
MMI.reserve = MMI.Util.reserve;
MMI.release = MMI.Util.release;

MMI.Util.ns = MMI.Util.namespace;

MMI.Util.ns('MMI.Json');

// Set this to the value of JSON if that object exists and contains the methods JSON.stringify and JSON.parse
if(JSON && typeof(JSON) == 'object') {
    if(typeof(JSON.stringify) == 'function') {
        MMI.Json.stringify = JSON.stringify;
    }

    if(typeof(JSON.parse) == 'function') {
        MMI.Json.parse = JSON.parse;
    }
} else {
    JSON = {}; // Set up the JSON object
}

if(typeof(MMI.Json.stringify) != 'function') {
    MMI.Json.stringify = function(obj) {
        var t = typeof (obj);
        if (t != "object" || obj === null) {
            // simple data type
            if (t == "string") {
                obj = '"'+obj+'"';
            }
            return String(obj);
        } else {
            // recurse array or object
            var n, v, json = [], arr = (obj && obj.constructor == Array);
            for (n in obj) {
                v = obj[n];
                t = typeof(v);
                if (t == "string") {
                    v = '"'+v+'"';
                } else if (t == "object" && v !== null) {
                    v = MMI.Json.stringify(v);
                }
                json.push((arr ? "" : '"' + n + '":') + String(v));
            }
            return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
        }
    };
    JSON.stringify = MMI.Json.stringify;
}

if(typeof(MMI.Json.parse) != 'function') {
    MMI.Json.parse = function(string) {
        if (string === "") {
            string = '""';
        }
        eval("var p = " + string + ";");
        return p;
    };
    JSON.parse = MMI.Json.parse;
}

MMI.Util.ns('MMI.stdClass');

(function() {
    /**
     * This class system is based on: http://ejohn.org/blog/simple-javascript-inheritance/
     *
     * Some examples for usage:
     *
     * Note that this class is designed to mimic PHP's inheritance model as closely as possible.
     *
     * stdClass.__construct() is called automatically when you create a new instance of the class like "new MyClassName();"
     * Inside any method (including the __construct() method) you can use "this._parent()" to call the parent class' version of the current method.
     *
     * See below for the code for a working example. See the code in action here: http://jsfiddle.net/mongoose643/79kGC/
     *
            var foo = MMI.stdClass.extend({
                '__construct': function() {

                },
                'output': function() {
                    console.log('I AM FOO');
                }
            });

            var bar = foo.extend({
                '__construct': function() {

                },
                'output': function() {
                    console.log(this);
                    console.log('I AM BAR');
                    console.log('But my parent is...');
                    this._parent();
                    console.log('Done with parent');
                }
            });

            var myFoo = new foo();
            myFoo.output(); // outputs "I AM FOO"

            var myBar = new bar();
            myBar.output(); // outputs "I AM BAR", "But my parent is...", "I AM FOO", "Done with parent"
     */

    MMI.stdClass = function() {};
    MMI.stdClass.__constructing = false;

    // Local variable used to test the function name.
    var _fnTest = /xyz/.test(function(){xyz;}) ? /\b_parent\b/ : /.*/;

    // Create a new stdClass that inherits from this class
    MMI.stdClass.extend = function(prop) {
        var _parent = this.prototype;
        MMI.stdClass.__constructing = true;
        var prototype = new this();
        MMI.stdClass.__constructing = false;
        for (var name in prop) {
            prototype[name] = typeof prop[name] == "function" &&
            typeof _parent[name] == "function" && _fnTest.test(prop[name]) ?
            (function(name, fn) {
                return function() {
                    var tmp = this._parent;
                    this._parent = _parent[name];
                    var ret = fn.apply(this, arguments);
                    this._parent = tmp;
                    return ret;
                };
            })(name, prop[name]) :
            prop[name];
        }
        function stdClass() {
            if(!MMI.stdClass.__constructing && this.__construct) {
                this.__construct.apply(this, arguments);
            }
        }
        stdClass.prototype = prototype;
        stdClass.constructor = stdClass;
        stdClass.extend = arguments.callee;
        return stdClass;
    };
})();

MMI.Util.ns('MMI.Util.Ajax');

// Currently outAnim and inAnim are not used.
MMI.Util.Ajax = {
    config: {
        bgImage: false,
        loaderImage: false
    },
    tryLogin: function(username, password, successHandlerFunction, errorHandlerFunction) {
        if(typeof(successHandlerFunction) != 'function') {
            successHandlerFunction = function(response) {
                clog('Default login success handler.');
            };
        }

        if(typeof(errorHandlerFunction) != 'function') {
            errorHandlerFunction = function(response) {
                clog('Default login error handler.');
            };
        }

        var handleRequestSuccess = function(response) {
            if(typeof(response) == 'object' && typeof(response.json) == 'object') {
                if(response.json.logged_in === true) {
                    successHandlerFunction(response);
                    return;
                }
            }
            errorHandlerFunction(response);
        };

        // First log in attempt. If this fails then try to register the user.
        MMI.Util.Ajax.request({
                url: '/',
                data: {
                    username: username,
                    password: password,
                    mode: 'login',
                    view: 'blank',
                    id: '1'
                },
                json: true
            },
            false,
            handleRequestSuccess,
            errorHandlerFunction,
            false
        );
    },
	/**
	 * Basic ajax call with a single callback (you have to detect status yourself)
	 *
	 * The "opts" argument uses any properties that Ajam.send() accepts.
	 * 	Required Properties:
	 * 		@prop string url
	 * 	Common Properties:
	 * 		@prop string method (post/get)
	 * 		@prop bool async (true/false)
	 * 		@prop object data (object of data/params to send)
	 * 	Custom MMI Properties:
	 * 		@prop bool json
	 *
	 * @param object opts
	 * @param function callback
	 * @param array callbackArgs
	 * @param object|null callbackScope
	 */
	basic: function(opts, callback, callbackArgs, callbackScope) {
	   var req = new Ajam();
	   if(typeof(callback) == 'function') {
		   req.addResponseHandler(AjamUtil.getRandomString(), callback, callbackArgs, callbackScope);
	   }
	   if(opts['json']) {
		   req.expectJson(true);
		   req.setResultTypeParameter('attitude');
		   opts['json'] = undefined;
	   }
	   return req.send(opts);
	},
	/**
	 * Standard ajax call with access to all four callback types (response, success, error, complete)
	 *
	 * The "opts" argument uses any properties that Ajam.send() accepts.
	 * 	Required Properties:
	 * 		@prop string url
	 * 	Common Properties:
	 * 		@prop string method (post/get) [post]
	 * 		@prop bool async (true/false) [true]
	 * 		@prop object data (object of data/params to send)
	 * 		@prop object effect (object like the following)
	 * 			@prop string type [overlayLoader]
	 * 			@prop string|DOM|jQuery target (any valid jQuery object, DOM element, or jQuery selector string)
	 * 			@prop object options [optional] An object of valid Maxjax::configureOnWaitEffect() options.
	 * 	Custom MMI Properties:
	 * 		@prop bool json [false]
	 *
	 * Each callback argument can be either a function or an object. Any other values will be ignored.
	 * When providing an object for a callback argument, the object recognizes the following accepts:
	 * 	Required Propeties:
	 * 		@prop function function // NOTE: You will need to use JSON or square bracket notation in
	 * 								order to set this property. Dot notation will result in a javascript
	 * 								error because "function" is a reserved word.
	 * 	Optional Properties:
	 * 		@prop array arguments An array of additional arguments to provide to the callback function (the response object is prepended to this list)
	 * 		@prop object|function scope The scope to bind to the callback function when running it (what "this" is inside the function when it runs)
	 *
	 * @param object opts
	 * @param function|object responseCallback Runs first.
	 * @param function|object successCallback Runs second but only when a request is successful.
	 * @param function|object errorCallback Runs second but only when a request fails.
	 * @param function|object completeCallback Runs third.
	 */
	request: function(opts, responseCallback, successCallback, errorCallback, completeCallback) {
		var req = false;
		if(typeof(opts['effect']) == 'object' && opts['effect']['target']) {
			req = new Maxjax();
            var ajamOptions = {};
            if(MMI.Util.Ajax.config.bgImage) {
                ajamOptions.bgImage = MMI.Util.Ajax.config.bgImage;
            }
            if(MMI.Util.Ajax.config.loaderImage) {
                ajamOptions.loaderImage = MMI.Util.Ajax.config.loaderImage;
            }
			opts['effect'] = MMI.Util.extend(opts['effect'], {
				'type': 'overlayLoader',
				'target': false,
				'options': ajamOptions
			});
			req.configureOnWaitEffect(opts['effect']['type'], opts['effect']['target'], opts['effect']['options']);
		} else {
			req = new Ajam();
		}
		if(responseCallback) {
			if(typeof(responseCallback) == 'function') {
				req.addResponseHandler(AjamUtil.getRandomString(), responseCallback);
			} else if(typeof(responseCallback) == 'object') {
				req.addResponseHandler(AjamUtil.getRandomString(), responseCallback['function'], responseCallback['arguments'], responseCallback['scope']);
			}
		}
		if(successCallback) {
			if (typeof(successCallback) == 'function') {
				req.addSuccessHandler(AjamUtil.getRandomString(), successCallback);
			} else if(typeof(successCallback) == 'object') {
				req.addSuccessHandler(AjamUtil.getRandomString(), successCallback['function'], successCallback['arguments'], successCallback['scope']);
			}
		}
		if(errorCallback) {
			if (typeof(errorCallback) == 'function') {
				req.addErrorHandler(AjamUtil.getRandomString(), errorCallback);
			} else if(typeof(errorCallback) == 'object') {
				req.addErrorHandler(AjamUtil.getRandomString(), errorCallback['function'], errorCallback['arguments'], errorCallback['scope']);
			}
		}
		if(completeCallback) {
			if (typeof(completeCallback) == 'function') {
				req.addCompleteHandler(AjamUtil.getRandomString(), completeCallback);
			} else if(typeof(completeCallback) == 'object') {
				req.addCompleteHandler(AjamUtil.getRandomString(), completeCallback['function'], completeCallback['arguments'], completeCallback['scope']);
			}
		}
		if(opts['json']) {
		   req.expectJson(true);
		   req.setResultTypeParameter('attitude');
		   opts['json'] = undefined;
		}
		return req.send(opts);
	},
	/**
	 * Submits a form using the Formjax extension of AjaxMadness.
	 *
	 * The "opts" argument uses any properties that AjaxMadness.send() accepts.
	 * 	Common Properties:
	 * 		method (post/get)
	 * 		async (true/false)
	 * 		data (object of data/params to send)
	 * 		primaryDataSource (form/params)
	 * 		submitForm (true/false) Whether or not to automatically submit the form. (Defaults to "true" if found "undefined". Otherwise not modified.)
	 *
	 * Submits to URL/Action in form attributes if no URL specified.
	 *
	 * @param mixed formEl Any jQuery object, DOM element, or valid jQuery selector string for the form to submit.
	 * @param object opts Options for AjaxMadness and/or Formjax
	 * @param function callback
	 * @param array callbackArgs
	 * @param object|null callbackScope
     * @return string Instance ID for the Ajam instance used to make this request.
	 */
	submitBasic: function(formEl, opts, callback, callbackArgs, callbackScope) {
		formEl = jQuery(formEl);
		var req = new Formjax();
		req.addResponseHandler(AjamUtil.getRandomString(), callback, callbackArgs, callbackScope);

		if(typeof(opts.submitForm) === 'undefined') {
			opts.submitForm = true;
		}
		var submitEvents = [];
		if(opts.submitForm === true) {
            if(formEl.data()){
                if(typeof(formEl.data().events) != 'undefined') {
                    var curEvents = formEl.data().events.submit;
                    if(curEvents != null) {
                        for(var i=0; i < curEvents.length; i++) {
                            submitEvents.push(curEvents[i]['handler']);
                        }
                    }
                }
            }
            formEl.unbind('submit'); // Unbind form submit handlers to prevent infinite loops.
		}

		if(typeof(opts.method) === 'undefined') {
			opts.method = 'post';
		}
		if(typeof(opts.async) === 'undefined') {
			opts.async = true;
		}
		if(typeof(opts.primaryDataSource) === 'undefined') {
			opts.primaryDataSource = 'form';
		}

		if(opts['json']) {
		   req.expectJson(true);
		   req.setResultTypeParameter('attitude');
		   opts['json'] = undefined;
		}

		req.send(
            formEl
            ,opts
        );

        if(submitEvents.length > 0) {
            for(i=0; i < submitEvents.length; i++) {
                formEl.bind('submit', submitEvents[i]);
            }
        }

        return req.config.ajamInstanceId;
	},
    /**
	 * Submits a form using the Formjax extension of AjaxMadness.
	 *
	 * The "opts" argument uses any properties that AjaxMadness.send() accepts.
	 * 	Common Properties:
	 * 		method (post/get)
	 * 		async (true/false)
	 * 		data (object of data/params to send)
	 * 		primaryDataSource (form/params)
	 * 		submitForm (true/false) Whether or not to automatically submit the form. (Defaults to "true" if found "undefined". Otherwise not modified.)
	 *
	 * Submits to URL/Action in form attributes if no URL specified.
	 *
	 * @param mixed formEl Any jQuery object, DOM element, or valid jQuery selector string for the form to submit.
	 * @param object opts Options for AjaxMadness and/or Formjax
	 * @param function callback
	 * @param array callbackArgs
	 * @param object|null callbackScope
     * @return string Instance ID for the Ajam instance used to make this request.
	 */
	submitAndReplaceContent: function(formEl, contentEl, opts, callback, callbackArgs, callbackScope) {
		formEl = jQuery(formEl);
		var req = new Formjax();
		req.addResponseHandler(AjamUtil.getRandomString(), callback, callbackArgs, callbackScope);

        req.expectJson(true);
        req.setParam('attitude', 'json');

        var containerId = '#' + jQuery(contentEl).attr('id');
        if(containerId == '#' && contentEl.size()) {
            var containerId = MMI.Util.getRandomString(8);
            contentEl.first().attr('id', containerId);
            containerId = '#' + containerId;
        } else if(containerId == '#') {
            containerId = false;
        }

        if(containerId) {
            req.configureOnWaitEffect(
                'overlayLoader'
                ,containerId
                ,{loaderImage: opts.loaderImage});
        }

        req.addSuccessHandler(AjamUtil.getRandomString(), function(response, containerId) {
                if (!response.json) {
                    MMI.Util.Ajax.replaceContentFailed(containerId, 'No Data');
                } else if (response.json.error == 1) {
                    MMI.Util.Ajax.replaceContentFailed(containerId, 'Error');
                } else if (containerId) {
                    MMI.Util.Ajax.replaceContentWithResponse(containerId, response.json);
                } else {
                    MMI.Util.Ajax.replaceContentFailed(containerId, 'Invalid container.');
                }
            }
            ,[containerId]
            ,this
        );

		if(typeof(opts.submitForm) === 'undefined') {
			opts.submitForm = true;
		}
		var submitEvents = [];
		if(opts.submitForm === true) {
            if(formEl.data()){
                if(typeof(formEl.data().events) != 'undefined') {
                    var curEvents = formEl.data().events.submit;
                    if(curEvents != null) {
                        for(var i=0; i < curEvents.length; i++) {
                            submitEvents.push(curEvents[i]['handler']);
                        }
                    }
                }
            }
            formEl.unbind('submit'); // Unbind form submit handlers to prevent infinite loops.
		}

		if(typeof(opts.method) === 'undefined') {
			opts.method = 'post';
		}
		if(typeof(opts.async) === 'undefined') {
			opts.async = true;
		}
		if(typeof(opts.primaryDataSource) === 'undefined') {
			opts.primaryDataSource = 'form';
		}

		req.send(
            formEl
            ,opts
        );

        if(submitEvents.length > 0) {
            for(i=0; i < submitEvents.length; i++) {
                formEl.bind('submit', submitEvents[i]);
            }
        }

        return req.config.ajamInstanceId;
	},
	/**
	 * Replaces an existing DOM element with the HTML returned in the response result.
	 *
	 * The "opts" argument accepts any of the properties defined in this list:
	 * 	Common Properties:
	 * 		method (post/get)
	 * 		loaderImage (url to a custom loader graphic - used in src of an img tag)
	 *
	 * @param mixed container Any valid jQuery selector, jQuery object, or DOM element.
	 * @param string contentUrl The URL to make the request to.
	 * @param object data An object containing data to send to the server.
	 * @param object opts An object containing options that alter the behavior of the request or the request wait effect.
	 * @param function responseCallback A function to call when the request is received from the server. (runs first)
	 * @param function completeCallback A function to call when the request is received from the server. (runs last)
	 */
	replaceElement: function(container, contentUrl, data, opts, responseCallback, completeCallback) {
		var ajam = new Maxjax();
        ajam.expectJson(true);
        ajam.setUrl(contentUrl);
        ajam.setData(data);
        if(typeof(opts.method) != 'undefined') {
        	ajam.setMethod(opts.method);
        }
        ajam.setParam('attitude', 'json');
        var ajamOptions = {
            loaderImage: opts.loaderImage
        };
        if(!ajamOptions.loaderImage && MMI.Util.Ajax.config.loaderImage) {
            ajamOptions.loaderImage = MMI.Util.Ajax.config.loaderImage;
        }
        if(MMI.Util.Ajax.config.bgImage) {
            ajamOptions.bgImage = MMI.Util.Ajax.config.bgImage;
        }
        var containerId = '#' + jQuery(container).attr('id');
        if(containerId != '#') {
            ajam.configureOnWaitEffect(
                'overlayLoader'
                ,containerId
                ,ajamOptions);
        } else {
            containerId = false;
        }

        ajam.addSuccessHandler(AjamUtil.getRandomString(), function(response, containerId) {
                if (!response.json) {
                    MMI.Util.Ajax.replaceElementFailed(containerId, 'No Data');
                } else if (response.json.error == 1) {
                    MMI.Util.Ajax.replaceElementFailed(containerId, 'Error');
                } else if (containerId) {
                    clog('Trying to do element replace.', 'Container:', containerId, 'Response JSON', response.json);
                    MMI.Util.Ajax.replaceElementWithResponse(containerId, response.json);
                } else {
                    MMI.Util.Ajax.replaceElementFailed(containerId, 'Invalid container ID');
                }
            }
            ,[containerId]
            ,this
        );

        if(typeof(responseCallback) === 'function') {
            ajam.addResponseHandler(AjamUtil.getRandomString(), responseCallback, [containerId], this);
        }

        if(typeof(completeCallback) === 'function') {
            ajam.addCompleteHandler(AjamUtil.getRandomString(), completeCallback, [containerId], this);
        }

        ajam.addErrorHandler(AjamUtil.getRandomString(), function(response, containerId) {
                MMI.Util.Ajax.replaceElementFailed(containerId, 'Page Failure');
            }
            ,[containerId]
            ,this
        );

        ajam.send();
	},
	/**
	 * Replaces an existing DOM element with the HTML returned in the response result.
	 *
	 * The "opts" argument accepts any of the properties defined in this list:
	 * 	Common Properties:
	 * 		method (post/get)
	 * 		loaderImage (url to a custom loader graphic - used in src of an img tag)
	 *
	 * @param mixed container Any valid jQuery selector, jQuery object, or DOM element.
	 * @param string contentUrl The URL to make the request to.
	 * @param object data An object containing data to send to the server.
	 * @param object opts An object containing options that alter the behavior of the request or the request wait effect.
	 * @param function responseCallback A function to call when the request is received from the server. (runs first)
	 * @param function completeCallback A function to call when the request is received from the server. (runs last)
     * @param function errorCallback A function to call when the request fails. The default is internal to this class.
     * @param function successCallback A custom success callback to use in addition to the default.
	 */
    replaceContent: function(container, contentUrl, data, opts, responseCallback, completeCallback, errorCallback, successCallback) {
        var ajam = new Maxjax();
        ajam.expectJson(true);
        ajam.setUrl(contentUrl);
        ajam.setData(data);
        if(typeof(opts.method) != 'undefined') {
            ajam.setMethod(opts.method);
        }
        ajam.setParam('attitude', 'json');
        var ajamOptions = {
            loaderImage: opts.loaderImage
        };
        if(!ajamOptions.loaderImage && MMI.Util.Ajax.config.loaderImage) {
            ajamOptions.loaderImage = MMI.Util.Ajax.config.loaderImage;
        }
        if(MMI.Util.Ajax.config.bgImage) {
            ajamOptions.bgImage = MMI.Util.Ajax.config.bgImage;
        }
        container = jQuery(container);
        var containerId = '#' + container.attr('id');
        if(container.size()) {
            ajam.configureOnWaitEffect(
                'overlayLoader'
                ,container
                ,ajamOptions);
        } else {
            containerId = false;
        }

        ajam.addSuccessHandler(AjamUtil.getRandomString(), function(response, container) {
                if (!response.json) {
                    MMI.Util.Ajax.replaceContentFailed(container, 'No Data');
                } else if (response.json.error == 1) {
                    MMI.Util.Ajax.replaceContentFailed(container, 'Error');
                } else if (container) {
                    MMI.Util.Ajax.replaceContentWithResponse(container, response.json);
                } else {
                    MMI.Util.Ajax.replaceContentFailed(container, 'Invalid container ID');
                }
            }
            ,[container]
            ,this
        );

        if(typeof(successCallback) == 'function') {
            ajam.addSuccessHandler(AjamUtil.getRandomString(), successCallback, [container], this);
        }

        if(typeof(responseCallback) == 'function') {
            ajam.addResponseHandler(AjamUtil.getRandomString(), responseCallback, [container], this);
        }

        if(typeof(completeCallback) == 'function') {
            ajam.addCompleteHandler(AjamUtil.getRandomString(), completeCallback, [container], this);
        }

        if(typeof(errorCallback) != 'function') {
            errorCallback = function(response, container) {
                MMI.Util.Ajax.replaceContentFailed(container, 'Page Failure');
            };
        }

        ajam.addErrorHandler(AjamUtil.getRandomString(), errorCallback, [container], this);

        ajam.send();
    }
    ,replaceContentFailed: function(container, error) {
        clog('MMI.Util.Ajax.replaceContentFailed()', arguments);
    }
    ,replaceContentWithResponse: function (containerId, response) {
        if (response.result != "" && jQuery(containerId).size()) {
            var jel = jQuery(containerId);
            if(jel.hasClass('live_remove')) { // Only trigger remove on the element if it has that class
                jel.trigger('live_remove');
            }
            jel.find('.live_remove').trigger('live_remove'); // Trigger remove on any elements inside of the content being replaced if they have that class
            jel.html(response.result);
            jQuery('.live_create').trigger('create'); // Trigger this in entire DOM. Response could contain JS that creates elements anywhere in the DOM.
        } else if(jQuery(containerId).size() == 0) {
            clog('MMI.Util.Ajax.replaceContentWithResponse: Invalid container.', containerId);
        } else {
            clog("MMI.Util.Ajax.replaceContentWithResponse: Incomplete response data.", response);
        }
    }
    ,replaceElementFailed: function(container, error) {
        clog('MMI.Util.Ajax.replaceElementFailed: Unable to complete request. ' + error);
    }
    ,replaceElementWithResponse: function (containerId, response) {
        AjamUtil.log('Container ID for Replace: ' + containerId);
        if (response.result != "") {
            var oldEl = jQuery(containerId);
            if(oldEl.hasClass('live_remove')) { // Only trigger remove on the element if it has that class
                oldEl.trigger('live_remove');
            }
            oldEl.find('.live_remove').trigger('live_remove'); // Trigger remove on any elements inside of the content being replaced if they have that class
        	var newEl = jQuery(response.result);
            oldEl.replaceWith(newEl);
            jQuery('.live_create').trigger('create'); // Trigger this in entire DOM. Response could contain JS that creates elements anywhere in the DOM.
        } else {
            clog("MMI.Util.Ajax.replaceElementWithResponse: Incomplete response data.", response);
        }
    }
};

MMI.Ajax = MMI.Util.Ajax; // Should this stay?

MMI.Util.ns('MMI.Util.Form.Field.SanitizeInput');
MMI.Util.Form.Field.SanitizeInput = {
	'number': function(changeEvent) {
		//// @mmi-log MMI.log('Sanitizing input field.');
		var el = jQuery(this);
		var original = el.val();
		var value = original;
		//// @mmi-log MMI.log('Original: ' + original);
		// Strip out illegal characters.
		//value = value.replace(/[^-\d\.,\+]/g, ''); // Does allow commas for human readable numbers
        value = value.replace(/[^-\d\.\+]/g, ''); // Does NOT allow commas as math cannot be done with commas!
		//// @mmi-log MMI.log('Stripped out illegals: ' + value);
		// Only keep the valid number part.
		value = parseFloat(value);
		//// @mmi-log MMI.log('Parsed as float: ' + value);
		if(value && value != original && original != '') {
			el.effect('highlight', {'color': '#a1e1ff'}, 3000);
			el.val(value);
        }else if(original === ''){
            el.val('');
		} else if(!value) {
			el.val(0);
		}
	}
};

MMI.Util.ns('MMI.Util.Form.Field.FilterInput');
MMI.Util.Form.Field.FilterInput = {
    'number': function(keypressEvent) {
        var el = jQuery(this);
        var code = keypressEvent.which;
        var usabilityCodes = {
            'backspace': 8,
            'up': 38,
            'right': 39,
            'down': 40,
            'left': 37,
            'shift': 16,
            'tab': 9,
            'plus': 43,
            'minus': 45,
            'other': 0
        };
        for(var key in usabilityCodes) {
            if(code == usabilityCodes[key]) {
                return true;
            }
        }
        var char = String.fromCharCode(code);
        var usabilityChars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-', '+', ','];
        for(var key in usabilityChars) {
            if(char == usabilityChars[key]) {
                return true;
            }
        }
        if(/[-\d\.,\+]/g.test(char)) {
            return true;
        }
        el.effect('highlight', {'color': '#a1e1ff'}, 250);
        return false;
    }
};

MMI.Util.ns('MMI.Util.Form.Validation');

MMI.Util.Form.Validation = {
    // There are plans to allow validator to be an object or array containing multiple validator callback functions.
    addValidator: function(jqSel, validator) {
        var jel = jQuery(jqSel);
        var validators = jel.data('mmiFormValidators');
        if(!MMI.Util.isArray(validators)) {
            validators = [];
        }
        validators[validators.length] = validator;
        jel.data('mmiFormValidators', validators);
        return true;
    }
};

MMI.Util.ns('MMI.Util.Form.AdvancedUploader');

MMI.Util.Form.AdvancedUploader = {
    /*
        itemType - the type of file object to create with the upload form.
        fieldName - the name and ID of the field containing the list of available files - this must be updated automatically after the uploaded file item is created.
        itemId - the ID of the item that the file reference field is attached to (NOT the ID of the newly uploaded file)
        allowedExtensions - an array of file extensions (no leading dot, for example: ['flv', 'mov', 'mp4']
     */
    openUploadWindow: function(itemType, fieldName, itemId, allowedExtensions) {
        var fieldId = MMI.Util.getRandomString();
        var dialogId = MMI.Util.alert('', 'Upload a File', { }, { 'width': 735, 'height': 535, 'close': function(event, ui) {
            var el = jQuery(this);
            el.dialog('destroy').remove();
        }});
        var allowedExtensionsString = '';
        if(MMI.Util.isArray(allowedExtensions)) {
            for(var i=0; i < allowedExtensions.length; i++) {
                if(i!=0) {
                    allowedExtensionsString += ',';
                }
                allowedExtensionsString += allowedExtensions[i];
            }
        }
        MMI.Ajax.replaceContent(
            '#' + dialogId,
            '/',
            {
                'mode': 'edit'
                ,'view': 'slide_uploader'
                ,'type': itemType
                ,'presentation_id': window.welch.getAjaxGlobalParam('presentation_id')
                ,'workspace_id': window.welch.getAjaxGlobalParam('workspace_id')
                ,'mmi_upload_dialog_id': dialogId
                ,'mmi_upload_field': fieldName
                ,'mmi_upload_field_item_id': itemId
                ,'mmi_upload_allowed_extensions': allowedExtensionsString
            },
            {

            },
            null,
            function(response) {
                MMI.Util.Form.AdvancedUploader.autoRegisterForms();
                window.welch.initializeLoadedElement('#' + dialogId);
            }
        );
    },
    autoRegisterFormsPersistent: function() {
        var jelForms = jQuery('form.mmi_advupload');
        var jelFileInputs = jQuery('form.mmi_advupload input[type=file]');
        // Wrap a div container around each file input element
        jelFileInputs.each(function() {
            var jel = jQuery(this);
            var wrapperId = 'mmi_advupload_container_' + jel.attr('name')
            if(jel.parent().attr('id') != wrapperId) {
                jel.wrap('<div id="' + wrapperId + '"></div>');
            }
        });
        jelFileInputs.live('change.mmi_advupload', MMI.Util.Form.AdvancedUploader._handleEvent_fileFieldChange);
    },
    autoRegisterForms: function() {
        var jelForms = jQuery('form.mmi_advupload');
        if(!jelForms.size()) {
            // @mmi-log MMI.log('-------- ## ALERT: Make sure you put the CSS class "mmi_advupload" on any forms you want to register this way [MMI.Util.AdvancedUploader.autoRegisterForms] ## --------');
        }
        var jelFileInputs = jQuery('form.mmi_advupload input[type=file]');
        // Wrap a div container around each file input element
        jelFileInputs.each(function() {
            var jel = jQuery(this);
            var wrapperId = 'mmi_advupload_container_' + jel.attr('name')
            if(jel.parent().attr('id') != wrapperId) {
                jel.wrap('<div id="' + wrapperId + '"></div>');
            }
        });
        jelFileInputs.unbind('change.mmi_advupload');
        jelFileInputs.bind('change.mmi_advupload', MMI.Util.Form.AdvancedUploader._handleEvent_fileFieldChange);
    },
    registerForm: function(jel_formEl) {
        var jelForm = jQuery(jel_formEl);
        var jelFileInputs = jQuery('input[type=file]', jelForm);
        // Wrap a div container around each file input element
        jelFileInputs.each(function() {
            var jel = jQuery(this);
            var wrapperId = 'mmi_advupload_container_' + jel.attr('name')
            if(jel.parent().attr('id') != wrapperId) {
                jel.wrap('<div id="' + wrapperId + '"></div>');
            }
        });
        jelFileInputs.unbind('change.mmi_advupload');
        jelFileInputs.bind('change.mmi_advupload', MMI.Util.Form.AdvancedUploader._handleEvent_fileFieldChange);
    },
    registerField: function(fieldEl) {
        var jelFileInput = jQuery(fieldEl);
        jelFileInput.unbind('change.mmi_advupload');
        jelFileInput.bind('change.mmi_advupload', MMI.Util.Form.AdvancedUploader._handleEvent_fileFieldChange);
    },
    cancelNewFile: function(originalFieldName, currentFileName) {
        var jelContainer = jQuery.genome('#mmi_advupload_container_' + originalFieldName);
        if(currentFileName) {
            jelContainer.html('Uploaded: <strong>' + currentFileName + "</strong> <span class='fakelink' onclick='MMI.Util.Form.AdvancedUploader.resetField(\"" + originalFieldName + "\", \"" + currentFileName + "\")'> [Pick a new file]</span><br />");
        }
    },
    resetField: function(originalFieldName, fileName) {
        var jelContainer = jQuery.genome('#mmi_advupload_container_' + originalFieldName);
        var newField = '';
        if(fileName) {
            newField = jQuery('<input type="file" name="'+ originalFieldName + '" id="' + originalFieldName + '" /> <span class="fakelink" onclick="MMI.Util.Form.AdvancedUploader.cancelNewFile(\''+originalFieldName+'\', \'' + fileName +  '\')">[Cancel]</span><br />');
        } else {
            newField = jQuery('<input type="file" name="'+ originalFieldName + '" id="' + originalFieldName + '" /><br />');
        }
        jelContainer.html(newField);
        var jelField = jQuery.genome('#' + originalFieldName);
        MMI.Util.Form.AdvancedUploader.registerField(jelField);
        return jelField;
    },
    cancelUpload: function(originalFieldName) {
        // This has to be delayed or it get's overwritten by the completed upload view because the complete callbacks still run when the iframe is deleted.
        //setTimeout(MMI.Util.bind(MMI.Util.Form.AdvancedUploader.resetField, this, [originalFieldName]), 500);

        var fieldContainer = jQuery.genome('#mmi_advupload_container_' + originalFieldName);
        MMI.log(fieldContainer);
        //var jelForm = fieldContainer.closest('form');
        //var uploadCount = parseInt(jelForm.data('ajam_uploadCount'));
        /*
        if(isNaN(uploadCount)) {
            jelForm.data('ajam_uploadCount', 0);
        } else {
            jelForm.data('ajam_uploadCount', uploadCount - 1);
        }*/
        var ajamInstanceId = fieldContainer.data('mmi_advupload_ajamInstanceId');
        MMI.log('Ajam Instance ID: ' + ajamInstanceId);

        // Create a dummy instance of the Formjax class.
        var ajamTmp = new Formjax();
        // Use the dummy instance to gain access to the ajam instance we need to cancel
        var ajam = ajamTmp.globalConfig.ajamInstanceStack[ajamInstanceId];
        // Cancel the original request created to do the file upload.
        ajam.cancel();
    },
    _handleEvent_fileFieldChange: function(event) {
        // @mmi-log MMI.log('MMI.Util.AdvancedUploader._handleEvent_fileFieldChange()');
        var jel = jQuery(this);

        // Does the file field contain a file path?
        if(!jel.val()) {
            return;
        }

        // Ensure that the file extension is allowed.
        var allowedExtensions = jel.data('mmi_upload_allowed_extensions');
        for(var extKey in allowedExtensions ) {
            if(typeof(allowedExtensions[extKey]) == 'string') {
                allowedExtensions[extKey] = allowedExtensions[extKey].toLowerCase();
            }
        }
        var fileExtension = MMI.Util.getFileExtension(jel.val()).substr(1).toLowerCase();
        if(allowedExtensions) {
            if(!MMI.Util.inArray(fileExtension, allowedExtensions)) {
                MMI.Util.alert('That type of file is not allowed. Please upload a file of an allowed type.');
                jel.val('');
                return;
            }
        }

        // @mmi-log MMI.log('--------- -------------- ----------- ---------- ----------');
        var jelNameField = jQuery.genome('#' + jel.data('mmi_upload_name_field_id'));
        // @mmi-log MMI.log(jel.data('mmi_upload_name_field_id'));
        // @mmi-log MMI.log(jelNameField);
        // @mmi-log MMI.log(jel.data('mmi_upload_name_field_default_value'));
        if(jelNameField.val() == '' || jelNameField.val() == jel.data('mmi_upload_name_field_default_value')) {
            var filePathParts = jel.val().split('/');
		    if(filePathParts.length == 1){
			    filePathParts = jel.val().split('\\');
		    }

		    var filename = filePathParts.pop();

            //remove the extension
		    filePathParts = filename.split('.');
		    filePathParts.pop();
		    var textFileName = filePathParts.join(' ');

		    //clean up underscores
		    textFileName = textFileName.replace(/_/g, ' ');
		    textFileName = textFileName.replace(/([A-Z])/g, " $1").trim();
		    jelNameField.val(textFileName);
        }


        var originalFieldName = jel.attr('name');
        var fieldContainer = jel.parent(); // We have to get this before we move jel's location in the DOM
        var fileName = jel.val();

        // Delay form submit until the file is finished uploading...
        // Select the form containing this field and modify its submit handler...
        var mainForm = jel.parents('form:first');
        var uploadCount = parseInt(mainForm.data('ajam_uploadCount'));

        if(isNaN(uploadCount)) {
            uploadCount = 0;
        }
        uploadCount = uploadCount + 1;
        mainForm.data('ajam_uploadCount', uploadCount);

        /*var jelHidden = jQuery.genome('#' + originalFieldName.replace(/\[file\]/, ''));
        if(!jelHidden.size()) {
            jelHidden = jQuery.genome('#' + originalFieldName);
        }
        jelHidden.val('');*/

        // Now we can swap the UI pieces.
        jel.parent().html('Uploading File: <strong>' + fileName + '</strong><br /><img style="clear: both;" src="/images/core/file-upload-loader-02.gif" /> <span class="fakelink" onclick="MMI.Util.Form.AdvancedUploader.cancelUpload(\'' + originalFieldName + '\')">[Cancel Upload]</span>');

        var uploadInstanceId = MMI.Util.getRandomString(23);
        var uploadForm = jQuery('<form id="mmi_advupload_tmpform_' + MMI.Util.getRandomString() + '" style="display: none;" action="/index.php" method="post" enctype="multipart/form-data"><input type="hidden" name="mode" value="upload" /><input type="hidden" name="attitude" value="overlay" /><input type="hidden" name="external_id" value="'+uploadInstanceId+'" /></form>');
        var newName = jel.attr('name').replace(/item\[/, 'upload[');
        jel.attr('name', newName);
        uploadForm.append(jel);
        jQuery('body').append(uploadForm);

        var ajamInstanceId = MMI.Util.Ajax.submitBasic(uploadForm, {}, MMI.Util.Form.AdvancedUploader._handleEvent_fileUploadComplete, [fieldContainer, uploadForm, originalFieldName, fileName, uploadInstanceId]);
        // @mmi-log MMI.log('Ajam Instance ID (1): ' + ajamInstanceId);

        MMI.log('File Field Changed');
        MMI.log(fieldContainer);
        MMI.log('Ajam Instance ID: ' + ajamInstanceId);
        fieldContainer.data('mmi_advupload_ajamInstanceId', ajamInstanceId);
    },
    _handleEvent_fileUploadComplete: function(response, jelFieldContainer, jelForm, originalFieldName, fileName, uploadInstanceId) {
        // @mmi-log MMI.log('MMI.Util.AdvancedUploader._handleEvent_fileUploadComplete()');
        jelForm.remove();
        var mainForm = jelFieldContainer.parents('form:first');
        var uploadCount = parseInt(mainForm.data('ajam_uploadCount'));
        mainForm.data('ajam_uploadCount', uploadCount - 1);
        if(response.status < 0) {
            // If the response.status value is less than 0 then the http request failed. Reset the field to accept another attempt.
            //MMI.Util.Form.AdvancedUploader.resetField(originalFieldName, fileName);
            MMI.Util.Form.AdvancedUploader.resetField(originalFieldName, false);
        } else {
            // Check the status of the upload
            var fieldName = originalFieldName.replace(/item\[/, 'upload[');
            if(fieldName.substring(fieldName.length - 6) == '[file]') {
                fieldName = fieldName.slice(0, fieldName.length - 6);
            }
            var checkData = {};
            checkData['mode'] = 'upload_status';
            checkData['external_id'] = uploadInstanceId;
            checkData[fieldName] = '';
            MMI.Util.Ajax.request(
                {
                    'url': '/index.php'
                    ,'method': 'post'
                    ,'async': true
                    ,'json': true
                    ,'data': checkData
                }
                ,function() {
                    // ajax response
                }
                ,function(response) {
                    // ajax success
                    if(typeof(response.json) == 'object') {
                        var statusCode = parseInt(response['json']['code']);
                        var statusMessage = response['json']['message'];
                        if(statusCode) {
                            // Revert the file upload field.
                            MMI.Util.Form.AdvancedUploader.resetField(originalFieldName, fileName);
                            // Something went wrong! ack
                            MMI.Util.alert(statusMessage);
                        } else {
                            // All clear! Upload successful.
                            var hiddenFieldName = originalFieldName.substr(0, originalFieldName.length - 6);
        	                jelBaseField = jQuery.genome('#' + hiddenFieldName).trigger('change');
                            jelFieldContainer.html('Uploaded: <strong>' + fileName + "</strong> <span class='fakelink' onclick='MMI.Util.Form.AdvancedUploader.resetField(\"" + originalFieldName + "\", \"" + fileName + "\")'> [Pick a new file]</span><br />");
                        }
                    }
                }
                ,function() {
                    // ajax error
                }
                ,function() {
                    // ajax complete
                }
            );
        }
    }
};

MMI.Util.ns('MMI.Util.TinyMCE');
MMI.Util.TinyMCE = {
    setup: function(editor) {
        // @mmi-log MMI.log('MMI.Util.TinyMCE.setup()');
        editor.onInit.add(function() {
            editor.getWin().onblur = MMI.Util.bind(MMI.Util.TinyMCE.onblurHandler, null, [editor]);
        });
    }
    ,execCommandHandler: function(editor_id, elm, command, user_interface, value) {
        // @mmi-log MMI.log('MMI.Util.TinyMCE.execCommandHandler');
        var ed = tinyMCE.get(editor_id);

        var runCancel = function() {
            //var h = tinymce.trim(ed.startContent);
            var h = ed.startContent;

            // This block is straight from the original save/cancel plugin.
            ed.setContent(h);
            ed.undoManager.clear();
            ed.nodeChanged();

            // Our custom addition
            tinyMCE.execCommand('mceRemoveControl', false, editor_id);
        };

        if(command == 'mceCancel') {
            if(!ed.isDirty()) {
                runCancel();
                return true; // prevents the standard functions of the command from running
            } else {
                MMI.Util.confirm('Are you sure you want to cancel all of the changes you made since your last save?', 'Discard Changes?', runCancel);
                return true; // prevents the standard functions of the command from running
            }
        }
        /* Old stuff for the inline editor - saving should not always work like this...
        else if(command == "mceSave") {
            inlineItSaveHandler(ed);
            return true;
        }
        */
    }
    ,eventHandler: function(event) {
        return true; // continue handling...
    }
    ,nodeChangeHandler: function() {
        return true;
    }
    ,onblurHandler: function(editor) {
        if(editor.isDirty()) {
            MMI.Util.TinyMCE.onchangeHandler(editor);
        }
    }
    ,onchangeHandler: function(editor) {
        // @mmi-log MMI.log('MMI.Util.TinyMCE.onChangeHandler()');
        if(typeof(editor) != 'undefined') {
            if(typeof(editor.save) == 'function') {
                var jel = jQuery(editor.getElement());
                // Tiny MCE does not like jQuery setting its value, so do it old school
                var content = editor.getContent();
                //jel.val(content);
                editor.getElement().value = content;
                jel.trigger('change');
            } else {
                // @mmi-log MMI.log('NO SAVE FUNCTION!!!!');
            }
        }
        return true;

    }
};


MMI.Util.ns('MMI.Util.ExtJS');
MMI.Util.ExtJS = {
	//Called from update commands on grids and data stores
	formDataSave : function(store, record, operation){

		var formid = store.baseParams.id;
		opts = {id : formid, mode : 'form', view : 'blank'};
		for(i in record.data){
			value = MMI.Util.ExtJS.getFormattedDataFromRecord(record, i);
			prop = 'formdata[' + formid + '][' + i + ']';
			opts[prop] = value;
		}
		opts['oldid'] = record.id;
		//opts['formdata['+formid+'][form_data_id]'] = record.get('form_data_id');
		MMI.Util.Ajax.basic({ 'url' : 'index.php', 'method' : 'post', 'json' : true, 'data' : opts }, MMI.Util.ExtJS.formDataSetID, [store, record]);//callback, callbackArgs, callbackScope

	}
	,formDataSetID: function(response, store, record){
		var jsonData = response.json;
		//new id
		var newid = jsonData.form_data_id;
		var oldid = jsonData.postdata.oldid;
		record.set('form_data_id', newid);
	}
	//removes a form record
	,formDataDelete : function(record, field){

	}
	//Gets the data in the right format
	,getFormattedDataFromRecord : function(record, field){
		value = record.data[field];
		//type
		var type = MMI.Util.ExtJS.getDataType(record, field);
		return MMI.Util.ExtJS.convertDataToType(value, type);
	}
	//Converts data to the set type from an EXT source
	,convertDataToType : function(value, type){
		//get the type
		switch(type){
		case 'date' :
		case 'datefield' :
		case 'dateformat' :
			//make the date
			dt = value.format('U');
			return dt;

		default :
			return value;
		}
	}
	//Gets the correct data type for a field
	,getDataType : function(record, field){
		var fieldData = record.fields.map[field];
		return fieldData.type;
	}
	,displayFormatBoolean: function(value, metaData, record, rowIndex, colIndex, store){
		if(value == 1 || value == '1' || value == true || value == 'true'){
			return 'Yes';
		}else{
			return 'No';
		}
	}
};


/**
 * @author Michael White
 * @copyright Copyright (c) 2009 Michael White (http://getsprink.com)
 *
 * This class allows you to easily create and extend classes in a way that
 * mimics the PHP behavior.
 */
(function() {
	/* This variable does not pollute the global scope.
	 * Scroll down to the end of the class to see where ClassMaker is set to this variable */
	var Class = (function() {
		this.__config = {
			trace: false
		};

		// Shallow copy.
		function extend(dest, source) {
			for(var prop in source) {
				dest[prop] = source[prop];
			}
			return dest;
		}

		// Shortcut to console.log() - only runs if tracing is "on" and there is a console available.
		function trace(message) {
			if(this.__config.trace) {
				if(window.console) {
					var args = [];
					if(arguments.length > 1) {
						args = arguments[1];
					}

					if(args.length < 1 && this.__config.trace < 2) {
						console.log(message);
					}
					else if(this.__config.trace == 2) {
						console.log(message);
					}
					else if(this.__config.trace >= 3) {
						console.log(message+'  ---- WITH ARGS:');
						console.log(arguments[1]);
					}
				}
				else {
					if(typeof(message) == "string") {
						var content = document.getElementsByTagName('body');
						content[0].innerHTML += message.toString() + "<br />";
					}
				}
			}
		}

		// Creates a new instance of the class.
		function create() {
			// Turn the arguments variable into a true array.
			var args = Array.prototype.slice.call(arguments);
			var parent = null, source = null;

			// If the first argument is a function then use it as the parent class.
			if(args.length > 1 && typeof(args[0]) == "function") {
				parent = args[0];
				source = args[1];
			}
			else {
				source = args[0];
			}

			// Creates the actual function that runs when this class is instantiated.
			function klass() {
				this.__construct.apply(this, arguments);
			}

			// Set a special configuration object but don't override the parent config if one is set.
			if(typeof(source.__config) != "object" && (!parent || typeof(parent.prototype.__config) != "object")) {
				source.__config = {
					trace: false
				};
			}

			// Make sure the source has at least an empty constructor set. Do not override the parent constructor if one is set.
			if(typeof(source.__construct) != "function" && (!parent || typeof(parent.prototype.__construct) != "function")) {
				source.__construct = function() {};
			}

			// Set an easy to access trace method as a console.log() shortcut.
			if(typeof(source.trace) != "function") {
				source.trace = trace;
			}

			// Extend the class function we just created with the standard method set.
			extend(klass, Class.Methods);

			// Add in the custom methods and properties from the source object.
			klass.addMethods(source);

			// Set the constructor of this class.
			klass.prototype.constructor = klass;

			if(parent) {
				// Provide access to the parent class. (accessed via this.$parent from the child class)
				klass.prototype.$parent = parent.prototype;

				// Loop through all of the parent class' properties and methods. If the child class does not have the property or method, set it to the parent version.
				for(var p in parent.prototype) {
					if(typeof(klass.prototype[p]) == "undefined") {
						klass.prototype[p] = parent.prototype[p];
					}
				}
			}

			// Required to allow things like "new myClassHere();" to work.
			// This is because returning this function to the "new" keyword will cause it to run.
			return klass;
		}

		// Copies methods from the source object to the class/function's prototype.
		// Ensures that toString() and valueOf() exist.
		function addMethods(source) {
			for(var prop in source) {
				this.prototype[prop] = source[prop];
			}

			if(typeof(this.toString) != "object") {
				this.prototype.toString = Object.prototype.toString;
			}

			if(typeof(this.valueOf) != "object") {
				this.prototype.toString = Object.prototype.valueOf;
			}
			return this;
		}

		return {
			create: create,
			Methods: {
				addMethods: addMethods
			}
		};
	})();

	/* Name this class anything we want to in the global scope. */
	window.Ln_ClassMaker = Class;
})();

function popUp(URL) {
    day = new Date();
    id = day.getTime();
    eval("page" + id + " = window.open(URL, '" + id + "', 'toolbar=0,scrollbars=1,location=0,statusbar=0,menubar=0,resizable=1,width=720,height=500');");
}

function popUpSize(URL, w, h) {
    day = new Date();
    id = day.getTime();
    eval("page" + id + " = window.open(URL, '" + id + "', 'toolbar=0,scrollbars=1,location=0,statusbar=0,menubar=0,resizable=1,width=" + w + ",height=" + h + "');");
}

function refreshPage(vPage){
    if(vPage == ""){
        window.location.href = window.location.href;
    }else{
        window.location.href = vPage;
    }
}

// Position finder
function findPosX(obj)
  {
    var curleft = 0;
    if(obj.offsetParent)
        while(1)
        {
          curleft += obj.offsetLeft;
          if(!obj.offsetParent)
            break;
          obj = obj.offsetParent;
        }
    else if(obj.x)
        curleft += obj.x;
    return curleft;
  }

function findPosY(obj)
  {
    var curtop = 0;
    if(obj.offsetParent)
        while(1)
        {
          curtop += obj.offsetTop;
          if(!obj.offsetParent)
            break;
          obj = obj.offsetParent;
        }
    else if(obj.y)
        curtop += obj.y;
    return curtop;
  }

  //Handles hide/show of display areas with buttons
  //uses the container name, and _hide and _show to toggle the buttons

  var containerTimers = new Object();

  function hideArea(baseName){
    jQuery('#'+baseName).slideUp('slow', function(){});
    jQuery('#'+baseName+'_hide').hide();
    jQuery('#'+baseName+'_show').show();
    /*var container = $(baseName);
    if(container != undefined){
        if(checkAreaTimer(baseName) == false){
            return;
        }
        Effect.BlindUp(baseName, {'afterFinish' : function(){clearAreaTimer(baseName)}});
        $(baseName + "_hide").hide();
        $(baseName + "_show").show();
    }*/
  }

  function showArea(baseName){
    jQuery('#'+baseName).slideDown('slow', function(){});
    jQuery('#'+baseName+'_hide').show();
    jQuery('#'+baseName+'_show').hide();
    /*var container = $(baseName);
    if(container != undefined){
        if(checkAreaTimer(baseName) == false){
            return;
        }
        Effect.BlindDown(baseName, {'afterFinish' : function(){clearAreaTimer(baseName)}});
        $(baseName + "_hide").show();
        $(baseName + "_show").hide();
    }*/
  }

  function toggleArea(baseName){
    var container = jQuery('#'+baseName);
    if(container.css('display') == 'none'){
        showArea(baseName);
        return 'show';
    }else{
        hideArea(baseName);
        return 'hide';
    }
  }

  function checkAreaTimer(baseName){
    //get the value
    var d = new Date();
    if(containerTimers[baseName] != undefined){
        //use padded time of 3 seconds to check 'in use'
        if((containerTimers[baseName] + 3000) > d.getTime()){
            //still "in use"
            return false;
        }
    }
    //save it
    containerTimers[baseName] = d.getTime();
    return true;
  }
  //clear the timer since we are done
  function clearAreaTimer(baseName){
    containerTimers[baseName] = undefined;
  }
