﻿(function($) {

    /* ---------- jQuery Element Extensions ---------- */

    $.fn.alignCenter = function(options) {
        options = $.extend({
            forceAbsolute: false,
            container: window,
            completeHandler: null
        }, options);

        return this.each(function(i) {
            var el = $(this);
            var jWin = $(options.container);
            var isWin = options.container == window;

            if (options.forceAbsolute) {
                if (isWin) {
                    el.remove().appendTo("body");
                } else {
                    el.remove().appendTo(jWin.get(0));
                }
            }

            el.css("position", "absolute");
            var heightFudge = isWin ? 2.0 : 1.8;
            var x = (isWin ? jWin.width() : jWin.outerWidth()) / 2 - el.outerWidth() / 2;
            var y = (isWin ? jWin.height() : jWin.outerHeight()) / heightFudge - el.outerHeight() / 2;
            el.css("left", x + jWin.scrollLeft()).css("top", y + jWin.scrollTop());

            if (options.completeHandler) options.completeHandler(this);
        });
    };

    $.fn.enableIE6Transparency = function(options) {
        options = $.extend({
            blankImageUrl: "/i/_.gif"
        }, options);

        if (navigator.appName == "Microsoft Internet Explorer" && parseInt(navigator.appVersion) == 4 && (navigator.appVersion.indexOf("MSIE 5.5") != -1 || navigator.appVersion.indexOf("MSIE 6.0") != -1)) {
            return this.each(function(i) { $(this).css("filter", "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled='true', src='" + $(this).attr("src") + "')").attr("src", options.blankImageUrl); });
        } else {
            return this;
        }
    },

    $.fn.enableIE6BackgroundTransparency = function(options) {
        options = $.extend({
            blankImageUrl: "/i/_.gif",
            scale: false
        }, options);

        if (navigator.appName == "Microsoft Internet Explorer" && parseInt(navigator.appVersion) == 4 && (navigator.appVersion.indexOf("MSIE 5.5") != -1 || navigator.appVersion.indexOf("MSIE 6.0") != -1)) {
            return this.each(function(i) {
                var bgCss = $(this).css("background-image");
                $(this).css({ "background-image": "none", "filter": "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled='true', sizingMethod='" + (options.scale ? "scale" : "crop") + "', src='" + bgCss.substring(5, bgCss.length - 2) + "')" });
            });
        } else {
            return this;
        }
    },

    $.fn.overlay = function(options) {
        options = $.extend({
            overlayZIndex: 1000,
            overlayColor: "#303132",
            overlayOpacity: 0.87
        }, options);

        $.overlay.show(options.overlayZIndex, options.overlayColor, options.overlayOpacity);

        return this.each(function(i) {
            $(this).addClass("Phizz_Overlay").css({ "z-index": (options.overlayZIndex + $.overlay.n + 1), "visibility": "visible", "opacity": 0 }).alignCenter().fadeTo(200, 1);
        });
    };

    $.fn.unoverlay = function() {
        $.overlay.hide();

        return this.each(function(i) {
            $(this).removeClass("Phizz_Overlay").fadeTo(200, 0, function() { $(this).css({ "top:": "-1000px", "visibility": "hidden" }); });
        });
    };

    $.fn.rotateImages = function(options) {
        options = $.extend({
            urlArray: [],
            delay: 5000,
            speed: 750
        }, options);

        return this.each(function(i) {
            $.rotator.initialise($(this), options.urlArray, options.delay, options.speed);
        });
    }




    /* ---------- Backwards Compatibility ---------- */

    $.backwardsCompatibility = {
        hideIE6Foregound: function() {
            $("object, select").each(function() {
                if ($(this).css("visibility") == null || $(this).css("visibility") == "visible") $(this).css("visibility", "hidden").addClass("IE6Foreground");
            });
        },
        unhideIE6Foreground: function() {
            $(".IE6Foreground").css("visibility", "visible").removeClass("IE6Foreground");
        }
    }




    /* ---------- Date ---------- */

    $.dateTime = {

        monthNames: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
        dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],

        toString: function(dt, strFormat) {
            strFormat = strFormat.replace(/yyyy/g, dt.getFullYear()).replace(/yy/g, dt.getFullYear().toString().substr(2, 2));
            strFormat = strFormat.replace(/MMMM/g, "$1").replace(/MMM/g, "$2").replace(/MM/g, $.padStart((dt.getMonth() + 1).toString(), "0", 2)).replace(/M/g, dt.getMonth() + 1);
            strFormat = strFormat.replace(/dddd/g, "$3").replace(/ddd/g, "$4").replace(/dd/g, $.padStart(dt.getDate().toString(), "0", 2)).replace(/d/g, dt.getDate());
            strFormat = strFormat.replace(/HH/g, $.padStart(dt.getHours().toString(), "0", 2)).replace(/H/g, dt.getHours());
            strFormat = strFormat.replace(/hh/g, $.padStart((dt.getHours() > 12 ? dt.getHours() - 12 : dt.getHours()).toString(), "0", 2)).replace(/h/g, dt.getHours() > 12 ? dt.getHours() - 12 : dt.getHours());
            strFormat = strFormat.replace(/mm/g, $.padStart(dt.getMinutes().toString(), "0", 2)).replace(/m/g, dt.getMinutes());
            strFormat = strFormat.replace(/ss/g, $.padStart(dt.getSeconds().toString(), "0", 2)).replace(/s/g, dt.getSeconds());
            strFormat = strFormat.replace(/tt/g, dt.getHours() > 12 ? "PM" : "AM").replace(/t/g, dt.getHours() > 12 ? "P" : "A");
            strFormat = strFormat.replace(/\$1/g, this.monthNames[dt.getMonth()]).replace(/\$2/g, this.monthNames[dt.getMonth()].substr(0, 3)).replace(/\$3/g, this.dayNames[dt.getDay()]).replace(/\$4/g, this.dayNames[dt.getDay()].substr(0, 3));
            return strFormat;
        },

        isValid: function(strDate, strFormat) {
            var dt;
            try {
                dt = this.parse(strDate, strFormat);
                return true;
            } catch (err) { }
            return false;
        },

        parse: function(strDate, strFormat) {
            strDate = strDate.toLowerCase();

            var i;
            for (i = 0; i < this.monthNames.length; i++) if (strDate.indexOf(this.monthNames[i].toLowerCase()) > -1) {
                strDate = strDate.replace(this.monthNames[i].toLowerCase(), i < 9 ? "0" + (i + 1) : i + 1);
                strFormat = strFormat.replace("MMMM", "MM");
                break;
            }
            for (i = 0; i < this.monthNames.length; i++) if (strDate.indexOf(this.monthNames[i].toLowerCase().substr(0, 3)) > -1) {
                strDate = strDate.replace(this.monthNames[i].toLowerCase().substr(0, 3), i < 9 ? "0" + (i + 1) : i + 1);
                strFormat = strFormat.replace("MMM", "MM");
                break;
            }

            var parsedDateElements = { year: null, month: null, day: null, hours: null, minutes: null, seconds: null, ampm: null };

            var tokens = new Array();
            while (strFormat.length > 0) {
                var obj = (function(s) {
                    var token = "";
                    if (s.search(/[yMdHhmst]/) == 0) {
                        do {
                            token = token.concat(s.charAt(0));
                            s = s.substr(1);
                        } while (s.length > 0 && s.charAt(0) == token.charAt(0));
                    } else {
                        token = "'";
                        do {
                            token = token.concat(s.charAt(0));
                            s = s.substr(1);
                        } while (s.length > 0 && s.search(/[yMdHhmst]/) != 0);
                        token = token.concat("'");
                    }
                    return { token: token, strFormat: s };
                })(strFormat);
                tokens.push(obj.token);
                strFormat = obj.strFormat;
            }

            for (i = 0; i < tokens.length; i++) {
                var token = tokens[i];
                if (/^'[^']+'$/.test(token)) {
                    token = token.substr(1, token.length - 2);
                    if (strDate.indexOf(token) == 0) {
                        strDate = strDate.substr(token.length);
                    } else {
                        throw new Error("Input string does not match format string.");
                    }
                } else if (token == "yyyy") {
                    if (parsedDateElements.year != null) throw new Error("Invalid format string. Year cannot be parsed more than once.");
                    parsedDateElements.year = parseInt(strDate.substr(0, 4));
                    strDate = strDate.substr(4);
                } else if (token == "yy") {
                    if (parsedDateElements.year != null) throw new Error("Invalid format string. Year cannot be parsed more than once.");
                    parsedDateElements.year = parseInt("20".concat(strDate.substr(0, 2)));
                    strDate = strDate.substr(2);
                } else if (token == "MM") {
                    if (parsedDateElements.month != null) throw new Error("Invalid format string. Month cannot be parsed more than once.");
                    parsedDateElements.month = parseInt(strDate.charAt(0) == "0" ? strDate.substr(1, 1) : strDate.substr(0, 2));
                    strDate = strDate.substr(2);
                } else if (token == "M") {
                    if (parsedDateElements.month != null) throw new Error("Invalid format string. Month cannot be parsed more than once.");
                    if (/^1[012]/.test(strDate)) {
                        parsedDateElements.month = parseInt(strDate.substr(0, 2));
                        strDate = strDate.substr(2);
                    } else {
                        parsedDateElements.month = parseInt(strDate.substr(0, 1));
                        strDate = strDate.substr(1);
                    }
                } else if (token == "dd") {
                    if (parsedDateElements.day != null) throw new Error("Invalid format string. Day cannot be parsed more than once.");
                    if (!/^(0[1-9]|[12][0-9]|3[01])/.test(strDate)) throw new Error("Input string does not match format string.");
                    parsedDateElements.day = parseInt(strDate.charAt(0) == "0" ? strDate.substr(1, 1) : strDate.substr(0, 2));
                    strDate = strDate.substr(2);
                } else if (token == "d") {
                    if (parsedDateElements.day != null) throw new Error("Invalid format string. Day cannot be parsed more than once.");
                    if (!/^([1-9]|[12][0-9]|3[01])/.test(strDate)) throw new Error("Input string does not match format string.");
                    if (/^([12][0-9]|3[01])/.test(strDate)) {
                        parsedDateElements.day = parseInt(strDate.substr(0, 2));
                        strDate = strDate.substr(2);
                    } else {
                        parsedDateElements.day = parseInt(strDate.substr(0, 1));
                        strDate = strDate.substr(1);
                    }
                } else if (token == "HH") {
                    if (parsedDateElements.hours != null) throw new Error("Invalid format string. Hours cannot be parsed more than once.");
                    if (!/^([0-1][0-9]|2[0-3])/.test(strDate)) throw new Error("Input string does not match format string.");
                    parsedDateElements.hours = parseInt(strDate.charAt(0) == "0" ? strDate.substr(1, 1) : strDate.substr(0, 2));
                    strDate = strDate.substr(2);
                } else if (token == "H") {
                    if (parsedDateElements.hours != null) throw new Error("Invalid format string. Hours cannot be parsed more than once.");
                    if (!/^(0-9|1[0-9]|2[0-3])/.test(strDate)) throw new Error("Input string does not match format string.");
                    if (/^(1[0-9]|2[0-3])/.test(strDate)) {
                        parsedDateElements.hours = parseInt(strDate.substr(0, 2));
                        strDate = strDate.substr(2);
                    } else {
                        parsedDateElements.hours = parseInt(strDate.substr(0, 1));
                        strDate = strDate.substr(1);
                    }
                } else if (token == "hh") {
                    if (parsedDateElements.hours != null) throw new Error("Invalid format string. Hours cannot be parsed more than once.");
                    if (!/^(0[1-9]|1[0-2])/.test(strDate)) throw new Error("Input string does not match format string.");
                    parsedDateElements.hours = parseInt(strDate.charAt(0) == "0" ? strDate.substr(1, 1) : strDate.substr(0, 2));
                    strDate = strDate.substr(2);
                } else if (token == "h") {
                    if (parsedDateElements.hours != null) throw new Error("Invalid format string. Hours cannot be parsed more than once.");
                    if (!/^([1-9]|1[0-2])/.test(strDate)) throw new Error("Input string does not match format string.");
                    if (/^1[0-2]/.test(strDate)) {
                        parsedDateElements.hours = parseInt(strDate.substr(0, 2));
                        strDate = strDate.substr(2);
                    } else {
                        parsedDateElements.hours = parseInt(strDate.substr(0, 1));
                        strDate = strDate.substr(1);
                    }
                } else if (token == "mm") {
                    if (parsedDateElements.minutes != null) throw new Error("Invalid format string. Minutes cannot be parsed more than once.");
                    if (!/^[0-5]?[0-9]/.test(strDate)) throw new Error("Input string does not match format string.");
                    parsedDateElements.minutes = parseInt(strDate.charAt(0) == "0" ? strDate.substr(1, 1) : strDate.substr(0, 2));
                    strDate = strDate.substr(2);
                } else if (token == "m") {
                    if (parsedDateElements.minutes != null) throw new Error("Invalid format string. Minutes cannot be parsed more than once.");
                    if (/^[1-5][0-9]/.test(strDate)) {
                        parsedDateElements.minutes = parseInt(strDate.substr(0, 2));
                        strDate = strDate.substr(2);
                    } else {
                        parsedDateElements.minutes = parseInt(strDate.substr(0, 1));
                        strDate = strDate.substr(1);
                    }
                } else if (token == "ss") {
                    if (parsedDateElements.seconds != null) throw new Error("Invalid format string. Seconds cannot be parsed more than once.");
                    if (!/^[0-5]?[0-9]/.test(strDate)) throw new Error("Input string does not match format string.");
                    parsedDateElements.seconds = parseInt(strDate.charAt(0) == "0" ? strDate.substr(1, 1) : strDate.substr(0, 2));
                    strDate = strDate.substr(2);
                } else if (token == "s") {
                    if (parsedDateElements.seconds != null) throw new Error("Invalid format string. Seconds cannot be parsed more than once.");
                    if (/^[1-5][0-9]/.test(strDate)) {
                        parsedDateElements.seconds = parseInt(strDate.substr(0, 2));
                        strDate = strDate.substr(2);
                    } else {
                        parsedDateElements.seconds = parseInt(strDate.substr(0, 1));
                        strDate = strDate.substr(1);
                    }
                } else if (token == "tt") {
                    if (parsedDateElements.ampm != null) throw new Error("Invalid format string. AM/PM cannot be parsed more than once.");
                    if (!/^[ap]m/.test(strDate)) throw new Error("Input string does not match format string.");
                    parsedDateElements.ampm = strDate.substr(0, 1);
                    strDate = strDate.substr(2);
                } else if (token == "t") {
                    if (parsedDateElements.ampm != null) throw new Error("Invalid format string. AM/PM cannot be parsed more than once.");
                    if (!/^[ap]/.test(strDate)) throw new Error("Input string does not match format string.");
                    parsedDateElements.ampm = strDate.substr(0, 1);
                    strDate = strDate.substr(1);
                }
            }

            if (strDate.length > 0) throw new Error("Input string does not match format string.");

            if (parsedDateElements.hours < 12 && parsedDateElements.ampm == "p") parsedDateElements.hours += 12;

            var dt = new Date();
            if (parsedDateElements.year != null) dt.setFullYear(parsedDateElements.year);
            dt.setMonth(parsedDateElements.month != null ? parsedDateElements.month - 1 : 0);
            dt.setDate(parsedDateElements.day != null ? parsedDateElements.day : 1);
            dt.setHours(parsedDateElements.hours != null ? parsedDateElements.hours : 0);
            dt.setMinutes(parsedDateElements.minutes != null ? parsedDateElements.minutes : 0);
            dt.setSeconds(parsedDateElements.seconds != null ? parsedDateElements.seconds : 0);

            return dt;
        },

        daysInMonth: function(date) {
            switch (date.getMonth() + 1) {
                case 1: case 3: case 5: case 7: case 8: case 10: case 12: return 31; break;
                case 4: case 6: case 9: case 11: return 30; break;
                case 2: return this.isLeapYear(date.getFullYear()) ? 29 : 28; break;
            }
        },

        isLeapYear: function(year) {
            return year % 4 == 0 ? (year % 100 == 0 ? year % 400 == 0 : true) : false;
        }

    };




    /* ---------- Navigation  ---------- */

    $.navigate = function(url) {
        if (window.event) window.event.returnValue = false;
        window.location.href = url;
    };

    $.openWindow = function(url) {
        window.open(url);
    };

    $.queryString = {
        path: null,
        keys: null,
        values: null,

        parse: function() {
            if (this.keys != null) return;
            var url = window.location.href;
            this.keys = new Array();
            this.values = new Array();
            if (url.indexOf("?") > -1) {
                var n = url.indexOf("?");
                this.path = url.substr(0, n);
                var pairs = url.substr(n + 1).split("&");
                for (var i = 0; i < pairs.length; i++) {
                    n = pairs[i].indexOf("=");
                    this.append(pairs[i].substr(0, n), $.urlDecode(pairs[i].substr(n + 1)), true);
                }
            } else {
                this.path = url;
            }
        },

        commit: function() {
            var qs = "";
            for (var i = 0; i < this.keys.length; i++) qs = qs.concat(qs.length > 0 ? "&" : "", $.urlEncode(this.keys[i]), "=", $.urlEncode(this.values[this.keys[i]]));
            $.navigate(this.path + (qs.length > 0 ? "?" + qs : ""));
        },

        get: function(key) {
            this.parse();
            return this.values[key];
        },

        hasKey: function(key) {
            return this.get(key) != null;
        },

        append: function(key, value, cancelCommit) {
            this.parse();

            if (this.hasKey(key) && this.values[key].length > 0) {
                if (this.values[key] != value) this.values[key] = this.values[key].concat(",", value);
            } else {
                this.keys.push(key);
                this.values[key] = value;
            }

            if (!cancelCommit) this.commit();
        },

        appendMultiple: function(keysAndValues, cancelCommit) {
            for (var key in keysAndValues) this.append(key, keysAndValues[key], true);
            if (!cancelCommit) this.commit();
        },

        set: function(key, value, cancelCommit) {
            this.parse();

            if (!this.hasKey(key)) this.keys.push(key);
            this.values[key] = value;

            if (!cancelCommit) this.commit();
        },

        setMultiple: function(keysAndValues, cancelCommit) {
            for (var key in keysAndValues) this.set(key, keysAndValues[key], true);
            if (!cancelCommit) this.commit();
        },

        remove: function(key, cancelCommit) {
            this.parse();

            if (this.hasKey(key)) {
                for (var i = 0; i < this.keys.length; i++) if (this.keys[i] == key) {
                    this.keys.splice(i, 1);
                    break;
                }
                delete this.values[key];
            }

            if (!cancelCommit) this.commit();
        },

        removeMultiple: function(keys, cancelCommit) {
            for (var i = 0; i < keys.length; i++) this.remove(keys[i], true);
            if (!cancelCommit) this.commit();
        }
    };




    /* ---------- Overlay  ---------- */

    $.overlay = {
        n: 0,

        show: function(zIndex, color, opacity) {
            if (document.getElementById("Phizz_Overlay") == null) $(document.body).append("<div id=\"Phizz_Overlay\" style=\"position:absolute;visibility:hidden;width:100%;height:1px;top:-1px;left:0px;\">&nbsp;</div>");

            var el = $("#Phizz_Overlay");
            el.css({ "z-index": zIndex, "background-color": color });

            if (this.n++ == 0) {
                el.css({ "visibility": "visible", "top": "0px", "height": $(document).height(), "opacity": 0 }).fadeTo(200, opacity);
                $(window).bind("resize", $.overlay.onWindowResize).bind("scroll", $.overlay.onWindowScroll);
            } else if (parseFloat(el.css("opacity")) != opacity) {
                el.css("opacity", opacity);
            }
        },

        hide: function() {
            if (--this.n == 0) {
                $("#Phizz_Overlay").fadeTo(200, 0, function() { $(this).css({ "visibility": "hidden", "top": "-1px", "height": "1px" }); });
                $(window).unbind("resize", $.overlay.onWindowResize).unbind("scroll", $.overlay.onWindowScroll);
            }
        },

        onWindowResize: function() {
            $(".Phizz_Overlay").alignCenter();
        },

        onWindowScroll: function() {
            $(".Phizz_Overlay").alignCenter();
        }
    };




    /* ---------- Rotator  ---------- */

    //TODO: handle elements with no id, handle elements with no <img>, handle non-<img> / multiple <img> child elements
    $.rotator = {
        urlArrays: null,
        indices: null,
        delays: null,
        speeds: null,

        initialise: function(jq, urlArray, delay, speed) {
            if (this.urlArrays == null) this.urlArrays = new Array();
            if (this.indices == null) this.indices = new Array();
            if (this.delays == null) this.delays = new Array();
            if (this.speeds == null) this.speeds = new Array();

            var id = jq.attr("id");
            this.indices[id] = 0;
            this.delays[id] = delay;
            this.speeds[id] = speed;

            this.urlArrays[id] = new Array(urlArray.length);
            var initialSrc = jq.find("img:eq(0)").attr("src");
            for (var i = 0; i < this.urlArrays[id].length; i++) {
                this.urlArrays[id][i] = urlArray.splice(Math.floor(Math.random() * urlArray.length), 1)[0];
                if (this.urlArrays[id][i] == initialSrc) { this.indices[id] = i; this.indices[id]; }
            }
            setTimeout(function() { $.rotator.rotate(jq); }, this.delays[id]);
        },

        rotate: function(jq) {
            var id = jq.attr("id");
            this.indices[id] = (this.indices[id] == this.urlArrays[id].length - 1) ? 0 : this.indices[id] + 1;
            var img = new Image();
            $(img).load(function() {
                $(this).hide();
                jq.append(this);
                $(this).fadeIn($.rotator.speeds[id], function() {
                    jq.find("img:eq(0)").remove();
                    setTimeout(function() { $.rotator.rotate(jq); }, $.rotator.delays[id]);
                });
            }).attr("src", this.urlArrays[id][this.indices[id]]);
        }
    };




    /* ---------- Utilities ---------- */

    $.padStart = function(strToPad, chrPadding, intLength) {
        while (strToPad.length < intLength) strToPad = chrPadding.concat(strToPad);
        return strToPad;
    };

    $.padEnd = function(strToPad, chrPadding, intLength) {
        while (strToPad.length < intLength) strToPad = strToPad.concat(chrPadding);
        return strToPad;
    };

    $.preloadImages = function() {
        for (var i = 0; i < arguments.length; i++) jQuery("<img>").attr("src", arguments[i]);
    };

    $.urlEncode = function(value) {
        var SAFECHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_.!~*'()";
        var HEX = "0123456789abcdef";

        var result = "";
        for (var i = 0; i < value.length; i++) {
            var c = value.charAt(i);
            if (c == " ") {
                result += "+";
            } else if (SAFECHARS.indexOf(c) != -1) {
                result += c;
            } else {
                var charCode = c.charCodeAt(0);
                if (charCode > 255) {
                    result += "+";
                } else {
                    result += "%";
                    result += HEX.charAt((charCode >> 4) & 0xF);
                    result += HEX.charAt(charCode & 0xF);
                }
            }
        }

        return result;
    };

    $.urlDecode = function(value) {
        var HEXCHARS = "0123456789ABCDEFabcdef";

        var result = "";
        var i = 0;
        while (i < value.length) {
            var c = value.charAt(i);
            if (c == "+") {
                result += " ";
                i++;
            } else if (c == "%") {
                if (i < (value.length - 2) && HEXCHARS.indexOf(value.charAt(i + 1)) != -1 && HEXCHARS.indexOf(value.charAt(i + 2)) != -1) {
                    result += unescape(value.substr(i, 3));
                    i += 3;
                } else {
                    result += "%[ERROR]";
                    i++;
                }
            } else {
                result += c;
                i++;
            }
        }

        return result;
    };




    /* ---------- Validation  ---------- */

    $.validation = {

        alert: function(s) { window.alert(s); },
        autoFocus: true,
        element: null,

        setElement: function(el) {
            this.element = el;
            if (this.autoFocus && $(el).css("visibility") != "hidden") el.focus();
        },

        hasValue: function(id) {
            var el = document.getElementById(id);
            if ($.trim(el.value).length == 0) {
                this.alert("Please provide a value for the " + el.title + " field.");
                this.setElement(el);
                return false;
            }
            return true;
        },

        matchesRegex: function(id, regExp, errorMessage, allowEmpty) {
            var el = document.getElementById(id);
            if (allowEmpty && el.value.length == 0) return true;
            if (!regExp.test(el.value)) {
                this.alert(errorMessage ? errorMessage : "Please enter a valid value.");
                this.setElement(el);
                return false;
            }
            return true;
        },

        hasEquivalence: function(id1, id2) {
            var el1 = document.getElementById(id1);
            var el2 = document.getElementById(id2);
            if ($.trim(el1.value) != $.trim(el2.value)) {
                this.alert("The " + el1.title + " and " + el2.title + " field values must be equivalent.");
                this.setElement(el1);
                return false;
            }
            return true;
        },

        matchesEmail: function(id, allowEmpty) {
            var el = document.getElementById(id);
            if (allowEmpty && el.value.length == 0) return true;
            if (!this.isEmail(el.value)) {
                this.alert("Please enter a valid email address in the " + el.title + " field.");
                this.setElement(el);
                return false;
            }
            return true;
        },

        isEmail: function(s) {
            return /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(s);
        },

        matchesInteger: function(id, allowEmpty) {
            var el = document.getElementById(id);
            if (allowEmpty && el.value.length == 0) return true;
            if (!this.isInteger(el.value)) {
                this.alert("Please enter a valid integer value in the " + el.title + " field.");
                this.setElement(el);
                return false;
            }
            return true;
        },

        isInteger: function(s) {
            return /^-?\d+$/.test(s);
        },

        matchesDecimal: function(id, allowEmpty) {
            var el = document.getElementById(id);
            if (allowEmpty && el.value.length == 0) return true;
            if (!this.isDecimal(el.value)) {
                this.alert("Please enter a valid numeric value in the " + el.title + " field.");
                this.setElement(el);
                return false;
            }
            return true;
        },

        isDecimal: function(s) {
            return /^-?\d+(\.\d+)?$/.test(s);
        },

        matchesDate: function(id, formatString, allowEmpty) {
            var el = document.getElementById(id);
            if (allowEmpty && el.value.length == 0) return true;
            if (!$.dateTime.isValid(el.value, formatString)) {
                this.alert("Please enter a valid date value in the " + el.title + " field, in the format " + formatString + ".");
                this.setElement(el);
                return false;
            }
            return true;
        },

        matchesHexColor: function(id, allowEmpty) {
            var el = document.getElementById(id);
            if (allowEmpty && el.value.length == 0) return true;
            if (!this.isHexColor(el.value)) {
                this.alert("Please enter a valid hexadecimal colour value in the " + el.title + " field.");
                this.setElement(el);
                return false;
            }
            return true;
        },

        isHexColor: function(s) {
            return /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(s);
        },

        hasSelection: function(id, allowEmpty) {
            var el = document.getElementById(id);

            if (el != null && el.nodeName.toLowerCase() == "select") { // if this is a dropdown selection
                // if (el.selectedIndex == 0 || (!allowEmpty && el.options[el.selectedIndex].value.length == 0)) {
                if (el.selectedIndex == 0 && el.options[el.selectedIndex].value.length == 0) {
                    this.alert("A value must be selected for the " + el.title + " field.");
                    this.setElement(el);
                    return false;
                }
                return true;

            } else if (document.getElementsByName(id).length > 0) { // if this is a checkbox or radio button list
                var checked = false;
                var elArray = document.getElementsByName(id);
                for (var i = 0; i < elArray.length; i++) if (elArray[i].checked) {
                    checked = true;
                    break;
                }
                if (!checked) {
                    this.alert("A value must be selected for the " + elArray[0].title + " field.");
                    this.setElement(elArray[0]);
                    return false;
                }
                return true;
            }
        }

    };




    /* ---------- XML ---------- */

    $.createXmlDocument = function(xml) {
        var objXmlDocument = null;
        if (window.ActiveXObject) {
            objXmlDocument = new ActiveXObject("MSXML2.FreeThreadedDOMDocument.3.0");
            if (xml != null && typeof (xml) == "string") {
                objXmlDocument.async = false;
                objXmlDocument.loadXML(xml);
            }
        } else if (window.DOMParser) {
            objXmlDocument = (xml != null && typeof (xml) == "string") ? (new DOMParser()).parseFromString(xml, "text/xml") : document.implementation.createDocument("ns", "root", null);
        }
        return objXmlDocument;
    },

	$.convertXmlDocumentToString = function(objXmlDocument) {
	    if (window.ActiveXObject) {
	        return objXmlDocument.documentElement.xml;
	    } else if (window.DOMParser) {
	        return (new XMLSerializer()).serializeToString(objXmlDocument);
	    }
	    return "";
	},

	$.xslt = function(objXmlDocument, objXslDocument, params) {
	    if (window.ActiveXObject) {
	        var objXSLTemplate = new ActiveXObject("MSXML2.XSLTemplate.3.0");
	        objXSLTemplate.stylesheet = objXslDocument;
	        var objProcessor = objXSLTemplate.createProcessor();
	        objProcessor.input = objXmlDocument;
	        if (params != null && typeof (params) == "object") for (var key in params) objProcessor.addParameter(key, params[key]);
	        objProcessor.transform();
	        return objProcessor.output;
	    } else if (window.XSLTProcessor) {
	        var objProcessor = new XSLTProcessor();
	        objProcessor.importStylesheet(objXslDocument);
	        if (params != null && typeof (params) == "object") for (var key in params) objProcessor.setParameter("", key, params[key]);
	        var objXmlTransformation = objProcessor.transformToDocument(objXmlDocument);
	        return (new XMLSerializer()).serializeToString(objXmlTransformation);
	    }
	    return "";
	}

})(jQuery);