function FMFormValidate(form) {

    this.isValid = true;
    this.invalidMessage = '';
    this.vForm = form;    
    this.invalidelement = null;
    this.validationErrorListeners = [];
    this.validationSuccessListener = null;
    this.self = this;

    this.serverSideValidation = [];

    this.submit = function ( form ) {
        return this.validateForm ( form );
    }

    this.validateForm = function(form) {
	if (form) { this.vForm = form; }
	try {
        var validateFields = [];
        this.serverSideValidation = [];
        this.confirmEmail = [];
		this.isValid = true;

        // This should contain the tag names of any form of input fields that we may validate
        var inputFields = new Array('INPUT','SELECT','TEXTAREA');
		
		for(i=0;i<inputFields.length;i++){
	        $(this.vForm).find(inputFields[i]).each(function () {
	    		validateFields.push ( $(this) );
	        });
	        
		};

        for(var i=0;i<validateFields.length;i++) {
            if (!this.validateField(validateFields[i])) {
                this.handleInvalid();
                return false;
            }
        }

        if (this.serverSideValidation.length > 0) {

            this.validateServerSide();
            //return false;

        } else {
	    
	    if(this.validationSuccessListener) {
			this.validationSuccessListener();
			return false;
	    } else {
			return true;
	    }

        }

	} catch(err) { return false;}

    }

    this.handleInvalid = function() {
       // if ( console )
/*             console.log(this.invalidMessage); */

        for (var i=0; i < this.validationErrorListeners.length; i++) {
            this.validationErrorListeners[i](this.invalidelement, this.invalidMessage);
        }
    }


    this.validateField = function(element) {
        // Checking 'required'
        if (!this.checkRequired($(element))) {
            this.isValid = false;
            this.invalidMessage = this.getFieldName($(element)) + ' is a required field';
            this.invalidelement = $(element);
            return false;
        }

		if(this.hasClass($(element), 'fmCheckBox') && this.hasClass($(element), 'required') && !$(element).checked) {
			this.isValid = false;
			
			var name = this.getFieldName($(element));
			name = name.substr(0, name.indexOf('_cb'));

			this.invalidMessage = name  + ' is a required field';
			this.invalidelement = $(element);
			return false;
		}

        // Date validation
        if (this.hasClass($($(element)),'fmDate') && $(element).val().length > 0) {
            
            var rx = /^[1-2][0-9]{3}-[0-1][0-9]-[0-3][0-9]$/;
            if (!rx.test($(element).val())) {
                this.isValid = false;
                this.invalidMessage = this.getFieldName($(element)) + ' does not have a valid date';
                this.invalidelement = $(element);
                return false;
            }

        } 

        // Phone number validation
        if ($(element).hasClass('fmPhonenumber') && $(element).val().length > 0) {
    
            $(element).val() = $(element).val().replace( /[^0-9]/g,'');
            var rx = /^[2-9][0-9][0-9][2-9][0-9]{6}$/;
            
            // This piece is ignored for now
            if (!rx.test($(element).val())) {
                this.isValid = false;
                this.invalidMessage = this.getFieldName($(element)) + ' does not contain a valid phone number';
                this.invalidelement = $(element);
                return false;
            }

        }

        if ($(element).hasClass('fmPassword')) {

            if ($(element).val().length < 6) {
                this.isValid = false;
                this.invalidMessage = 'Your password needs to be at least 6 characters long';
                this.invalidelement = $(element);
                return false;
            }
        }

        if ($(element).hasClass('fmPasswordConfirm')) {

            var passwordelements = this.vForm.getlementsByTagName('input');
            for(var i=0; i < passwordelements.length; i++) {

                var pwdElem = passwordelements[i];
                if (this.hasClass(pwdElem,'fmPassword')) {
                    
                    // Found our $(element)
                    if (pwdElem.value != $(element).val()) {
                        this.isValid = false;
                        this.invalidMessage = 'The passwords did not match';
                        this.invalidelement = $(element);
                        return false;
                    }
                }

            }

        }

        if ($(element).hasClass('fmEmail')) {

            var rx = /^([a-zA-Z0-9_\.\-\+])+\@(([a-zA-Z0-9\-\.])+\.)+([a-zA-Z0-9]{2,4})+$/;
            if (!rx.test($(element).val())) {
                this.isValid = false;
                this.invalidMessage = this.getFieldName($(element)) + ' does not contain a valid email address';
                this.invalidelement = $(element);
                return false;
            }
			
			if (this.hasClass($(element), 'confirm')) {
				for (var i=0; i < this.confirmEmail.length; i++) {
					if (this.confirmEmail[i] != $(element).val()) {
						this.isValid = false;
						this.invalidMessage = 'The email fields do not match';
						this.invalidelement = $(element);
						return false;
					} 
				}
				this.confirmEmail.push($(element).val());
			}

        }

        if ($(element).hasClass('fmPostalcode')) {

            $(element).val() = $(element).val().toUpperCase();
            var rx = /^([A-Z][0-9][A-Z](-| )?[0-9][A-Z][0-9])$/;

            if (!rx.test($(element).val())) {

                this.isValid = false;
                this.invalidMessage = this.getFieldName($(element)) + ' does not contain a valid Canadian postal code';
                this.invalidelement = $(element);
                return false;

            }

        }

        if ($(element).hasClass('fmUsername')) {

            $(element).val() = $(element).val().toLowerCase();
            var rx = /^[a-z][a-z0-9_]{5,}$/;
            if (!rx.test($(element).val())) {

                this.isValid = false;
                this.invalidMessage = 'A username has to be a minimum of 6 characters, can only contain A-Z 0-9 and _ (underscore) and has to start with a letter';
                this.invalidelement = $(element);
                return false;

            } else {

				if ($(element).hasClass('checkUnique')) {

					this.serverSideValidation.push({
						type : 'UniqueUser',
						element : element
					});    

				}

			}

        }

        if (this.hasClass($(element), 'fmCaptcha')) {

            this.serverSideValidation.push({
                type : 'VerifyCaptcha',
                element : element
            });

        }

        return true;

    }

    this.checkRequired = function(element) {
	var value = $(element).val();
	value = value.replace(/ /g, '');
		// This method returns true if the $(element) wasn't required or the $(element) has a value
        return (!$(element).hasClass('required') || (value.length>0 && !this.hasClass($(element), 'fmCheckBox')) || this.hasClass($(element), 'fmCheckBox'));
    }

    this.hasClass = function(element, className) {
        var rx = new RegExp('(^| )' + className + '($| )');
        return (rx.test($(element).className));

    }

    this.getFieldName = function(element) {
        // This should scan for the label and return the proper label
        return $(element).attr('name');
    }

    this.addValidationErrorListener = function(callBack) {
        this.validationErrorListeners.push(callBack);
    }

    this.setValidationSuccessListener = function(callBack) {
		this.validationSuccessListener = callBack;
    }

    this.validateServerSide = function() {

        var params = {} 

        for(var i=0; i < this.serverSideValidation.length; i++) {

            var valD = this.serverSideValidation[i];
/*             console.log(valD); */
            switch (valD.type) {
                case 'UniqueEmail' :
                    params['requests[' + i + '][methodName]'] = 'users.verifyUserEmail';
                    params['requests[' + i + '][params][0]'] = vhost;
                    params['requests[' + i + '][params][1]'] = '';
                    params['requests[' + i + '][params][2]'] = valD.element.value;
                    break;
                case 'UniqueUser' :
                    params['requests[' + i + '][methodName]'] = 'users.verifyUserEmail';
                    params['requests[' + i + '][params][0]'] = vhost;
                    params['requests[' + i + '][params][1]'] = valD.element.value;
                    params['requests[' + i + '][params][2]'] = '';
                    break;
                case 'VerifyCaptcha' :
                    params['requests[' + i + '][methodName]'] = 'users.verifyCaptcha';
                    params['requests[' + i + '][params][0]'] = valD.element.value;
                    params['requests[' + i + '][params][1]'] = 0;
                    break;
                default :
/*                     console.log('Unknown server-side validation type: ' + valD.type); */
                    break;

            }

        }
        
        var self = this;
        jsonPRequest('system.multicall', params, function(result) { self.serverSideValidateResponse(result); });

    }

    this.serverSideValidateResponse = function(result) {

        for(var i=0; i < this.serverSideValidation.length; i++) {

            var valD = this.serverSideValidation[i];

            if (!result[i]) 
/*             	console.log('We expected ' + i + ' results to be returned, but we only got: ' + result.length); */
            var response = result[i][0];

            switch(valD.type) {

                case 'UniqueEmail' :
                    if (response!=1) { 
                        this.isValid = false;
                        this.invalidelement = valD.element;
                        this.invalidMessage = 'The email address supplied is already registered. Did you forget your login information?';
                    }
                    break;
                case 'UniqueUser' :
                    if (response!=1) { 
                        this.isValid = false;
                        this.invalidelement = valD.element;
                        this.invalidMessage = 'The username you picked was already taken. Please try something else';
                    }
                    break;
                case 'VerifyCaptcha' :
                    if (response==false) {
                        this.isValid = false;
                        this.invalidelement = valD.element;
                        this.invalidMessage = 'The captcha you entered was invalid, please try again';
                        try {
                            var captchaImg = document.getelementById(valD.element.id + '_captcha').children()[0];
							captchaImg.src = captchaImg.src + '&anoth=' + Math.random();
                        } catch (ex) {
/*                             console.log('We got an exception trying to refresh the captcha image: ' + ex); */
                        }
                    }
                    break;
                default :
/*                     console.log('Unknown server-side validation type: ' + valD.type); */
                    return false;


            }

			if (!this.isValid) break;

        }

        if (this.isValid) {
            
		    if(this.validationSuccessListener) {
				this.validationSuccessListener();
		    } else {
/* 		    	console.log("UMMMM"); */
				this.vForm.submit();
		    }
	        
		} else {
	   		
	   		this.handleInvalid();
	    }
	
	}

}

function handleValidation(element,message) {
	// we check if it already has the error class to prevent multiple messages appearing on the same $(element)
	
	if (!$(element).hasClass('errorField')) {
	
		// the name of the $(element)
		var name = message.substr(0,message.indexOf(' '));
		
		// the actually error message
		var originalMessage = message;
		var message = message.substr(message.indexOf(' '));
		
		// if its a checkbox, don't highlight the $(element) but highlight the text beside it
		if ($(element).type == "checkbox") {
			var label = $($(element)).next();
		} else {
			var label = $($(element).parent()[0]).prev().children();
		}
		
		// the title we're going to use
		var title = $(element).attr("title");
		
		// the title without an asterisk
		var titlesub = title.substr(0,title.indexOf('*'));
		
		// the output
		var out = '';
		
		// check the message and output the right title and message combo
		if (name == $(element).attr('name') && title) {
			if (message != "The email address supplied is already registered. Did you forget your login information?" && message.substr(1,8) != "username" && message.substr(0,11) != "The captcha") {
				if (title.indexOf('*') > -1) {
					out = titlesub+" "+message;
				} else {
					out = title+message;
				}
			} else if (message == "The email address supplied is already registered. Did you forget your login information?") {
				out = "The email address supplied is already registered.";
			} else {
				out = titlesub+" "+message;
			}
		} else {
		
			if (message.substr(1,8) === "username" && message.substr(14,6) !== "picked") {
				out = titlesub+" "+message.substr(10);
			} else	if (message.substr(1) === "passwords did not match") {
				out = "P"+message.substr(2);
			} else if ($(element).type == "checkbox") {
				out = titlesub+message;
			} else {
				out = originalMessage;
			}
		}
		
		// get all the labels in this from
		var labels = $('label');
		for(var i=0;i<labels.length;i++) {
			// check all the labels for the one that matches the failed form $(element)
			if (labels[i].htmlFor == $(element).attr('id')) {
				// put the message inside the label
				//if ($(element).type != "checkbox") {
				$(labels[i]).html(out);
				//}
				// make the label stand out
				$(labels[i]).addClass('error');
				// make the input stand out
				$(element).addClass('errorField');
				// scroll the browser to that $(element).
			}
		}
	
		// remove the styles from the failed form $(element) and the label
		function removeStyles(t) {
			// search though all the labels again
			for(var i=0;i<labels.length;i++) {
				// find the label that matches the failed form $(element)
				if (labels[i].htmlFor == $(element).attr('id')) {
					// return everything to normal
					$(labels[i]).html(t);
					$(labels[i]).removeClass('error');
					$(element).removeClass('errorField');
				}
			}
		}

	}

	// we want the $(element) and label to retrun to normal when the user relizes the error.
	$(element).focusin(function() {
		removeStyles(title);
	});
}
/***************************************************************/
/* SabreTooth 5 JavaScript library loader                      */
/*                                                             */
/* loaded libraries:                                           */
/* core                                                        */
/* util                                                        */
/* api                                                         */
/* cookies                                                     */
/* flash                                                       */
/* geolocation                                                 */
/*                                                             */
/***************************************************************/


if (typeof fm == 'undefined') {

	fm = function() { }

} 


fm.util = function() {}

fm.util.toJSON = function(obj) {

    var type = typeof obj;

    switch(type) {

		case 'object' :
			if (!obj) return null; 
			var list = [];
			if (obj instanceof Array) {
				for(var i=0; i < obj.length; i++) {
					list.push(fm.util.toJSON(obj[i]));
				}
				return '[' + list.join(',') + ']';
			}

			for(var prop in obj) {
				list.push(fm.util.toJSON(prop) + ':' + fm.util.toJSON(obj[prop]));
			}
			return '{' + list.join(',') + '}';

		case 'string' :
            // Easiest way to handle this is to escape everything in a single operation,
            // otherwise you're likely to run into issues where the backslash from one
            // escape sequence gets escaped again.
            return '"' + obj.replace (
                /([\\"\n\t\r\b\f])/g,
                function ( specialChar ) {
                    switch ( specialChar ) {
                        case '\n': return '\\n';
                        case '\t': return '\\t';
                        case '\r': return '\\r';
                        case '\b': return '\\b';
                        case '\f': return '\\f';

                        case '"':
                        case '\\':
                            return '\\' + specialChar;

                        default: return specialChar;
                    }
                }
            ) + '"';

		case 'number' :
		case 'boolean' :
			return new String(obj);

    }

}

fm.util.getXMLHTTPObject = function() {

	try {
		var x = new XMLHttpRequest();
    } catch(e) {
		try {
			var x = new ActiveXObject('Msxml2.XMLHTTP');
        } catch(e) {
 			try { 
				var x = new ActiveXObject('Microsoft.XMLHTTP'); 
			} catch(e) {
				fm.util.log('Fatal error, could not locate XMLHTTPObject');
				return null;
			}
        } 
	}
	return x;

}

fm.util.log = function(message) {
	/*
if (console.log) {
		console.log(message);
	
}*/
}

fm.util.escapeHTML = function(str) {

	if (typeof(str)!='string') str = str.toString();
	return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');

}

fm.util.toQueryString = function(hashmap) {

	var parts = [];
	for(prop in hashmap)
		 parts.push(escape(prop) + '=' + escape(hashmap[prop]));

	return parts.join('&');

}
fm.api = function() { }

fm.api.Request = function(method,args) {

  this.method = method;
  this.args   = args;
  this.onResult = null;
  this.onError  = null;
  this.endPoint = '/services/json';
  this.currentRequest = null;

  this.invoke = function () {

	var request = fm.util.getXMLHTTPObject();
    this.currentRequest = request; 
  	request.open("POST",this.endPoint,true);
    request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8');
	var self = this;
    request.onreadystatechange = function() { self.onreadystatechange() };
    request.send('method=' + this.method + '&jsonArgs=' + encodeURI(fm.util.toJSON(args)));

  }

  this.onreadystatechange = function() {
    switch(this.currentRequest.readyState) {

		case 4 :
			var resultData = eval("(" + this.currentRequest.responseText + ")");
 			if (resultData.status == true) {
				if (this.onResult) this.onResult(resultData.result);
			} else if (this.onError) {
				this.onError(resultData);
			} else {
				fm.util.log('Unhandled error from json api: ' + resultData.result);
			}
			break; 

    }

  }

} 
fm.cookies = function() { }

fm.cookies.addCookie = function(name,value, expire) {
    
    if (expire){
        var date = new Date();
        date.setTime(date.getTime() + (expire * 1000)); // getTime returns milliseconds since 1970. We pass it the number of seconds (* 1000 for milliseconds)  we want the cookie to live from "now".

        var expires = "; expires="+date.toGMTString();
    } else {
        var expires = "";
    }
	
    var newCookie = name + '=' + escape(value) + expires +"; path=/";
	
    document.cookie = newCookie;

}

fm.cookies.getCookie = function(name) {
	var results = document.cookie.match ( '(^|; )' + name + '=(.*?)(;|$)' );

	if ( results )
		return unescape ( results [ 2 ] );
	else
		return null;
}
fm.flash = function() { }

fm.flash.getVersion = function() {

	var version = [0, 0, 0];

	if (navigator.plugins && navigator.plugins.length && navigator.plugins['Shockwave Flash']) {

		var plugin = navigator.plugins['Shockwave Flash'];

		var versionparts = plugin.description.match(/Shockwave Flash ([\d]*).([\d]*) r([\d]*)/);
		version = [ parseInt(versionparts[1]), parseInt(versionparts[2]), parseInt(versionparts[3]) ];

	}

	if (window.ActiveXObject) {

		try {

			var x = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
			if (x) {
				var versionparts = x.GetVariable("$version").split(' ')[1].split(',');
				version = [ parseInt(versionparts[0]), parseInt(versionparts[1]), parseInt(versionparts[2]) ]	
			}

		} catch (e) {  };

	}

	return version;

}

fm.flash.Object = function(options) {

	this.src = null;
	this.width = 0;
	this.height = 0;
	this.flashVars = {}
	this.allowScriptAccess = 'always';
    this.backgroundColor = null;
	this.allowFullScreen = true;
	this.wmode = null;
	this.id = null;

	// Loop through options, only overwrite property if they already exist
	for(i in options)
		if (typeof(this[i])!='undefined')
			this[i] = options[i];


	this.embed = function(element) {

		if (typeof(element)=='string') 
			element = document.getElementById(element);
		$(element).html(this.generateHTML());
		

	}

	this.write=function() {

		document.write(this.generateHTML());

	}

	this.generateHTML = function() {

		if (!this.src) throw 'Url to swf was not set';
		var html = '<object type="application/x-shockwave-flash" data="' +
			fm.util.escapeHTML(this.src) + '" ' 
			+ 'width="' + fm.util.escapeHTML(this.width) + '" ' 
			+ 'height="' + fm.util.escapeHTML(this.height) + '" ';
			
		if (this.id) html+='id="' + fm.util.escapeHTML(this.id) + '"'; 

		html+='>\n';

		html+='  <param name="movie" value="' + fm.util.escapeHTML(this.src) + '" />\n';

		if (this.allowScriptAccess) 
			html+='  <param name="allowScriptAccess" value="' + fm.util.escapeHTML(this.allowScriptAccess) + '" />\n';

		if (this.backgroundColor) 
			html+='  <param name="bgcolor" value="' + fm.util.escapeHTML(this.backgroundColor) + '" />\n';

		if (this.allowFullScreen)
			html+='  <param name="allowFullScreen" value="true" />\n';

		if (this.flashVars)
			html+='  <param name="flashvars" value="' + fm.util.toQueryString(this.flashVars) + '" />\n';
 
		// Wmode controls the transparancy. Use 'transparent' or 'opaque'
		if (this.wmode)
			html+='  <param name="wmode" value="' + fm.util.escapeHTML(this.wmode) + '" />\n'; 
		
		html+='</object>';

		return html;

	}

}


fm.geolocation = function() { }

fm.geolocation.getCurrentPosition = function(successCallback, errorCallback, options) {

	// Built-in
	if (false && typeof navigator.geolocation != 'undefined') {

		var geo = navigator.geolocation;

	} else {

	// Google gears
		try {	

			var geo = google.gears.factory.create('beta.geolocation');

		} catch (ex) { 
			errorCallback({ code: 0, message: 'Could not find support for the geolocation api'});
			return;
		}

	}

	geo.getCurrentPosition(successCallback, errorCallback, options);

}