/// <reference path="jquery-1.4.4.min.js" />
/// <reference path="jquery.blockUI.js" />
/// <reference path="jquery.store.js" />

$(document).ready(function () {
    try {
        //
        $.storage = new $.store();

        //Standard Ajax Einstellungen festlegen
        $.ajaxSettings.url = 'web/service.asmx';
        $.ajaxSettings.type = 'POST';
        $.ajaxSettings.contentType = 'application/json; charset=utf-8';
        $.ajaxSettings.dataType = 'json';

        //Standard für den Ajax Loading-Indicator
        $.blockUI.defaults.overlayCSS.backgroundColor = '#F2F2F2';
        $.blockUI.defaults.css.border = '1px solid #646464';
        $.blockUI.defaults.css.padding = '5px';
        $.blockUI.defaults.css.width = "150px";

        //Quickfinder Hinweis
        $('.toggle_value').clearAndRestoreDefaultValue("#646464");

        //EMail-Adresse-Default Text
        $('input[name="txtLoginMail"]').clearAndRestoreDefaultValue("#646464");
        $('input[name="cadAnfrageEmail"]').clearAndRestoreDefaultValue("#646464");
        $('input[name="dxfAnfrageEmail"]').clearAndRestoreDefaultValue("#646464");
        //Passwort-Default Text (hierfür muss eine zweite Textbox verwendet werden)
        $('input[name="txtLoginPasswordDefault"]').css('display', '');
        $('input[name="txtLoginPassword"]').css('display', 'none');
        $('input[name="txtLoginPasswordDefault"]').focus(onLoginPasswordDefaultFocus);
        $('input[name="txtLoginPassword"]').blur(onLoginPasswordBlur);

        $('#SendPasswordLink').click(function () {
            $(this).css('display', 'none');
            $(this).next().css('display', '');
        });
        $('input[name="txtMailPassword"]').clearAndRestoreDefaultValue("#646464");

        $('input[name="txtEMail"]').clearAndRestoreDefaultValue("#646464");
        $('input[name="txtCaptcha"]').clearAndRestoreDefaultValue("#646464");

        //Autocomplete deaktivieren
        $('input[name="txtQuickfinder"]').attr('autocomplete', 'off');

        //Autocomplete fuer den kompltten Warenkorb deaktivieren
        $('.warenkorb input[type="text"]').attr('autocomplete', 'off');

        //Autocomplete fuer Kennwoerter deaktvieren
        $('#KennwortContainer :password').attr('autocomplete', 'off');

        $('input[name="txtQuickfinder"]').keydown(function () {
            clearTimeout($.data(this, 'timer'));
            var wait = setTimeout(autoSuggest, 250);
            $(this).data('timer', wait);
        });
        $('#auto_suggest_close').click(function () {
            $('.auto_suggest').hide();
        });

        $('.add_to_cart #Menge').keydown(function () {
            var elemMenge;

            elemMenge = $(this);
            clearTimeout($.data(this, 'timer'));
            var wait = setTimeout(function () { onMengeKeydown(elemMenge) }, 250);
            $(this).data('timer', wait);
        });

        $('.warenkorb input[name^="txtMenge"]').keydown(function () {
            var elemMenge;

            elemMenge = $(this);
            clearTimeout($.data(this, 'timer'));
            var wait = setTimeout(function () { onMengeKeydown(elemMenge) }, 250);
            $(this).data('timer', wait);
        });

        //Ausgewaehlte Warenkorb optionen hervorheben
        $('.warenkorb :radio').change(function () {
            $('.warenkorb input[name="' + $(this).attr('name') + '"]').each(function () {
                if ($(this).attr('checked') == true || $(this).attr('checked') == 'checked') {
                    $('label[for="' + $(this).attr('id') + '"]').addClass('highlight');
                }
                else {
                    $('label[for="' + $(this).attr('id') + '"]').removeClass('highlight');
                }
            });
        }).trigger('change');

        //Artikelauswahl Varianten
        $('.options_box').find('input[id^="norm"]').change(onArtikelauswahlVariantenClick);

        //Artikelauswahl
        $('.options_box :radio').not('input[id^="norm"]').change(onArtikelauswahlClick);
        $('.options_box :image').click(onArtikelauswahlClick);
        $('.options_box :text').not('#ArtikelNr').not('.email').change(onArtikelauswahlClick);

        //Eintrag Toggle
        setEintragEinstellungenOrDefaults()
        $('.eintrag h2 img').not('#loginboxclose').click(onEintragClick);

        //Tooltips - Allgemein (isHovered Status)
        $('.tooltip').mouseenter(function () {
            $(this).data('isHovered', true);
        });
        $('.tooltip').mouseleave(function () {
            var tooltip;

            tooltip = $(this);
            tooltip.data('isHovered', false);
            setTimeout(function () { onTooltipMouseleave(tooltip) }, 400);
        });

        //Tooltips - Info
        $('.info').hoverIntent(getInfoHoverIntentConfig());
        //Tooltips - zuletzt geklickt
        $('a.last_clicked').hoverIntent(getZuletztGeklicktHoverIntentConfig());

        //Foto FancyBox
        $("a.product").fancybox({
            'overlayColor': '#fff',
            'overlayOpacity': '0.6'
        });

        //Mein Ganter
        //Kundenart
        $(".meinGanter #cboKundenart").change(function () {
            if ($(this).val() == "2") {
                $(".meinGanter .firma").hide();
            }
            else {
                $(".meinGanter .firma").show();
            }
        });
        //Kennwort ändern
        $(".meinGanter #KennwortAendern").click(function () {
            $(".meinGanter #KennwortContainer").show();
            $(this).hide();
        });
    }
    catch (ex) {
        //alert(ex);
    }
});

$.fn.clearAndRestoreDefaultValue = function(defaultColor){
	
	var defaultValue = $(this).val();
	
	if(defaultColor != "")
		$(this).css('color', defaultColor);
	
	$(this).focus(function(){
		if ($(this).val() == defaultValue) {			
			$(this).val('');
		}
		if(defaultColor != "")
			$(this).css('color', '#000');
	});
	
	$(this).blur(function(){
		if ($(this).val() == "") {
			$(this).val(defaultValue);
			
			if(defaultColor != "")
				$(this).css('color', defaultColor);	
		}
	});
}

function onLoginPasswordDefaultFocus() {
    $('input[name="txtLoginPasswordDefault"]').css('display', 'none');
    $('input[name="txtLoginPassword"]').css('display', '').focus();
}

function onLoginPasswordBlur() {
    if ($('input[name="txtLoginPassword"]').val() == '') {
        $('input[name="txtLoginPassword"]').css('display', 'none');
        $('input[name="txtLoginPasswordDefault"]').css('display', '');
    }
}

function onArtikelauswahlVariantenClick() {
    window.location = $(this).attr('value');
}

function ajaxFailed(xmlRequest) {
    //alert(xmlRequest.status + ' \n\r ' + xmlRequest.statusText + '\n\r' + xmlRequest.responseText);
}

function autoSuggest() {
    var quickfinder;
    var txtQuickfinder;
    var auto_suggest;
    var lcid;
    var strData;

    txtQuickfinder = $('input[name="txtQuickfinder"]');
    //auto_suggest = $('.auto_suggest');
    //Die Suche muss aus mindestens zwei Zeichen bestehen
    quickfinder = txtQuickfinder.val();
    if (quickfinder.length < 2) {
        $('.auto_suggest').hide();
        return;
    }
    //
    if (txtQuickfinder.data('quickfinder') == quickfinder) {
        return;
    }

    txtQuickfinder.data('quickfinder', quickfinder);
    lcid = $('input[name="LCID"]').val();
    if (lcid == undefined) {
        //Alternative Sprachermitteln im re-Lounge Kontext
        lcid = $('div.navigation form').attr('action');
        if (lcid == undefined) {
            lcid = "1031";
        }
        else {
            if (lcid.endsWith('DE,')) {
                lcid = "1031";
            }
            else {
                if (lcid.endsWith('EN,')) {
                    lcid = "2057";
                }
                else {
                    lcid = "1031";
                }
            }
        }
    }

    strData = '{ ' +
                '"pstrValue": "' + quickfinder + '",' +
                '"pintLCID": "' + lcid +
              '" }'

    $.ajax({
        url: $.ajaxSettings.url + '/QuickFinderAutocomplete',
        cache: false,
        data: strData,
        success: function (data, status) {
            if (data.count == 0) {
                $('.auto_suggest').hide();
            }
            else {
                $('#auto_suggest').html(data.result);
                $('.auto_suggest').show();
            }
        },
        error: ajaxFailed
    });
}

/*
****** Eintrag Toggle ******
*/

//Übernimmt die Benutzereinstellungen bzw. die default Einstellungen der Eintrag-Toggles
function setEintragEinstellungenOrDefaults() {
    $('.eintrag').each(function () {
        var id;
        var isClosed;
        var image;

        //Falls keine id vorhanden können keine Benutzereinstellungen
        //bzw. defaults definiert werden
        id = $(this).attr('id');
        if (id == undefined) {
            return
        }
        if (id.length == 0) {
            return
        }

        image = $(this).find('h2 img');
        isClosed = $.storage.get('eintrag#' + id);
        //Falls keine Benutzereinstellungen vorhanden sind
        //müssen ggf. die default Einstellungen übernommen werden
        //z.B. wird die Warenkorb und Anfrage Box standardmäßig zugeklappt angezeigt
        if (isClosed == undefined) {
            switch (id) {
                case 'Warenkorb':
                    closeToogle($(this), image);
                    break;
                case 'Anfrage':
                    closeToogle($(this), image);
                    break;
            }
            return
        }

        //
        if (isClosed) {
            closeToogle($(this), image);
        }
        else {
            openToogle($(this), image);
        }
    });
}

//Öffnet einen Eintrag-Toggle
function openToogle(eintrag, image) {
    image.attr('alt', '-');
    image.attr('src', 'images/button_minus.gif');
    eintrag.removeClass('closed');
}

//Schleißt einen Eintrag-Toggle
function closeToogle(eintrag, image) {
    image.attr('alt', '+');
    image.attr('src', 'images/button_plus.gif');
    eintrag.addClass('closed');
}

//Öffnet bzw, Schleißt einen Eintrag-Toggle
//und speichert sich den Zustand der Toggles (nur wenn eine ID vorhanden ist)
//somit wird bei einem erneuten Seitenaufruf die aktuellen Toggle Einstellungen übernommen
//siehe setEintragEinstellungenOrDefaults()
function onEintragClick() {
    var eintrag;
    var id;
    var isClosed;

    eintrag = $(this).parents('.eintrag:eq(0)');
    id = eintrag.attr('id');
    if (eintrag.hasClass('closed')) {
        openToogle(eintrag, $(this));
        isClosed = false;
    }
    else {
        closeToogle(eintrag, $(this));
        isClosed = true;
    }

    if (id.length > 0) {
        $.storage.set('eintrag#' + id, isClosed);
    }
}

function getInfoHoverIntentConfig() {
    return {
        interval: 200,
        timeout: 400,
        over: onInfoOver,
        out: onInfoOut
    }
}

function onInfoOver() {
    var tooltip;
    var iconOffset;
    var tooltipWidth;
    var tooltipHeight;

    tooltip = $(this).next();
    iconOffset = $(this).offset();
    tooltipWidth = tooltip.width();
    tooltipHeight = tooltip.height();

    tooltip.css({ top: iconOffset.top - (tooltipHeight - 5), left: iconOffset.left - (tooltipWidth - 28) });

    tooltip.show();
}

function onInfoOut() {
    var tooltip;

    tooltip = $(this).next();

    if (tooltip.data('isHovered') != true) {
        tooltip.hide();
    }
}

function getZuletztGeklicktHoverIntentConfig() {
    return {
        interval: 200,
        timeout: 400,
        over: onZuletztGeklicktOver,
        out: onZuletztGeklicktOut
    }
}

function onZuletztGeklicktOver() {
    var tooltip;
    var iconOffset;
    var tooltipWidth;
    var tooltipHeight;

    tooltip = $(this).next();
    iconOffset = $(this).offset();
    tooltipWidth = tooltip.width();
    tooltipHeight = tooltip.height();

    tooltip.css({ top: iconOffset.top - (tooltipHeight -4), left: iconOffset.left - (tooltipWidth - 50) });

    tooltip.show();
}

function onZuletztGeklicktOut() {
    var tooltip;

    tooltip = $(this).next();

    if (tooltip.data('isHovered') != true) {
        tooltip.hide();
    }
}

function onTooltipMouseleave(tooltip) {
    if (tooltip.data('isHovered') != true) {
        tooltip.hide();
    }
}

/*
   ****** Artikelauswahl ******
*/

//:Radio => Click
function onArtikelauswahlClick() {
    var strData;
    var strFilter;

    if ($(this).attr('type') == 'radio') {
        strFilter = $(this).attr('value');
    }
    else {
        var text;
        var hiddenText;

        text = $('.options_box :text').filter('[name=' + $(this).attr('name') + ']');
        hiddenText = $('.options_box :hidden').filter('[name=' + $(this).attr('name') + ']');

        strFilter = $(hiddenText).attr('value');

        strFilter = strFilter.substring(0, strFilter.lastIndexOf('('));
        strFilter = strFilter + '(' + $(text).attr('value') + ')'
    }
    

    strData = '{' + $('#AjaxRequestData').html() +
              '"pstrFilter":"' + strFilter + '"}';

    $.ajax({
        url: $.ajaxSettings.url + '/GetArtikelauswahl',
        cache: false,
        data: strData,
        beforeSend: ajaxArtikelauswahlBeforeSend,
        success: ajaxArtikelauswahlSuccess,
        error: ajaxFailed,
        complete: ajaxArtikelauswahlComplete
    });
}

//BeforeSend
function ajaxArtikelauswahlBeforeSend(jqXHR, settings) {
    $.blockUI({ message: $('#ajaxmsg') });
}

//Success
function ajaxArtikelauswahlSuccess(data, status) {
    var titelArtikelNr;
    var links;
    var formActions;
    var href;
    var action;
    var urlParameter;

    try {
        //ArtikelNr in der Artikelauswahl
        $('#ArtikelNr').attr('value', data.artikelNr);

        //Artikelauswahl
        $('#Artikelauswahl').html(data.artikelAuswahl);

        //ArtikelInfo
        $('#ArtikelInfo').replaceWith(data.artikelInfo);

        //ArtikelNr im Normblatttitel anpassen bzw. ein- und ausblenden
        titelArtikelNr = $('#TitelArtikelNr');
        if (data.isArtikelVollstaendig == true) {
            titelArtikelNr.html('&nbsp;(' + data.artikelNr + ')');
            titelArtikelNr.show();
            $('.eintrag.add_to_cart').removeClass('inactive');
            $('.eintrag.add_to_cart').find('.inactive_layer').hide();
            //Artikeloptionen Titel
            $('#artikeloptionTitel').show();
            $('#artikeloptionTitelUnvollstaendig').hide();
        }
        else {
            titelArtikelNr.hide();
            $('.eintrag.add_to_cart').addClass('inactive');
            $('.eintrag.add_to_cart').find('.inactive_layer').show();
            //Artikeloptionen Titel
            $('#artikeloptionTitel').hide();
            $('#artikeloptionTitelUnvollstaendig').show();
        }

        $('#AjaxRequestData').html(data.ajaxRequestData);

        //Links anpassen die auf die bisherige GUID referenzieren
        links = $('[href*="' + data.aktuelleGUID + '"]');
        links.each(function () {
            href = $(this).attr('href');
            href = href.replace(data.aktuelleGUID, data.neueGUID);

            //Wertebereich für die Norm 990
            urlParameter = new Object();
            if (data.rangeFilter == undefined) {
                //href = $.param.querystring(href, 'rangeFilter=').toString();
                urlParameter.rangeFilter = '';
            }
            else {
                //href = $.param.querystring(href, 'rangeFilter=' + data.rangeFilter).toString();
                urlParameter.rangeFilter = data.rangeFilter;
            }
            href = $.param.querystring(href, urlParameter);

            $(this).attr('href', href);
        });

        //Form-Actions anpassen die auf die bisherige GUID referenzieren
        formActions = $('.add_to_cart form, #CADDownload form, #CADDownloadNichtVorhanden form, #DXFDownload form, #DXFDownloadNichtVorhanden form');
        formActions.each(function () {
            action = $(this).attr('action');
            if (action != undefined) {
                action = action.replace(data.aktuelleGUID, data.neueGUID);
            }

            //Wertebereich für die Norm 990
            if (data.rangeFilter == undefined) {
                //action = $.param.querystring(href, 'rangeFilter=').toString();
                urlParameter.rangeFilter = '';
            }
            else {
                //action = $.param.querystring(href, 'rangeFilter=' + data.rangeFilter).toString();
                urlParameter.rangeFilter = data.rangeFilter;
            }
            action = $.param.querystring(action, urlParameter);

            $(this).attr('action', action);
        });

        if (data.isCADAnsicht == true) {
            if (data.isArtikelVollstaendig == true) {
                //CAD
                if (data.isCADVorhanden == true) {
                    $('#CADDownload').show();
                    $('#CADDownloadNichtVorhanden').hide();
                }
                else {
                    $('#CADDownload').hide();
                    $('#CADDownloadNichtVorhanden').show();
                }

                //DXF
                if (data.isDXFVorhanden == true) {
                    $('#DXFDownload').show();
                    $('#DXFDownloadNichtVorhanden').hide();
                }
                else {
                    $('#DXFDownload').hide();
                    $('#DXFDownloadNichtVorhanden').show();
                }
            }
            else {
                $('#CADDownload').hide();
                $('#CADDownloadNichtVorhanden').hide();
                $('#DXFDownload').hide();
                $('#DXFDownloadNichtVorhanden').hide();
            }

            $('#cadenasArtikel').html(data.cadenasArtikel);
            //$('#cadenasArtikel').val('');
        }

        //Wichtig! Die Change Events an die neuen Elemente binden 
        $('.options_box :radio').not('input[id^="norm"]').change(onArtikelauswahlClick);
        $('.options_box :image').click(onArtikelauswahlClick);
        $('.options_box :text').not('#ArtikelNr').not('.email').change(onArtikelauswahlClick);
        $('.info').hoverIntent(getInfoHoverIntentConfig());
    }
    catch (ex) {
        //alert(ex);
    }
}


//
function onMengeKeydown(elemMenge) {
    var menge;
    var guid;
    var verfuegbarkeit;

    menge = elemMenge.val();

    if (elemMenge.attr('id') == 'Menge') {
        verfuegbarkeit = $('img[id^="verfuegbarkeit_"]')
        guid = verfuegbarkeit.attr('id');
        guid = guid.substring(15, guid.length);
    }
    else {
        guid = elemMenge.attr('id');
        guid = guid.substring(6, guid.length);

        verfuegbarkeit = $('#verfuegbarkeit_' + guid)
    }
    
    strData = '{ ' +
                '"pArtikelGuid": "' + guid + '",' +
                '"pstrMenge": "' + menge +
            '" }'

    $.ajax({
        url: $.ajaxSettings.url + '/GetVerfuegbarkeitImageName',
        cache: false,
        data: strData,
        success: function (data, status) {
            verfuegbarkeit.attr('src', data)
        },
        error: ajaxFailed
    });
}

//Complete
function ajaxArtikelauswahlComplete(jqXHR, textStatus) {
    $.unblockUI()
}

//String.endsWith
String.prototype.endsWith = function (str)
{ return (this.match(str + "$") == str) }


/*
* http://www.JSON.org/json2.js
* 2011-10-19
* 
* Public Domain.
* 
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
* 
* See http://www.JSON.org/js.html
* 
* 
* This code should be minified before deployment.
* See http://javascript.crockford.com/jsmin.html
*/
var JSON; if (!JSON) { JSON = {} } (function () { 'use strict'; function f(n) { return n < 10 ? '0' + n : n } if (typeof Date.prototype.toJSON !== 'function') { Date.prototype.toJSON = function (key) { return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z' : null }; String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function (key) { return this.valueOf() } } var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"': '\\"', '\\': '\\\\' }, rep; function quote(string) { escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4) }) + '"' : '"' + string + '"' } function str(key, holder) { var i, k, v, length, mind = gap, partial, value = holder[key]; if (value && typeof value === 'object' && typeof value.toJSON === 'function') { value = value.toJSON(key) } if (typeof rep === 'function') { value = rep.call(holder, key, value) } switch (typeof value) { case 'string': return quote(value); case 'number': return isFinite(value) ? String(value) : 'null'; case 'boolean': case 'null': return String(value); case 'object': if (!value) { return 'null' } gap += indent; partial = []; if (Object.prototype.toString.apply(value) === '[object Array]') { length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null' } v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']'; gap = mind; return v } if (rep && typeof rep === 'object') { length = rep.length; for (i = 0; i < length; i += 1) { if (typeof rep[i] === 'string') { k = rep[i]; v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v) } } } } else { for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v) } } } } v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}'; gap = mind; return v } } if (typeof JSON.stringify !== 'function') { JSON.stringify = function (value, replacer, space) { var i; gap = ''; indent = ''; if (typeof space === 'number') { for (i = 0; i < space; i += 1) { indent += ' ' } } else if (typeof space === 'string') { indent = space } rep = replacer; if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { throw new Error('JSON.stringify'); } return str('', { '': value }) } } if (typeof JSON.parse !== 'function') { JSON.parse = function (text, reviver) { var j; function walk(holder, key) { var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v } else { delete value[k] } } } } return reviver.call(holder, key, value) } text = String(text); cx.lastIndex = 0; if (cx.test(text)) { text = text.replace(cx, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4) }) } if (/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { j = eval('(' + text + ')'); return typeof reviver === 'function' ? walk({ '': j }, '') : j } throw new SyntaxError('JSON.parse'); } } } ());

/*!
* jQuery blockUI plugin
* Version 2.37 (29-JAN-2011)
* @requires jQuery v1.2.3 or later
*
* Examples at: http://malsup.com/jquery/block/
* Copyright (c) 2007-2010 M. Alsup
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Thanks to Amir-Hossein Sobhi for some excellent contributions!
*/
; (function ($) { if (/1\.(0|1|2)\.(0|1|2)/.test($.fn.jquery) || /^1.1/.test($.fn.jquery)) { alert('blockUI requires jQuery v1.2.3 or later!  You are using v' + $.fn.jquery); return } $.fn._fadeIn = $.fn.fadeIn; var noOp = function () { }; var mode = document.documentMode || 0; var setExpr = $.browser.msie && (($.browser.version < 8 && !mode) || mode < 8); var ie6 = $.browser.msie && /MSIE 6.0/.test(navigator.userAgent) && !mode; $.blockUI = function (opts) { install(window, opts) }; $.unblockUI = function (opts) { remove(window, opts) }; $.growlUI = function (title, message, timeout, onClose) { var $m = $('<div class="growlUI"></div>'); if (title) $m.append('<h1>' + title + '</h1>'); if (message) $m.append('<h2>' + message + '</h2>'); if (timeout == undefined) timeout = 3000; $.blockUI({ message: $m, fadeIn: 700, fadeOut: 1000, centerY: false, timeout: timeout, showOverlay: false, onUnblock: onClose, css: $.blockUI.defaults.growlCSS }) }; $.fn.block = function (opts) { return this.unblock({ fadeOut: 0 }).each(function () { if ($.css(this, 'position') == 'static') this.style.position = 'relative'; if ($.browser.msie) this.style.zoom = 1; install(this, opts) }) }; $.fn.unblock = function (opts) { return this.each(function () { remove(this, opts) }) }; $.blockUI.version = 2.37; $.blockUI.defaults = { message: '<h1>Please wait...</h1>', title: null, draggable: true, theme: false, css: { padding: 0, margin: 0, width: '30%', top: '40%', left: '35%', textAlign: 'center', color: '#000', border: '3px solid #aaa', backgroundColor: '#fff', cursor: 'wait' }, themedCSS: { width: '30%', top: '40%', left: '35%' }, overlayCSS: { backgroundColor: '#000', opacity: 0.6, cursor: 'wait' }, growlCSS: { width: '350px', top: '10px', left: '', right: '10px', border: 'none', padding: '5px', opacity: 0.6, cursor: 'default', color: '#fff', backgroundColor: '#000', '-webkit-border-radius': '10px', '-moz-border-radius': '10px', 'border-radius': '10px' }, iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank', forceIframe: false, baseZ: 1000, centerX: true, centerY: true, allowBodyStretch: true, bindEvents: true, constrainTabKey: true, fadeIn: 200, fadeOut: 400, timeout: 0, showOverlay: true, focusInput: true, applyPlatformOpacityRules: true, onBlock: null, onUnblock: null, quirksmodeOffsetHack: 4, blockMsgClass: 'blockMsg' }; var pageBlock = null; var pageBlockEls = []; function install(el, opts) { var full = (el == window); var msg = opts && opts.message !== undefined ? opts.message : undefined; opts = $.extend({}, $.blockUI.defaults, opts || {}); opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {}); var css = $.extend({}, $.blockUI.defaults.css, opts.css || {}); var themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {}); msg = msg === undefined ? opts.message : msg; if (full && pageBlock) remove(window, { fadeOut: 0 }); if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) { var node = msg.jquery ? msg[0] : msg; var data = {}; $(el).data('blockUI.history', data); data.el = node; data.parent = node.parentNode; data.display = node.style.display; data.position = node.style.position; if (data.parent) data.parent.removeChild(node) } var z = opts.baseZ; var lyr1 = ($.browser.msie || opts.forceIframe) ? $('<iframe class="blockUI" style="z-index:' + (z++) + ';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="' + opts.iframeSrc + '"></iframe>') : $('<div class="blockUI" style="display:none"></div>'); var lyr2 = $('<div class="blockUI blockOverlay" style="z-index:' + (z++) + ';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>'); var lyr3, s; if (opts.theme && full) { s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:' + z + ';display:none;position:fixed">' + '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">' + (opts.title || '&nbsp;') + '</div>' + '<div class="ui-widget-content ui-dialog-content"></div>' + '</div>' } else if (opts.theme) { s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:' + z + ';display:none;position:absolute">' + '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">' + (opts.title || '&nbsp;') + '</div>' + '<div class="ui-widget-content ui-dialog-content"></div>' + '</div>' } else if (full) { s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage" style="z-index:' + z + ';display:none;position:fixed"></div>' } else { s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement" style="z-index:' + z + ';display:none;position:absolute"></div>' } lyr3 = $(s); if (msg) { if (opts.theme) { lyr3.css(themedCSS); lyr3.addClass('ui-widget-content') } else lyr3.css(css) } if (!opts.applyPlatformOpacityRules || !($.browser.mozilla && /Linux/.test(navigator.platform))) lyr2.css(opts.overlayCSS); lyr2.css('position', full ? 'fixed' : 'absolute'); if ($.browser.msie || opts.forceIframe) lyr1.css('opacity', 0.0); var layers = [lyr1, lyr2, lyr3], $par = full ? $('body') : $(el); $.each(layers, function () { this.appendTo($par) }); if (opts.theme && opts.draggable && $.fn.draggable) { lyr3.draggable({ handle: '.ui-dialog-titlebar', cancel: 'li' }) } var expr = setExpr && (!$.boxModel || $('object,embed', full ? null : el).length > 0); if (ie6 || expr) { if (full && opts.allowBodyStretch && $.boxModel) $('html,body').css('height', '100%'); if ((ie6 || !$.boxModel) && !full) { var t = sz(el, 'borderTopWidth'), l = sz(el, 'borderLeftWidth'); var fixT = t ? '(0 - ' + t + ')' : 0; var fixL = l ? '(0 - ' + l + ')' : 0 } $.each([lyr1, lyr2, lyr3], function (i, o) { var s = o[0].style; s.position = 'absolute'; if (i < 2) { full ? s.setExpression('height', 'Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.boxModel?0:' + opts.quirksmodeOffsetHack + ') + "px"') : s.setExpression('height', 'this.parentNode.offsetHeight + "px"'); full ? s.setExpression('width', 'jQuery.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"') : s.setExpression('width', 'this.parentNode.offsetWidth + "px"'); if (fixL) s.setExpression('left', fixL); if (fixT) s.setExpression('top', fixT) } else if (opts.centerY) { if (full) s.setExpression('top', '(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'); s.marginTop = 0 } else if (!opts.centerY && full) { var top = (opts.css && opts.css.top) ? parseInt(opts.css.top) : 0; var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + ' + top + ') + "px"'; s.setExpression('top', expression) } }) } if (msg) { if (opts.theme) lyr3.find('.ui-widget-content').append(msg); else lyr3.append(msg); if (msg.jquery || msg.nodeType) $(msg).show() } if (($.browser.msie || opts.forceIframe) && opts.showOverlay) lyr1.show(); if (opts.fadeIn) { var cb = opts.onBlock ? opts.onBlock : noOp; var cb1 = (opts.showOverlay && !msg) ? cb : noOp; var cb2 = msg ? cb : noOp; if (opts.showOverlay) lyr2._fadeIn(opts.fadeIn, cb1); if (msg) lyr3._fadeIn(opts.fadeIn, cb2) } else { if (opts.showOverlay) lyr2.show(); if (msg) lyr3.show(); if (opts.onBlock) opts.onBlock() } bind(1, el, opts); if (full) { pageBlock = lyr3[0]; pageBlockEls = $(':input:enabled:visible', pageBlock); if (opts.focusInput) setTimeout(focus, 20) } else center(lyr3[0], opts.centerX, opts.centerY); if (opts.timeout) { var to = setTimeout(function () { full ? $.unblockUI(opts) : $(el).unblock(opts) }, opts.timeout); $(el).data('blockUI.timeout', to) } }; function remove(el, opts) { var full = (el == window); var $el = $(el); var data = $el.data('blockUI.history'); var to = $el.data('blockUI.timeout'); if (to) { clearTimeout(to); $el.removeData('blockUI.timeout') } opts = $.extend({}, $.blockUI.defaults, opts || {}); bind(0, el, opts); var els; if (full) els = $('body').children().filter('.blockUI').add('body > .blockUI'); else els = $('.blockUI', el); if (full) pageBlock = pageBlockEls = null; if (opts.fadeOut) { els.fadeOut(opts.fadeOut); setTimeout(function () { reset(els, data, opts, el) }, opts.fadeOut) } else reset(els, data, opts, el) }; function reset(els, data, opts, el) { els.each(function (i, o) { if (this.parentNode) this.parentNode.removeChild(this) }); if (data && data.el) { data.el.style.display = data.display; data.el.style.position = data.position; if (data.parent) data.parent.appendChild(data.el); $(el).removeData('blockUI.history') } if (typeof opts.onUnblock == 'function') opts.onUnblock(el, opts) }; function bind(b, el, opts) { var full = el == window, $el = $(el); if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked'))) return; if (!full) $el.data('blockUI.isBlocked', b); if (!opts.bindEvents || (b && !opts.showOverlay)) return; var events = 'mousedown mouseup keydown keypress'; b ? $(document).bind(events, opts, handler) : $(document).unbind(events, handler) }; function handler(e) { if (e.keyCode && e.keyCode == 9) { if (pageBlock && e.data.constrainTabKey) { var els = pageBlockEls; var fwd = !e.shiftKey && e.target === els[els.length - 1]; var back = e.shiftKey && e.target === els[0]; if (fwd || back) { setTimeout(function () { focus(back) }, 10); return false } } } var opts = e.data; if ($(e.target).parents('div.' + opts.blockMsgClass).length > 0) return true; return $(e.target).parents().children().filter('div.blockUI').length == 0 }; function focus(back) { if (!pageBlockEls) return; var e = pageBlockEls[back === true ? pageBlockEls.length - 1 : 0]; if (e) e.focus() }; function center(el, x, y) { var p = el.parentNode, s = el.style; var l = ((p.offsetWidth - el.offsetWidth) / 2) - sz(p, 'borderLeftWidth'); var t = ((p.offsetHeight - el.offsetHeight) / 2) - sz(p, 'borderTopWidth'); if (x) s.left = l > 0 ? (l + 'px') : '0'; if (y) s.top = t > 0 ? (t + 'px') : '0' }; function sz(el, p) { return parseInt($.css(el, p)) || 0 } })(jQuery);


/**
* hoverIntent r6 // 2011.02.26 // jQuery 1.5.1+
* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
* 
* @param  f  onMouseOver function || An object with configuration options
* @param  g  onMouseOut function  || Nothing (use configuration options object)
* @author    Brian Cherne brian(at)cherne(dot)net
*/
(function ($) { $.fn.hoverIntent = function (f, g) { var cfg = { sensitivity: 7, interval: 100, timeout: 0 }; cfg = $.extend(cfg, g ? { over: f, out: g} : f); var cX, cY, pX, pY; var track = function (ev) { cX = ev.pageX; cY = ev.pageY }; var compare = function (ev, ob) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); if ((Math.abs(pX - cX) + Math.abs(pY - cY)) < cfg.sensitivity) { $(ob).unbind("mousemove", track); ob.hoverIntent_s = 1; return cfg.over.apply(ob, [ev]) } else { pX = cX; pY = cY; ob.hoverIntent_t = setTimeout(function () { compare(ev, ob) }, cfg.interval) } }; var delay = function (ev, ob) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); ob.hoverIntent_s = 0; return cfg.out.apply(ob, [ev]) }; var handleHover = function (e) { var ev = jQuery.extend({}, e); var ob = this; if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t) } if (e.type == "mouseenter") { pX = ev.pageX; pY = ev.pageY; $(ob).bind("mousemove", track); if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout(function () { compare(ev, ob) }, cfg.interval) } } else { $(ob).unbind("mousemove", track); if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout(function () { delay(ev, ob) }, cfg.timeout) } } }; return this.bind('mouseenter', handleHover).bind('mouseleave', handleHover) } })(jQuery);


/*
* jQuery store - Plugin for persistent data storage using localStorage, userData (and window.name)
* 
* Authors: Rodney Rehm
* Web: http://medialize.github.com/jQuery-store/
* 
* Licensed under the MIT License:
*   http://www.opensource.org/licenses/mit-license.php
*
*/

/**********************************************************************************
* INITIALIZE EXAMPLES:
**********************************************************************************
* 	// automatically detect best suited storage driver and use default serializers
*	$.storage = new $.store();
*	// optionally initialize with specific driver and or serializers
*	$.storage = new $.store( [driver] [, serializers] );
*		driver		can be the key (e.g. "windowName") or the driver-object itself
*		serializers	can be a list of named serializers like $.store.serializers
**********************************************************************************
* USAGE EXAMPLES:
**********************************************************************************
*	$.storage.get( key );			// retrieves a value
*	$.storage.set( key, value );	// saves a value
*	$.storage.del( key );			// deletes a value
*	$.storage.flush();				// deletes aall values
**********************************************************************************
*/
(function ($, undefined) { $.store = function (driver, serializers) { var that = this; if (typeof driver == 'string') { if ($.store.drivers[driver]) this.driver = $.store.drivers[driver]; else throw new Error('Unknown driver ' + driver); } else if (typeof driver == 'object') { var invalidAPI = !$.isFunction(driver.init) || !$.isFunction(driver.get) || !$.isFunction(driver.set) || !$.isFunction(driver.del) || !$.isFunction(driver.flush); if (invalidAPI) throw new Error('The specified driver does not fulfill the API requirements'); this.driver = driver } else { $.each($.store.drivers, function () { if (!$.isFunction(this.available) || !this.available()) return true; that.driver = this; if (that.driver.init() === false) { that.driver = null; return true } return false }) } if (!serializers) serializers = $.store.serializers; this.serializers = {}; $.each(serializers, function (key, serializer) { if (!$.isFunction(this.init)) return true; that.serializers[key] = this; that.serializers[key].init(that.encoders, that.decoders) }) }; $.extend($.store.prototype, { get: function (key) { var value = this.driver.get(key); return this.driver.encodes ? value : this.unserialize(value) }, set: function (key, value) { this.driver.set(key, this.driver.encodes ? value : this.serialize(value)) }, del: function (key) { this.driver.del(key) }, flush: function () { this.driver.flush() }, driver: undefined, encoders: [], decoders: [], serialize: function (value) { var that = this; $.each(this.encoders, function () { var serializer = that.serializers[this + ""]; if (!serializer || !serializer.encode) return true; try { value = serializer.encode(value) } catch (e) { } }); return value }, unserialize: function (value) { var that = this; if (!value) return value; $.each(this.decoders, function () { var serializer = that.serializers[this + ""]; if (!serializer || !serializer.decode) return true; value = serializer.decode(value) }); return value } }); $.store.drivers = { 'localStorage': { ident: "$.store.drivers.localStorage", scope: 'browser', available: function () { try { return !!window.localStorage } catch (e) { return false } }, init: $.noop, get: function (key) { return window.localStorage.getItem(key) }, set: function (key, value) { window.localStorage.setItem(key, value) }, del: function (key) { window.localStorage.removeItem(key) }, flush: function () { window.localStorage.clear() } }, 'userData': { ident: "$.store.drivers.userData", element: null, nodeName: 'userdatadriver', scope: 'browser', initialized: false, available: function () { try { return !!(document.documentElement && document.documentElement.addBehavior) } catch (e) { return false } }, init: function () { if (this.initialized) return; try { this.element = document.createElement(this.nodeName); document.documentElement.insertBefore(this.element, document.getElementsByTagName('title')[0]); this.element.addBehavior("#default#userData"); this.initialized = true } catch (e) { return false } }, get: function (key) { this.element.load(this.nodeName); return this.element.getAttribute(key) }, set: function (key, value) { this.element.setAttribute(key, value); this.element.save(this.nodeName) }, del: function (key) { this.element.removeAttribute(key); this.element.save(this.nodeName) }, flush: function () { this.element.expires = (new Date).toUTCString(); this.element.save(this.nodeName) } }, 'windowName': { ident: "$.store.drivers.windowName", scope: 'window', cache: {}, encodes: true, available: function () { return true }, init: function () { this.load() }, save: function () { window.name = $.store.serializers.json.encode(this.cache) }, load: function () { try { this.cache = $.store.serializers.json.decode(window.name + ""); if (typeof this.cache != "object") this.cache = {} } catch (e) { this.cache = {}; window.name = "{}" } }, get: function (key) { return this.cache[key] }, set: function (key, value) { this.cache[key] = value; this.save() }, del: function (key) { try { delete this.cache[key] } catch (e) { this.cache[key] = undefined } this.save() }, flush: function () { window.name = "{}" } } }; $.store.serializers = { 'json': { ident: "$.store.serializers.json", init: function (encoders, decoders) { encoders.push("json"); decoders.push("json") }, encode: JSON.stringify, decode: JSON.parse }, 'xml': { ident: "$.store.serializers.xml", init: function (encoders, decoders) { encoders.unshift("xml"); decoders.push("xml") }, isXML: function (value) { var documentElement = (value ? value.ownerDocument || value : 0).documentElement; return documentElement ? documentElement.nodeName.toLowerCase() !== "html" : false }, encode: function (value) { if (!value || value._serialized || !this.isXML(value)) return value; var _value = { _serialized: this.ident, value: value }; try { _value.value = new XMLSerializer().serializeToString(value); return _value } catch (E1) { try { _value.value = value.xml; return _value } catch (E2) { } } return value }, decode: function (value) { if (!value || !value._serialized || value._serialized != this.ident) return value; var dom_parser = ("DOMParser" in window && (new DOMParser()).parseFromString); if (!dom_parser && window.ActiveXObject) { dom_parser = function (_xmlString) { var xml_doc = new ActiveXObject('Microsoft.XMLDOM'); xml_doc.async = 'false'; xml_doc.loadXML(_xmlString); return xml_doc } } if (!dom_parser) { return undefined } value.value = dom_parser.call("DOMParser" in window && (new DOMParser()) || window, value.value, 'text/xml'); return this.isXML(value.value) ? value.value : undefined } }} })(jQuery);


/*
 * FancyBox - jQuery Plugin
 * Simple and fancy lightbox alternative
 *
 * Examples and documentation at: http://fancybox.net
 *
 * Copyright (c) 2008 - 2010 Janis Skarnelis
 * That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated.
 *
 * Version: 1.3.4 (11/11/2010)
 * Requires: jQuery v1.3+
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 */

; (function ($) { var tmp, loading, overlay, wrap, outer, content, close, title, nav_left, nav_right, selectedIndex = 0, selectedOpts = {}, selectedArray = [], currentIndex = 0, currentOpts = {}, currentArray = [], ajaxLoader = null, imgPreloader = new Image(), imgRegExp = /\.(jpg|gif|png|bmp|jpeg)(.*)?$/i, swfRegExp = /[^\.]\.(swf)\s*$/i, loadingTimer, loadingFrame = 1, titleHeight = 0, titleStr = '', start_pos, final_pos, busy = false, fx = $.extend($('<div/>')[0], { prop: 0 }), isIE6 = $.browser.msie && $.browser.version < 7 && !window.XMLHttpRequest, _abort = function () { loading.hide(); imgPreloader.onerror = imgPreloader.onload = null; if (ajaxLoader) { ajaxLoader.abort() } tmp.empty() }, _error = function () { if (false === selectedOpts.onError(selectedArray, selectedIndex, selectedOpts)) { loading.hide(); busy = false; return } selectedOpts.titleShow = false; selectedOpts.width = 'auto'; selectedOpts.height = 'auto'; tmp.html('<p id="fancybox-error">The requested content cannot be loaded.<br />Please try again later.</p>'); _process_inline() }, _start = function () { var obj = selectedArray[selectedIndex], href, type, title, str, emb, ret; _abort(); selectedOpts = $.extend({}, $.fn.fancybox.defaults, (typeof $(obj).data('fancybox') == 'undefined' ? selectedOpts : $(obj).data('fancybox'))); ret = selectedOpts.onStart(selectedArray, selectedIndex, selectedOpts); if (ret === false) { busy = false; return } else if (typeof ret == 'object') { selectedOpts = $.extend(selectedOpts, ret) } title = selectedOpts.title || (obj.nodeName ? $(obj).attr('title') : obj.title) || ''; if (obj.nodeName && !selectedOpts.orig) { selectedOpts.orig = $(obj).children("img:first").length ? $(obj).children("img:first") : $(obj) } if (title === '' && selectedOpts.orig && selectedOpts.titleFromAlt) { title = selectedOpts.orig.attr('alt') } href = selectedOpts.href || (obj.nodeName ? $(obj).attr('href') : obj.href) || null; if ((/^(?:javascript)/i).test(href) || href == '#') { href = null } if (selectedOpts.type) { type = selectedOpts.type; if (!href) { href = selectedOpts.content } } else if (selectedOpts.content) { type = 'html' } else if (href) { if (href.match(imgRegExp)) { type = 'image' } else if (href.match(swfRegExp)) { type = 'swf' } else if ($(obj).hasClass("iframe")) { type = 'iframe' } else if (href.indexOf("#") === 0) { type = 'inline' } else { type = 'ajax' } } if (!type) { _error(); return } if (type == 'inline') { obj = href.substr(href.indexOf("#")); type = $(obj).length > 0 ? 'inline' : 'ajax' } selectedOpts.type = type; selectedOpts.href = href; selectedOpts.title = title; if (selectedOpts.autoDimensions) { if (selectedOpts.type == 'html' || selectedOpts.type == 'inline' || selectedOpts.type == 'ajax') { selectedOpts.width = 'auto'; selectedOpts.height = 'auto' } else { selectedOpts.autoDimensions = false } } if (selectedOpts.modal) { selectedOpts.overlayShow = true; selectedOpts.hideOnOverlayClick = false; selectedOpts.hideOnContentClick = false; selectedOpts.enableEscapeButton = false; selectedOpts.showCloseButton = false } selectedOpts.padding = parseInt(selectedOpts.padding, 10); selectedOpts.margin = parseInt(selectedOpts.margin, 10); tmp.css('padding', (selectedOpts.padding + selectedOpts.margin)); $('.fancybox-inline-tmp').unbind('fancybox-cancel').bind('fancybox-change', function () { $(this).replaceWith(content.children()) }); switch (type) { case 'html': tmp.html(selectedOpts.content); _process_inline(); break; case 'inline': if ($(obj).parent().is('#fancybox-content') === true) { busy = false; return } $('<div class="fancybox-inline-tmp" />').hide().insertBefore($(obj)).bind('fancybox-cleanup', function () { $(this).replaceWith(content.children()) }).bind('fancybox-cancel', function () { $(this).replaceWith(tmp.children()) }); $(obj).appendTo(tmp); _process_inline(); break; case 'image': busy = false; $.fancybox.showActivity(); imgPreloader = new Image(); imgPreloader.onerror = function () { _error() }; imgPreloader.onload = function () { busy = true; imgPreloader.onerror = imgPreloader.onload = null; _process_image() }; imgPreloader.src = href; break; case 'swf': selectedOpts.scrolling = 'no'; str = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="' + selectedOpts.width + '" height="' + selectedOpts.height + '"><param name="movie" value="' + href + '"></param>'; emb = ''; $.each(selectedOpts.swf, function (name, val) { str += '<param name="' + name + '" value="' + val + '"></param>'; emb += ' ' + name + '="' + val + '"' }); str += '<embed src="' + href + '" type="application/x-shockwave-flash" width="' + selectedOpts.width + '" height="' + selectedOpts.height + '"' + emb + '></embed></object>'; tmp.html(str); _process_inline(); break; case 'ajax': busy = false; $.fancybox.showActivity(); selectedOpts.ajax.win = selectedOpts.ajax.success; ajaxLoader = $.ajax($.extend({}, selectedOpts.ajax, { url: href, data: selectedOpts.ajax.data || {}, error: function (XMLHttpRequest, textStatus, errorThrown) { if (XMLHttpRequest.status > 0) { _error() } }, success: function (data, textStatus, XMLHttpRequest) { var o = typeof XMLHttpRequest == 'object' ? XMLHttpRequest : ajaxLoader; if (o.status == 200) { if (typeof selectedOpts.ajax.win == 'function') { ret = selectedOpts.ajax.win(href, data, textStatus, XMLHttpRequest); if (ret === false) { loading.hide(); return } else if (typeof ret == 'string' || typeof ret == 'object') { data = ret } } tmp.html(data); _process_inline() } } })); break; case 'iframe': _show(); break } }, _process_inline = function () { var w = selectedOpts.width, h = selectedOpts.height; if (w.toString().indexOf('%') > -1) { w = parseInt(($(window).width() - (selectedOpts.margin * 2)) * parseFloat(w) / 100, 10) + 'px' } else { w = w == 'auto' ? 'auto' : w + 'px' } if (h.toString().indexOf('%') > -1) { h = parseInt(($(window).height() - (selectedOpts.margin * 2)) * parseFloat(h) / 100, 10) + 'px' } else { h = h == 'auto' ? 'auto' : h + 'px' } tmp.wrapInner('<div style="width:' + w + ';height:' + h + ';overflow: ' + (selectedOpts.scrolling == 'auto' ? 'auto' : (selectedOpts.scrolling == 'yes' ? 'scroll' : 'hidden')) + ';position:relative;"></div>'); selectedOpts.width = tmp.width(); selectedOpts.height = tmp.height(); _show() }, _process_image = function () { selectedOpts.width = imgPreloader.width; selectedOpts.height = imgPreloader.height; $("<img />").attr({ 'id': 'fancybox-img', 'src': imgPreloader.src, 'alt': selectedOpts.title }).appendTo(tmp); _show() }, _show = function () { var pos, equal; loading.hide(); if (wrap.is(":visible") && false === currentOpts.onCleanup(currentArray, currentIndex, currentOpts)) { $.event.trigger('fancybox-cancel'); busy = false; return } busy = true; $(content.add(overlay)).unbind(); $(window).unbind("resize.fb scroll.fb"); $(document).unbind('keydown.fb'); if (wrap.is(":visible") && currentOpts.titlePosition !== 'outside') { wrap.css('height', wrap.height()) } currentArray = selectedArray; currentIndex = selectedIndex; currentOpts = selectedOpts; if (currentOpts.overlayShow) { overlay.css({ 'background-color': currentOpts.overlayColor, 'opacity': currentOpts.overlayOpacity, 'cursor': currentOpts.hideOnOverlayClick ? 'pointer' : 'auto', 'height': $(document).height() }); if (!overlay.is(':visible')) { if (isIE6) { $('select:not(#fancybox-tmp select)').filter(function () { return this.style.visibility !== 'hidden' }).css({ 'visibility': 'hidden' }).one('fancybox-cleanup', function () { this.style.visibility = 'inherit' }) } overlay.show() } } else { overlay.hide() } final_pos = _get_zoom_to(); _process_title(); if (wrap.is(":visible")) { $(close.add(nav_left).add(nav_right)).hide(); pos = wrap.position(), start_pos = { top: pos.top, left: pos.left, width: wrap.width(), height: wrap.height() }; equal = (start_pos.width == final_pos.width && start_pos.height == final_pos.height); content.fadeTo(currentOpts.changeFade, 0.3, function () { var finish_resizing = function () { content.html(tmp.contents()).fadeTo(currentOpts.changeFade, 1, _finish) }; $.event.trigger('fancybox-change'); content.empty().removeAttr('filter').css({ 'border-width': currentOpts.padding, 'width': final_pos.width - currentOpts.padding * 2, 'height': selectedOpts.autoDimensions ? 'auto' : final_pos.height - titleHeight - currentOpts.padding * 2 }); if (equal) { finish_resizing() } else { fx.prop = 0; $(fx).animate({ prop: 1 }, { duration: currentOpts.changeSpeed, easing: currentOpts.easingChange, step: _draw, complete: finish_resizing }) } }); return } wrap.removeAttr("style"); content.css('border-width', currentOpts.padding); if (currentOpts.transitionIn == 'elastic') { start_pos = _get_zoom_from(); content.html(tmp.contents()); wrap.show(); if (currentOpts.opacity) { final_pos.opacity = 0 } fx.prop = 0; $(fx).animate({ prop: 1 }, { duration: currentOpts.speedIn, easing: currentOpts.easingIn, step: _draw, complete: _finish }); return } if (currentOpts.titlePosition == 'inside' && titleHeight > 0) { title.show() } content.css({ 'width': final_pos.width - currentOpts.padding * 2, 'height': selectedOpts.autoDimensions ? 'auto' : final_pos.height - titleHeight - currentOpts.padding * 2 }).html(tmp.contents()); wrap.css(final_pos).fadeIn(currentOpts.transitionIn == 'none' ? 0 : currentOpts.speedIn, _finish) }, _format_title = function (title) { if (title && title.length) { if (currentOpts.titlePosition == 'float') { return '<table id="fancybox-title-float-wrap" cellpadding="0" cellspacing="0"><tr><td id="fancybox-title-float-left"></td><td id="fancybox-title-float-main">' + title + '</td><td id="fancybox-title-float-right"></td></tr></table>' } return '<div id="fancybox-title-' + currentOpts.titlePosition + '">' + title + '</div>' } return false }, _process_title = function () { titleStr = currentOpts.title || ''; titleHeight = 0; title.empty().removeAttr('style').removeClass(); if (currentOpts.titleShow === false) { title.hide(); return } titleStr = $.isFunction(currentOpts.titleFormat) ? currentOpts.titleFormat(titleStr, currentArray, currentIndex, currentOpts) : _format_title(titleStr); if (!titleStr || titleStr === '') { title.hide(); return } title.addClass('fancybox-title-' + currentOpts.titlePosition).html(titleStr).appendTo('body').show(); switch (currentOpts.titlePosition) { case 'inside': title.css({ 'width': final_pos.width - (currentOpts.padding * 2), 'marginLeft': currentOpts.padding, 'marginRight': currentOpts.padding }); titleHeight = title.outerHeight(true); title.appendTo(outer); final_pos.height += titleHeight; break; case 'over': title.css({ 'marginLeft': currentOpts.padding, 'width': final_pos.width - (currentOpts.padding * 2), 'bottom': currentOpts.padding }).appendTo(outer); break; case 'float': title.css('left', parseInt((title.width() - final_pos.width - 40) / 2, 10) * -1).appendTo(wrap); break; default: title.css({ 'width': final_pos.width - (currentOpts.padding * 2), 'paddingLeft': currentOpts.padding, 'paddingRight': currentOpts.padding }).appendTo(wrap); break } title.hide() }, _set_navigation = function () { if (currentOpts.enableEscapeButton || currentOpts.enableKeyboardNav) { $(document).bind('keydown.fb', function (e) { if (e.keyCode == 27 && currentOpts.enableEscapeButton) { e.preventDefault(); $.fancybox.close() } else if ((e.keyCode == 37 || e.keyCode == 39) && currentOpts.enableKeyboardNav && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA' && e.target.tagName !== 'SELECT') { e.preventDefault(); $.fancybox[e.keyCode == 37 ? 'prev' : 'next']() } }) } if (!currentOpts.showNavArrows) { nav_left.hide(); nav_right.hide(); return } if ((currentOpts.cyclic && currentArray.length > 1) || currentIndex !== 0) { nav_left.show() } if ((currentOpts.cyclic && currentArray.length > 1) || currentIndex != (currentArray.length - 1)) { nav_right.show() } }, _finish = function () { if (!$.support.opacity) { content.get(0).style.removeAttribute('filter'); wrap.get(0).style.removeAttribute('filter') } if (selectedOpts.autoDimensions) { content.css('height', 'auto') } wrap.css('height', 'auto'); if (titleStr && titleStr.length) { title.show() } if (currentOpts.showCloseButton) { close.show() } _set_navigation(); if (currentOpts.hideOnContentClick) { content.bind('click', $.fancybox.close) } if (currentOpts.hideOnOverlayClick) { overlay.bind('click', $.fancybox.close) } $(window).bind("resize.fb", $.fancybox.resize); if (currentOpts.centerOnScroll) { $(window).bind("scroll.fb", $.fancybox.center) } if (currentOpts.type == 'iframe') { $('<iframe id="fancybox-frame" name="fancybox-frame' + new Date().getTime() + '" frameborder="0" hspace="0" ' + ($.browser.msie ? 'allowtransparency="true""' : '') + ' scrolling="' + selectedOpts.scrolling + '" src="' + currentOpts.href + '"></iframe>').appendTo(content) } wrap.show(); busy = false; $.fancybox.center(); currentOpts.onComplete(currentArray, currentIndex, currentOpts); _preload_images() }, _preload_images = function () { var href, objNext; if ((currentArray.length - 1) > currentIndex) { href = currentArray[currentIndex + 1].href; if (typeof href !== 'undefined' && href.match(imgRegExp)) { objNext = new Image(); objNext.src = href } } if (currentIndex > 0) { href = currentArray[currentIndex - 1].href; if (typeof href !== 'undefined' && href.match(imgRegExp)) { objNext = new Image(); objNext.src = href } } }, _draw = function (pos) { var dim = { width: parseInt(start_pos.width + (final_pos.width - start_pos.width) * pos, 10), height: parseInt(start_pos.height + (final_pos.height - start_pos.height) * pos, 10), top: parseInt(start_pos.top + (final_pos.top - start_pos.top) * pos, 10), left: parseInt(start_pos.left + (final_pos.left - start_pos.left) * pos, 10) }; if (typeof final_pos.opacity !== 'undefined') { dim.opacity = pos < 0.5 ? 0.5 : pos } wrap.css(dim); content.css({ 'width': dim.width - currentOpts.padding * 2, 'height': dim.height - (titleHeight * pos) - currentOpts.padding * 2 }) }, _get_viewport = function () { return [$(window).width() - (currentOpts.margin * 2), $(window).height() - (currentOpts.margin * 2), $(document).scrollLeft() + currentOpts.margin, $(document).scrollTop() + currentOpts.margin] }, _get_zoom_to = function () { var view = _get_viewport(), to = {}, resize = currentOpts.autoScale, double_padding = currentOpts.padding * 2, ratio; if (currentOpts.width.toString().indexOf('%') > -1) { to.width = parseInt((view[0] * parseFloat(currentOpts.width)) / 100, 10) } else { to.width = currentOpts.width + double_padding } if (currentOpts.height.toString().indexOf('%') > -1) { to.height = parseInt((view[1] * parseFloat(currentOpts.height)) / 100, 10) } else { to.height = currentOpts.height + double_padding } if (resize && (to.width > view[0] || to.height > view[1])) { if (selectedOpts.type == 'image' || selectedOpts.type == 'swf') { ratio = (currentOpts.width) / (currentOpts.height); if ((to.width) > view[0]) { to.width = view[0]; to.height = parseInt(((to.width - double_padding) / ratio) + double_padding, 10) } if ((to.height) > view[1]) { to.height = view[1]; to.width = parseInt(((to.height - double_padding) * ratio) + double_padding, 10) } } else { to.width = Math.min(to.width, view[0]); to.height = Math.min(to.height, view[1]) } } to.top = parseInt(Math.max(view[3] - 20, view[3] + ((view[1] - to.height - 40) * 0.5)), 10); to.left = parseInt(Math.max(view[2] - 20, view[2] + ((view[0] - to.width - 40) * 0.5)), 10); return to }, _get_obj_pos = function (obj) { var pos = obj.offset(); pos.top += parseInt(obj.css('paddingTop'), 10) || 0; pos.left += parseInt(obj.css('paddingLeft'), 10) || 0; pos.top += parseInt(obj.css('border-top-width'), 10) || 0; pos.left += parseInt(obj.css('border-left-width'), 10) || 0; pos.width = obj.width(); pos.height = obj.height(); return pos }, _get_zoom_from = function () { var orig = selectedOpts.orig ? $(selectedOpts.orig) : false, from = {}, pos, view; if (orig && orig.length) { pos = _get_obj_pos(orig); from = { width: pos.width + (currentOpts.padding * 2), height: pos.height + (currentOpts.padding * 2), top: pos.top - currentOpts.padding - 20, left: pos.left - currentOpts.padding - 20} } else { view = _get_viewport(); from = { width: currentOpts.padding * 2, height: currentOpts.padding * 2, top: parseInt(view[3] + view[1] * 0.5, 10), left: parseInt(view[2] + view[0] * 0.5, 10)} } return from }, _animate_loading = function () { if (!loading.is(':visible')) { clearInterval(loadingTimer); return } $('div', loading).css('top', (loadingFrame * -40) + 'px'); loadingFrame = (loadingFrame + 1) % 12 }; $.fn.fancybox = function (options) { if (!$(this).length) { return this } $(this).data('fancybox', $.extend({}, options, ($.metadata ? $(this).metadata() : {}))).unbind('click.fb').bind('click.fb', function (e) { e.preventDefault(); if (busy) { return } busy = true; $(this).blur(); selectedArray = []; selectedIndex = 0; var rel = $(this).attr('rel') || ''; if (!rel || rel == '' || rel === 'nofollow') { selectedArray.push(this) } else { selectedArray = $("a[rel=" + rel + "], area[rel=" + rel + "]"); selectedIndex = selectedArray.index(this) } _start(); return }); return this }; $.fancybox = function (obj) { var opts; if (busy) { return } busy = true; opts = typeof arguments[1] !== 'undefined' ? arguments[1] : {}; selectedArray = []; selectedIndex = parseInt(opts.index, 10) || 0; if ($.isArray(obj)) { for (var i = 0, j = obj.length; i < j; i++) { if (typeof obj[i] == 'object') { $(obj[i]).data('fancybox', $.extend({}, opts, obj[i])) } else { obj[i] = $({}).data('fancybox', $.extend({ content: obj[i] }, opts)) } } selectedArray = jQuery.merge(selectedArray, obj) } else { if (typeof obj == 'object') { $(obj).data('fancybox', $.extend({}, opts, obj)) } else { obj = $({}).data('fancybox', $.extend({ content: obj }, opts)) } selectedArray.push(obj) } if (selectedIndex > selectedArray.length || selectedIndex < 0) { selectedIndex = 0 } _start() }; $.fancybox.showActivity = function () { clearInterval(loadingTimer); loading.show(); loadingTimer = setInterval(_animate_loading, 66) }; $.fancybox.hideActivity = function () { loading.hide() }; $.fancybox.next = function () { return $.fancybox.pos(currentIndex + 1) }; $.fancybox.prev = function () { return $.fancybox.pos(currentIndex - 1) }; $.fancybox.pos = function (pos) { if (busy) { return } pos = parseInt(pos); selectedArray = currentArray; if (pos > -1 && pos < currentArray.length) { selectedIndex = pos; _start() } else if (currentOpts.cyclic && currentArray.length > 1) { selectedIndex = pos >= currentArray.length ? 0 : currentArray.length - 1; _start() } return }; $.fancybox.cancel = function () { if (busy) { return } busy = true; $.event.trigger('fancybox-cancel'); _abort(); selectedOpts.onCancel(selectedArray, selectedIndex, selectedOpts); busy = false }; $.fancybox.close = function () { if (busy || wrap.is(':hidden')) { return } busy = true; if (currentOpts && false === currentOpts.onCleanup(currentArray, currentIndex, currentOpts)) { busy = false; return } _abort(); $(close.add(nav_left).add(nav_right)).hide(); $(content.add(overlay)).unbind(); $(window).unbind("resize.fb scroll.fb"); $(document).unbind('keydown.fb'); content.find('iframe').attr('src', isIE6 && /^https/i.test(window.location.href || '') ? 'javascript:void(false)' : 'about:blank'); if (currentOpts.titlePosition !== 'inside') { title.empty() } wrap.stop(); function _cleanup() { overlay.fadeOut('fast'); title.empty().hide(); wrap.hide(); $.event.trigger('fancybox-cleanup'); content.empty(); currentOpts.onClosed(currentArray, currentIndex, currentOpts); currentArray = selectedOpts = []; currentIndex = selectedIndex = 0; currentOpts = selectedOpts = {}; busy = false } if (currentOpts.transitionOut == 'elastic') { start_pos = _get_zoom_from(); var pos = wrap.position(); final_pos = { top: pos.top, left: pos.left, width: wrap.width(), height: wrap.height() }; if (currentOpts.opacity) { final_pos.opacity = 1 } title.empty().hide(); fx.prop = 1; $(fx).animate({ prop: 0 }, { duration: currentOpts.speedOut, easing: currentOpts.easingOut, step: _draw, complete: _cleanup }) } else { wrap.fadeOut(currentOpts.transitionOut == 'none' ? 0 : currentOpts.speedOut, _cleanup) } }; $.fancybox.resize = function () { if (overlay.is(':visible')) { overlay.css('height', $(document).height()) } $.fancybox.center(true) }; $.fancybox.center = function () { var view, align; if (busy) { return } align = arguments[0] === true ? 1 : 0; view = _get_viewport(); if (!align && (wrap.width() > view[0] || wrap.height() > view[1])) { return } wrap.stop().animate({ 'top': parseInt(Math.max(view[3] - 20, view[3] + ((view[1] - content.height() - 40) * 0.5) - currentOpts.padding)), 'left': parseInt(Math.max(view[2] - 20, view[2] + ((view[0] - content.width() - 40) * 0.5) - currentOpts.padding)) }, typeof arguments[0] == 'number' ? arguments[0] : 200) }; $.fancybox.init = function () { if ($("#fancybox-wrap").length) { return } $('body').append(tmp = $('<div id="fancybox-tmp"></div>'), loading = $('<div id="fancybox-loading"><div></div></div>'), overlay = $('<div id="fancybox-overlay"></div>'), wrap = $('<div id="fancybox-wrap"></div>')); outer = $('<div id="fancybox-outer"></div>').append('<div class="fancybox-bg" id="fancybox-bg-n"></div><div class="fancybox-bg" id="fancybox-bg-ne"></div><div class="fancybox-bg" id="fancybox-bg-e"></div><div class="fancybox-bg" id="fancybox-bg-se"></div><div class="fancybox-bg" id="fancybox-bg-s"></div><div class="fancybox-bg" id="fancybox-bg-sw"></div><div class="fancybox-bg" id="fancybox-bg-w"></div><div class="fancybox-bg" id="fancybox-bg-nw"></div>').appendTo(wrap); outer.append(content = $('<div id="fancybox-content"></div>'), close = $('<a id="fancybox-close"></a>'), title = $('<div id="fancybox-title"></div>'), nav_left = $('<a href="javascript:;" id="fancybox-left"><span class="fancy-ico" id="fancybox-left-ico"></span></a>'), nav_right = $('<a href="javascript:;" id="fancybox-right"><span class="fancy-ico" id="fancybox-right-ico"></span></a>')); close.click($.fancybox.close); loading.click($.fancybox.cancel); nav_left.click(function (e) { e.preventDefault(); $.fancybox.prev() }); nav_right.click(function (e) { e.preventDefault(); $.fancybox.next() }); if ($.fn.mousewheel) { wrap.bind('mousewheel.fb', function (e, delta) { if (busy) { e.preventDefault() } else if ($(e.target).get(0).clientHeight == 0 || $(e.target).get(0).scrollHeight === $(e.target).get(0).clientHeight) { e.preventDefault(); $.fancybox[delta > 0 ? 'prev' : 'next']() } }) } if (!$.support.opacity) { wrap.addClass('fancybox-ie') } if (isIE6) { loading.addClass('fancybox-ie6'); wrap.addClass('fancybox-ie6'); $('<iframe id="fancybox-hide-sel-frame" src="' + (/^https/i.test(window.location.href || '') ? 'javascript:void(false)' : 'about:blank') + '" scrolling="no" border="0" frameborder="0" tabindex="-1"></iframe>').prependTo(outer) } }; $.fn.fancybox.defaults = { padding: 10, margin: 40, opacity: false, modal: false, cyclic: false, scrolling: 'auto', width: 560, height: 340, autoScale: true, autoDimensions: true, centerOnScroll: false, ajax: {}, swf: { wmode: 'transparent' }, hideOnOverlayClick: true, hideOnContentClick: false, overlayShow: true, overlayOpacity: 0.7, overlayColor: '#777', titleShow: true, titlePosition: 'float', titleFormat: null, titleFromAlt: false, transitionIn: 'fade', transitionOut: 'fade', speedIn: 300, speedOut: 300, changeSpeed: 300, changeFade: 'fast', easingIn: 'swing', easingOut: 'swing', showCloseButton: true, showNavArrows: true, enableEscapeButton: true, enableKeyboardNav: true, onStart: function () { }, onCancel: function () { }, onComplete: function () { }, onCleanup: function () { }, onClosed: function () { }, onError: function () { } }; $(document).ready(function () { $.fancybox.init() }) })(jQuery);


/*!
* jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010
* http://benalman.com/projects/jquery-bbq-plugin/
* 
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/

// Script: jQuery BBQ: Back Button & Query Library
//
// *Version: 1.2.1, Last updated: 2/17/2010*
// 
// Project Home - http://benalman.com/projects/jquery-bbq-plugin/
// GitHub       - http://github.com/cowboy/jquery-bbq/
// Source       - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.js
// (Minified)   - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.min.js (4.0kb)
// 
// About: License
// 
// Copyright (c) 2010 "Cowboy" Ben Alman,
// Dual licensed under the MIT and GPL licenses.
// http://benalman.com/about/license/
// 
// About: Examples
// 
// These working examples, complete with fully commented code, illustrate a few
// ways in which this plugin can be used.
// 
// Basic AJAX     - http://benalman.com/code/projects/jquery-bbq/examples/fragment-basic/
// Advanced AJAX  - http://benalman.com/code/projects/jquery-bbq/examples/fragment-advanced/
// jQuery UI Tabs - http://benalman.com/code/projects/jquery-bbq/examples/fragment-jquery-ui-tabs/
// Deparam        - http://benalman.com/code/projects/jquery-bbq/examples/deparam/
// 
// About: Support and Testing
// 
// Information about what version or versions of jQuery this plugin has been
// tested with, what browsers it has been tested in, and where the unit tests
// reside (so you can test it yourself).
// 
// jQuery Versions - 1.3.2, 1.4.1, 1.4.2
// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4,
//                   Chrome 4-5, Opera 9.6-10.1.
// Unit Tests      - http://benalman.com/code/projects/jquery-bbq/unit/
// 
// About: Release History
// 
// 1.2.1 - (2/17/2010) Actually fixed the stale window.location Safari bug from
//         <jQuery hashchange event> in BBQ, which was the main reason for the
//         previous release!
// 1.2   - (2/16/2010) Integrated <jQuery hashchange event> v1.2, which fixes a
//         Safari bug, the event can now be bound before DOM ready, and IE6/7
//         page should no longer scroll when the event is first bound. Also
//         added the <jQuery.param.fragment.noEscape> method, and reworked the
//         <hashchange event (BBQ)> internal "add" method to be compatible with
//         changes made to the jQuery 1.4.2 special events API.
// 1.1.1 - (1/22/2010) Integrated <jQuery hashchange event> v1.1, which fixes an
//         obscure IE8 EmulateIE7 meta tag compatibility mode bug.
// 1.1   - (1/9/2010) Broke out the jQuery BBQ event.special <hashchange event>
//         functionality into a separate plugin for users who want just the
//         basic event & back button support, without all the extra awesomeness
//         that BBQ provides. This plugin will be included as part of jQuery BBQ,
//         but also be available separately. See <jQuery hashchange event>
//         plugin for more information. Also added the <jQuery.bbq.removeState>
//         method and added additional <jQuery.deparam> examples.
// 1.0.3 - (12/2/2009) Fixed an issue in IE 6 where location.search and
//         location.hash would report incorrectly if the hash contained the ?
//         character. Also <jQuery.param.querystring> and <jQuery.param.fragment>
//         will no longer parse params out of a URL that doesn't contain ? or #,
//         respectively.
// 1.0.2 - (10/10/2009) Fixed an issue in IE 6/7 where the hidden IFRAME caused
//         a "This page contains both secure and nonsecure items." warning when
//         used on an https:// page.
// 1.0.1 - (10/7/2009) Fixed an issue in IE 8. Since both "IE7" and "IE8
//         Compatibility View" modes erroneously report that the browser
//         supports the native window.onhashchange event, a slightly more
//         robust test needed to be added.
// 1.0   - (10/2/2009) Initial release

(function ($, window) {
    '$:nomunge'; // Used by YUI compressor.

    // Some convenient shortcuts.
    var undefined,
    aps = Array.prototype.slice,
    decode = decodeURIComponent,

    // Method / object references.
    jq_param = $.param,
    jq_param_fragment,
    jq_deparam,
    jq_deparam_fragment,
    jq_bbq = $.bbq = $.bbq || {},
    jq_bbq_pushState,
    jq_bbq_getState,
    jq_elemUrlAttr,
    jq_event_special = $.event.special,

    // Reused strings.
    str_hashchange = 'hashchange',
    str_querystring = 'querystring',
    str_fragment = 'fragment',
    str_elemUrlAttr = 'elemUrlAttr',
    str_location = 'location',
    str_href = 'href',
    str_src = 'src',

    // Reused RegExp.
    re_trim_querystring = /^.*\?|#.*$/g,
    re_trim_fragment = /^.*\#/,
    re_no_escape,

    // Used by jQuery.elemUrlAttr.
    elemUrlAttr_cache = {};

    // A few commonly used bits, broken out to help reduce minified file size.

    function is_string(arg) {
        return typeof arg === 'string';
    };

    // Why write the same function twice? Let's curry! Mmmm, curry..

    function curry(func) {
        var args = aps.call(arguments, 1);

        return function () {
            return func.apply(this, args.concat(aps.call(arguments)));
        };
    };

    // Get location.hash (or what you'd expect location.hash to be) sans any
    // leading #. Thanks for making this necessary, Firefox!
    function get_fragment(url) {
        return url.replace(/^[^#]*#?(.*)$/, '$1');
    };

    // Get location.search (or what you'd expect location.search to be) sans any
    // leading #. Thanks for making this necessary, IE6!
    function get_querystring(url) {
        return url.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/, '$1');
    };

    // Section: Param (to string)
    // 
    // Method: jQuery.param.querystring
    // 
    // Retrieve the query string from a URL or if no arguments are passed, the
    // current window.location.
    // 
    // Usage:
    // 
    // > jQuery.param.querystring( [ url ] );
    // 
    // Arguments:
    // 
    //  url - (String) A URL containing query string params to be parsed. If url
    //    is not passed, the current window.location is used.
    // 
    // Returns:
    // 
    //  (String) The parsed query string, with any leading "?" removed.
    //

    // Method: jQuery.param.querystring (build url)
    // 
    // Merge a URL, with or without pre-existing query string params, plus any
    // object, params string or URL containing query string params into a new URL.
    // 
    // Usage:
    // 
    // > jQuery.param.querystring( url, params [, merge_mode ] );
    // 
    // Arguments:
    // 
    //  url - (String) A valid URL for params to be merged into. This URL may
    //    contain a query string and/or fragment (hash).
    //  params - (String) A params string or URL containing query string params to
    //    be merged into url.
    //  params - (Object) A params object to be merged into url.
    //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
    //    specified, and is as-follows:
    // 
    //    * 0: params in the params argument will override any query string
    //         params in url.
    //    * 1: any query string params in url will override params in the params
    //         argument.
    //    * 2: params argument will completely replace any query string in url.
    // 
    // Returns:
    // 
    //  (String) Either a params string with urlencoded data or a URL with a
    //    urlencoded query string in the format 'a=b&c=d&e=f'.

    // Method: jQuery.param.fragment
    // 
    // Retrieve the fragment (hash) from a URL or if no arguments are passed, the
    // current window.location.
    // 
    // Usage:
    // 
    // > jQuery.param.fragment( [ url ] );
    // 
    // Arguments:
    // 
    //  url - (String) A URL containing fragment (hash) params to be parsed. If
    //    url is not passed, the current window.location is used.
    // 
    // Returns:
    // 
    //  (String) The parsed fragment (hash) string, with any leading "#" removed.

    // Method: jQuery.param.fragment (build url)
    // 
    // Merge a URL, with or without pre-existing fragment (hash) params, plus any
    // object, params string or URL containing fragment (hash) params into a new
    // URL.
    // 
    // Usage:
    // 
    // > jQuery.param.fragment( url, params [, merge_mode ] );
    // 
    // Arguments:
    // 
    //  url - (String) A valid URL for params to be merged into. This URL may
    //    contain a query string and/or fragment (hash).
    //  params - (String) A params string or URL containing fragment (hash) params
    //    to be merged into url.
    //  params - (Object) A params object to be merged into url.
    //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
    //    specified, and is as-follows:
    // 
    //    * 0: params in the params argument will override any fragment (hash)
    //         params in url.
    //    * 1: any fragment (hash) params in url will override params in the
    //         params argument.
    //    * 2: params argument will completely replace any query string in url.
    // 
    // Returns:
    // 
    //  (String) Either a params string with urlencoded data or a URL with a
    //    urlencoded fragment (hash) in the format 'a=b&c=d&e=f'.

    function jq_param_sub(is_fragment, get_func, url, params, merge_mode) {
        var result,
      qs,
      matches,
      url_params,
      hash;

        if (params !== undefined) {
            // Build URL by merging params into url string.

            // matches[1] = url part that precedes params, not including trailing ?/#
            // matches[2] = params, not including leading ?/#
            // matches[3] = if in 'querystring' mode, hash including leading #, otherwise ''
            matches = url.match(is_fragment ? /^([^#]*)\#?(.*)$/ : /^([^#?]*)\??([^#]*)(#?.*)/);

            // Get the hash if in 'querystring' mode, and it exists.
            hash = matches[3] || '';

            if (merge_mode === 2 && is_string(params)) {
                // If merge_mode is 2 and params is a string, merge the fragment / query
                // string into the URL wholesale, without converting it into an object.
                qs = params.replace(is_fragment ? re_trim_fragment : re_trim_querystring, '');

            } else {
                // Convert relevant params in url to object.
                url_params = jq_deparam(matches[2]);

                params = is_string(params)

                // Convert passed params string into object.
          ? jq_deparam[is_fragment ? str_fragment : str_querystring](params)

                // Passed params object.
          : params;

                qs = merge_mode === 2 ? params                              // passed params replace url params
          : merge_mode === 1 ? $.extend({}, params, url_params)  // url params override passed params
          : $.extend({}, url_params, params);                     // passed params override url params

                // Convert params object to a string.
                qs = jq_param(qs);

                // Unescape characters specified via $.param.noEscape. Since only hash-
                // history users have requested this feature, it's only enabled for
                // fragment-related params strings.
                if (is_fragment) {
                    qs = qs.replace(re_no_escape, decode);
                }
            }

            // Build URL from the base url, querystring and hash. In 'querystring'
            // mode, ? is only added if a query string exists. In 'fragment' mode, #
            // is always added.
            result = matches[1] + (is_fragment ? '#' : qs || !matches[1] ? '?' : '') + qs + hash;

        } else {
            // If URL was passed in, parse params from URL string, otherwise parse
            // params from window.location.
            result = get_func(url !== undefined ? url : window[str_location][str_href]);
        }

        return result;
    };

    jq_param[str_querystring] = curry(jq_param_sub, 0, get_querystring);
    jq_param[str_fragment] = jq_param_fragment = curry(jq_param_sub, 1, get_fragment);

    // Method: jQuery.param.fragment.noEscape
    // 
    // Specify characters that will be left unescaped when fragments are created
    // or merged using <jQuery.param.fragment>, or when the fragment is modified
    // using <jQuery.bbq.pushState>. This option only applies to serialized data
    // object fragments, and not set-as-string fragments. Does not affect the
    // query string. Defaults to ",/" (comma, forward slash).
    // 
    // Note that this is considered a purely aesthetic option, and will help to
    // create URLs that "look pretty" in the address bar or bookmarks, without
    // affecting functionality in any way. That being said, be careful to not
    // unescape characters that are used as delimiters or serve a special
    // purpose, such as the "#?&=+" (octothorpe, question mark, ampersand,
    // equals, plus) characters.
    // 
    // Usage:
    // 
    // > jQuery.param.fragment.noEscape( [ chars ] );
    // 
    // Arguments:
    // 
    //  chars - (String) The characters to not escape in the fragment. If
    //    unspecified, defaults to empty string (escape all characters).
    // 
    // Returns:
    // 
    //  Nothing.

    jq_param_fragment.noEscape = function (chars) {
        chars = chars || '';
        var arr = $.map(chars.split(''), encodeURIComponent);
        re_no_escape = new RegExp(arr.join('|'), 'g');
    };

    // A sensible default. These are the characters people seem to complain about
    // "uglifying up the URL" the most.
    jq_param_fragment.noEscape(',/');

    // Section: Deparam (from string)
    // 
    // Method: jQuery.deparam
    // 
    // Deserialize a params string into an object, optionally coercing numbers,
    // booleans, null and undefined values; this method is the counterpart to the
    // internal jQuery.param method.
    // 
    // Usage:
    // 
    // > jQuery.deparam( params [, coerce ] );
    // 
    // Arguments:
    // 
    //  params - (String) A params string to be parsed.
    //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
    //    undefined to their actual value. Defaults to false if omitted.
    // 
    // Returns:
    // 
    //  (Object) An object representing the deserialized params string.

    $.deparam = jq_deparam = function (params, coerce) {
        var obj = {},
      coerce_types = { 'true': !0, 'false': !1, 'null': null };

        // Iterate over all name=value pairs.
        $.each(params.replace(/\+/g, ' ').split('&'), function (j, v) {
            var param = v.split('='),
        key = decode(param[0]),
        val,
        cur = obj,
        i = 0,

            // If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it
            // into its component parts.
        keys = key.split(']['),
        keys_last = keys.length - 1;

            // If the first keys part contains [ and the last ends with ], then []
            // are correctly balanced.
            if (/\[/.test(keys[0]) && /\]$/.test(keys[keys_last])) {
                // Remove the trailing ] from the last keys part.
                keys[keys_last] = keys[keys_last].replace(/\]$/, '');

                // Split first keys part into two parts on the [ and add them back onto
                // the beginning of the keys array.
                keys = keys.shift().split('[').concat(keys);

                keys_last = keys.length - 1;
            } else {
                // Basic 'foo' style key.
                keys_last = 0;
            }

            // Are we dealing with a name=value pair, or just a name?
            if (param.length === 2) {
                val = decode(param[1]);

                // Coerce values.
                if (coerce) {
                    val = val && !isNaN(val) ? +val              // number
            : val === 'undefined' ? undefined         // undefined
            : coerce_types[val] !== undefined ? coerce_types[val] // true, false, null
            : val;                                                // string
                }

                if (keys_last) {
                    // Complex key, build deep object structure based on a few rules:
                    // * The 'cur' pointer starts at the object top-level.
                    // * [] = array push (n is set to array length), [n] = array if n is 
                    //   numeric, otherwise object.
                    // * If at the last keys part, set the value.
                    // * For each keys part, if the current level is undefined create an
                    //   object or array based on the type of the next keys part.
                    // * Move the 'cur' pointer to the next level.
                    // * Rinse & repeat.
                    for (; i <= keys_last; i++) {
                        key = keys[i] === '' ? cur.length : keys[i];
                        cur = cur[key] = i < keys_last
              ? cur[key] || (keys[i + 1] && isNaN(keys[i + 1]) ? {} : [])
              : val;
                    }

                } else {
                    // Simple key, even simpler rules, since only scalars and shallow
                    // arrays are allowed.

                    if ($.isArray(obj[key])) {
                        // val is already an array, so push on the next value.
                        obj[key].push(val);

                    } else if (obj[key] !== undefined) {
                        // val isn't an array, but since a second value has been specified,
                        // convert val into an array.
                        obj[key] = [obj[key], val];

                    } else {
                        // val is a scalar.
                        obj[key] = val;
                    }
                }

            } else if (key) {
                // No value was defined, so set something meaningful.
                obj[key] = coerce
          ? undefined
          : '';
            }
        });

        return obj;
    };

    // Method: jQuery.deparam.querystring
    // 
    // Parse the query string from a URL or the current window.location,
    // deserializing it into an object, optionally coercing numbers, booleans,
    // null and undefined values.
    // 
    // Usage:
    // 
    // > jQuery.deparam.querystring( [ url ] [, coerce ] );
    // 
    // Arguments:
    // 
    //  url - (String) An optional params string or URL containing query string
    //    params to be parsed. If url is omitted, the current window.location
    //    is used.
    //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
    //    undefined to their actual value. Defaults to false if omitted.
    // 
    // Returns:
    // 
    //  (Object) An object representing the deserialized params string.

    // Method: jQuery.deparam.fragment
    // 
    // Parse the fragment (hash) from a URL or the current window.location,
    // deserializing it into an object, optionally coercing numbers, booleans,
    // null and undefined values.
    // 
    // Usage:
    // 
    // > jQuery.deparam.fragment( [ url ] [, coerce ] );
    // 
    // Arguments:
    // 
    //  url - (String) An optional params string or URL containing fragment (hash)
    //    params to be parsed. If url is omitted, the current window.location
    //    is used.
    //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
    //    undefined to their actual value. Defaults to false if omitted.
    // 
    // Returns:
    // 
    //  (Object) An object representing the deserialized params string.

    function jq_deparam_sub(is_fragment, url_or_params, coerce) {
        if (url_or_params === undefined || typeof url_or_params === 'boolean') {
            // url_or_params not specified.
            coerce = url_or_params;
            url_or_params = jq_param[is_fragment ? str_fragment : str_querystring]();
        } else {
            url_or_params = is_string(url_or_params)
        ? url_or_params.replace(is_fragment ? re_trim_fragment : re_trim_querystring, '')
        : url_or_params;
        }

        return jq_deparam(url_or_params, coerce);
    };

    jq_deparam[str_querystring] = curry(jq_deparam_sub, 0);
    jq_deparam[str_fragment] = jq_deparam_fragment = curry(jq_deparam_sub, 1);

    // Section: Element manipulation
    // 
    // Method: jQuery.elemUrlAttr
    // 
    // Get the internal "Default URL attribute per tag" list, or augment the list
    // with additional tag-attribute pairs, in case the defaults are insufficient.
    // 
    // In the <jQuery.fn.querystring> and <jQuery.fn.fragment> methods, this list
    // is used to determine which attribute contains the URL to be modified, if
    // an "attr" param is not specified.
    // 
    // Default Tag-Attribute List:
    // 
    //  a      - href
    //  base   - href
    //  iframe - src
    //  img    - src
    //  input  - src
    //  form   - action
    //  link   - href
    //  script - src
    // 
    // Usage:
    // 
    // > jQuery.elemUrlAttr( [ tag_attr ] );
    // 
    // Arguments:
    // 
    //  tag_attr - (Object) An object containing a list of tag names and their
    //    associated default attribute names in the format { tag: 'attr', ... } to
    //    be merged into the internal tag-attribute list.
    // 
    // Returns:
    // 
    //  (Object) An object containing all stored tag-attribute values.

    // Only define function and set defaults if function doesn't already exist, as
    // the urlInternal plugin will provide this method as well.
    $[str_elemUrlAttr] || ($[str_elemUrlAttr] = function (obj) {
        return $.extend(elemUrlAttr_cache, obj);
    })({
        a: str_href,
        base: str_href,
        iframe: str_src,
        img: str_src,
        input: str_src,
        form: 'action',
        link: str_href,
        script: str_src
    });

    jq_elemUrlAttr = $[str_elemUrlAttr];

    // Method: jQuery.fn.querystring
    // 
    // Update URL attribute in one or more elements, merging the current URL (with
    // or without pre-existing query string params) plus any params object or
    // string into a new URL, which is then set into that attribute. Like
    // <jQuery.param.querystring (build url)>, but for all elements in a jQuery
    // collection.
    // 
    // Usage:
    // 
    // > jQuery('selector').querystring( [ attr, ] params [, merge_mode ] );
    // 
    // Arguments:
    // 
    //  attr - (String) Optional name of an attribute that will contain a URL to
    //    merge params or url into. See <jQuery.elemUrlAttr> for a list of default
    //    attributes.
    //  params - (Object) A params object to be merged into the URL attribute.
    //  params - (String) A URL containing query string params, or params string
    //    to be merged into the URL attribute.
    //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
    //    specified, and is as-follows:
    //    
    //    * 0: params in the params argument will override any params in attr URL.
    //    * 1: any params in attr URL will override params in the params argument.
    //    * 2: params argument will completely replace any query string in attr
    //         URL.
    // 
    // Returns:
    // 
    //  (jQuery) The initial jQuery collection of elements, but with modified URL
    //  attribute values.

    // Method: jQuery.fn.fragment
    // 
    // Update URL attribute in one or more elements, merging the current URL (with
    // or without pre-existing fragment/hash params) plus any params object or
    // string into a new URL, which is then set into that attribute. Like
    // <jQuery.param.fragment (build url)>, but for all elements in a jQuery
    // collection.
    // 
    // Usage:
    // 
    // > jQuery('selector').fragment( [ attr, ] params [, merge_mode ] );
    // 
    // Arguments:
    // 
    //  attr - (String) Optional name of an attribute that will contain a URL to
    //    merge params into. See <jQuery.elemUrlAttr> for a list of default
    //    attributes.
    //  params - (Object) A params object to be merged into the URL attribute.
    //  params - (String) A URL containing fragment (hash) params, or params
    //    string to be merged into the URL attribute.
    //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
    //    specified, and is as-follows:
    //    
    //    * 0: params in the params argument will override any params in attr URL.
    //    * 1: any params in attr URL will override params in the params argument.
    //    * 2: params argument will completely replace any fragment (hash) in attr
    //         URL.
    // 
    // Returns:
    // 
    //  (jQuery) The initial jQuery collection of elements, but with modified URL
    //  attribute values.

    function jq_fn_sub(mode, force_attr, params, merge_mode) {
        if (!is_string(params) && typeof params !== 'object') {
            // force_attr not specified.
            merge_mode = params;
            params = force_attr;
            force_attr = undefined;
        }

        return this.each(function () {
            var that = $(this),

            // Get attribute specified, or default specified via $.elemUrlAttr.
        attr = force_attr || jq_elemUrlAttr()[(this.nodeName || '').toLowerCase()] || '',

            // Get URL value.
        url = attr && that.attr(attr) || '';

            // Update attribute with new URL.
            that.attr(attr, jq_param[mode](url, params, merge_mode));
        });

    };

    $.fn[str_querystring] = curry(jq_fn_sub, str_querystring);
    $.fn[str_fragment] = curry(jq_fn_sub, str_fragment);

    // Section: History, hashchange event
    // 
    // Method: jQuery.bbq.pushState
    // 
    // Adds a 'state' into the browser history at the current position, setting
    // location.hash and triggering any bound <hashchange event> callbacks
    // (provided the new state is different than the previous state).
    // 
    // If no arguments are passed, an empty state is created, which is just a
    // shortcut for jQuery.bbq.pushState( {}, 2 ).
    // 
    // Usage:
    // 
    // > jQuery.bbq.pushState( [ params [, merge_mode ] ] );
    // 
    // Arguments:
    // 
    //  params - (String) A serialized params string or a hash string beginning
    //    with # to merge into location.hash.
    //  params - (Object) A params object to merge into location.hash.
    //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
    //    specified (unless a hash string beginning with # is specified, in which
    //    case merge behavior defaults to 2), and is as-follows:
    // 
    //    * 0: params in the params argument will override any params in the
    //         current state.
    //    * 1: any params in the current state will override params in the params
    //         argument.
    //    * 2: params argument will completely replace current state.
    // 
    // Returns:
    // 
    //  Nothing.
    // 
    // Additional Notes:
    // 
    //  * Setting an empty state may cause the browser to scroll.
    //  * Unlike the fragment and querystring methods, if a hash string beginning
    //    with # is specified as the params agrument, merge_mode defaults to 2.

    jq_bbq.pushState = jq_bbq_pushState = function (params, merge_mode) {
        if (is_string(params) && /^#/.test(params) && merge_mode === undefined) {
            // Params string begins with # and merge_mode not specified, so completely
            // overwrite window.location.hash.
            merge_mode = 2;
        }

        var has_args = params !== undefined,
        // Merge params into window.location using $.param.fragment.
      url = jq_param_fragment(window[str_location][str_href],
        has_args ? params : {}, has_args ? merge_mode : 2);

        // Set new window.location.href. If hash is empty, use just # to prevent
        // browser from reloading the page. Note that Safari 3 & Chrome barf on
        // location.hash = '#'.
        window[str_location][str_href] = url + (/#/.test(url) ? '' : '#');
    };

    // Method: jQuery.bbq.getState
    // 
    // Retrieves the current 'state' from the browser history, parsing
    // location.hash for a specific key or returning an object containing the
    // entire state, optionally coercing numbers, booleans, null and undefined
    // values.
    // 
    // Usage:
    // 
    // > jQuery.bbq.getState( [ key ] [, coerce ] );
    // 
    // Arguments:
    // 
    //  key - (String) An optional state key for which to return a value.
    //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
    //    undefined to their actual value. Defaults to false.
    // 
    // Returns:
    // 
    //  (Anything) If key is passed, returns the value corresponding with that key
    //    in the location.hash 'state', or undefined. If not, an object
    //    representing the entire 'state' is returned.

    jq_bbq.getState = jq_bbq_getState = function (key, coerce) {
        return key === undefined || typeof key === 'boolean'
      ? jq_deparam_fragment(key) // 'key' really means 'coerce' here
      : jq_deparam_fragment(coerce)[key];
    };

    // Method: jQuery.bbq.removeState
    // 
    // Remove one or more keys from the current browser history 'state', creating
    // a new state, setting location.hash and triggering any bound
    // <hashchange event> callbacks (provided the new state is different than
    // the previous state).
    // 
    // If no arguments are passed, an empty state is created, which is just a
    // shortcut for jQuery.bbq.pushState( {}, 2 ).
    // 
    // Usage:
    // 
    // > jQuery.bbq.removeState( [ key [, key ... ] ] );
    // 
    // Arguments:
    // 
    //  key - (String) One or more key values to remove from the current state,
    //    passed as individual arguments.
    //  key - (Array) A single array argument that contains a list of key values
    //    to remove from the current state.
    // 
    // Returns:
    // 
    //  Nothing.
    // 
    // Additional Notes:
    // 
    //  * Setting an empty state may cause the browser to scroll.

    jq_bbq.removeState = function (arr) {
        var state = {};

        // If one or more arguments is passed..
        if (arr !== undefined) {

            // Get the current state.
            state = jq_bbq_getState();

            // For each passed key, delete the corresponding property from the current
            // state.
            $.each($.isArray(arr) ? arr : arguments, function (i, v) {
                delete state[v];
            });
        }

        // Set the state, completely overriding any existing state.
        jq_bbq_pushState(state, 2);
    };

    // Event: hashchange event (BBQ)
    // 
    // Usage in jQuery 1.4 and newer:
    // 
    // In jQuery 1.4 and newer, the event object passed into any hashchange event
    // callback is augmented with a copy of the location.hash fragment at the time
    // the event was triggered as its event.fragment property. In addition, the
    // event.getState method operates on this property (instead of location.hash)
    // which allows this fragment-as-a-state to be referenced later, even after
    // window.location may have changed.
    // 
    // Note that event.fragment and event.getState are not defined according to
    // W3C (or any other) specification, but will still be available whether or
    // not the hashchange event exists natively in the browser, because of the
    // utility they provide.
    // 
    // The event.fragment property contains the output of <jQuery.param.fragment>
    // and the event.getState method is equivalent to the <jQuery.bbq.getState>
    // method.
    // 
    // > $(window).bind( 'hashchange', function( event ) {
    // >   var hash_str = event.fragment,
    // >     param_obj = event.getState(),
    // >     param_val = event.getState( 'param_name' ),
    // >     param_val_coerced = event.getState( 'param_name', true );
    // >   ...
    // > });
    // 
    // Usage in jQuery 1.3.2:
    // 
    // In jQuery 1.3.2, the event object cannot to be augmented as in jQuery 1.4+,
    // so the fragment state isn't bound to the event object and must instead be
    // parsed using the <jQuery.param.fragment> and <jQuery.bbq.getState> methods.
    // 
    // > $(window).bind( 'hashchange', function( event ) {
    // >   var hash_str = $.param.fragment(),
    // >     param_obj = $.bbq.getState(),
    // >     param_val = $.bbq.getState( 'param_name' ),
    // >     param_val_coerced = $.bbq.getState( 'param_name', true );
    // >   ...
    // > });
    // 
    // Additional Notes:
    // 
    // * Due to changes in the special events API, jQuery BBQ v1.2 or newer is
    //   required to enable the augmented event object in jQuery 1.4.2 and newer.
    // * See <jQuery hashchange event> for more detailed information.

    jq_event_special[str_hashchange] = $.extend(jq_event_special[str_hashchange], {

        // Augmenting the event object with the .fragment property and .getState
        // method requires jQuery 1.4 or newer. Note: with 1.3.2, everything will
        // work, but the event won't be augmented)
        add: function (handleObj) {
            var old_handler;

            function new_handler(e) {
                // e.fragment is set to the value of location.hash (with any leading #
                // removed) at the time the event is triggered.
                var hash = e[str_fragment] = jq_param_fragment();

                // e.getState() works just like $.bbq.getState(), but uses the
                // e.fragment property stored on the event object.
                e.getState = function (key, coerce) {
                    return key === undefined || typeof key === 'boolean'
            ? jq_deparam(hash, key) // 'key' really means 'coerce' here
            : jq_deparam(hash, coerce)[key];
                };

                old_handler.apply(this, arguments);
            };

            // This may seem a little complicated, but it normalizes the special event
            // .add method between jQuery 1.4/1.4.1 and 1.4.2+
            if ($.isFunction(handleObj)) {
                // 1.4, 1.4.1
                old_handler = handleObj;
                return new_handler;
            } else {
                // 1.4.2+
                old_handler = handleObj.handler;
                handleObj.handler = new_handler;
            }
        }

    });

})(jQuery, this);

/*!
* jQuery hashchange event - v1.2 - 2/11/2010
* http://benalman.com/projects/jquery-hashchange-plugin/
* 
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/

// Script: jQuery hashchange event
//
// *Version: 1.2, Last updated: 2/11/2010*
// 
// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
// GitHub       - http://github.com/cowboy/jquery-hashchange/
// Source       - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
// (Minified)   - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (1.1kb)
// 
// About: License
// 
// Copyright (c) 2010 "Cowboy" Ben Alman,
// Dual licensed under the MIT and GPL licenses.
// http://benalman.com/about/license/
// 
// About: Examples
// 
// This working example, complete with fully commented code, illustrate one way
// in which this plugin can be used.
// 
// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
// 
// About: Support and Testing
// 
// Information about what version or versions of jQuery this plugin has been
// tested with, what browsers it has been tested in, and where the unit tests
// reside (so you can test it yourself).
// 
// jQuery Versions - 1.3.2, 1.4.1, 1.4.2
// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4, Chrome, Opera 9.6-10.1.
// Unit Tests      - http://benalman.com/code/projects/jquery-hashchange/unit/
// 
// About: Known issues
// 
// While this jQuery hashchange event implementation is quite stable and robust,
// there are a few unfortunate browser bugs surrounding expected hashchange
// event-based behaviors, independent of any JavaScript window.onhashchange
// abstraction. See the following examples for more information:
// 
// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
// 
// About: Release History
// 
// 1.2   - (2/11/2010) Fixed a bug where coming back to a page using this plugin
//         from a page on another domain would cause an error in Safari 4. Also,
//         IE6/7 Iframe is now inserted after the body (this actually works),
//         which prevents the page from scrolling when the event is first bound.
//         Event can also now be bound before DOM ready, but it won't be usable
//         before then in IE6/7.
// 1.1   - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug
//         where browser version is incorrectly reported as 8.0, despite
//         inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag.
// 1.0   - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special
//         window.onhashchange functionality into a separate plugin for users
//         who want just the basic event & back button support, without all the
//         extra awesomeness that BBQ provides. This plugin will be included as
//         part of jQuery BBQ, but also be available separately.

(function ($, window, undefined) {
    '$:nomunge'; // Used by YUI compressor.

    // Method / object references.
    var fake_onhashchange,
    jq_event_special = $.event.special,

    // Reused strings.
    str_location = 'location',
    str_hashchange = 'hashchange',
    str_href = 'href',

    // IE6/7 specifically need some special love when it comes to back-button
    // support, so let's do a little browser sniffing..
    browser = $.browser,
    mode = document.documentMode,
    is_old_ie = browser.msie && (mode === undefined || mode < 8),

    // Does the browser support window.onhashchange? Test for IE version, since
    // IE8 incorrectly reports this when in "IE7" or "IE8 Compatibility View"!
    supports_onhashchange = 'on' + str_hashchange in window && !is_old_ie;

    // Get location.hash (or what you'd expect location.hash to be) sans any
    // leading #. Thanks for making this necessary, Firefox!
    function get_fragment(url) {
        url = url || window[str_location][str_href];
        return url.replace(/^[^#]*#?(.*)$/, '$1');
    };

    // Property: jQuery.hashchangeDelay
    // 
    // The numeric interval (in milliseconds) at which the <hashchange event>
    // polling loop executes. Defaults to 100.

    $[str_hashchange + 'Delay'] = 100;

    // Event: hashchange event
    // 
    // Fired when location.hash changes. In browsers that support it, the native
    // window.onhashchange event is used (IE8, FF3.6), otherwise a polling loop is
    // initialized, running every <jQuery.hashchangeDelay> milliseconds to see if
    // the hash has changed. In IE 6 and 7, a hidden Iframe is created to allow
    // the back button and hash-based history to work.
    // 
    // Usage:
    // 
    // > $(window).bind( 'hashchange', function(e) {
    // >   var hash = location.hash;
    // >   ...
    // > });
    // 
    // Additional Notes:
    // 
    // * The polling loop and Iframe are not created until at least one callback
    //   is actually bound to 'hashchange'.
    // * If you need the bound callback(s) to execute immediately, in cases where
    //   the page 'state' exists on page load (via bookmark or page refresh, for
    //   example) use $(window).trigger( 'hashchange' );
    // * The event can be bound before DOM ready, but since it won't be usable
    //   before then in IE6/7 (due to the necessary Iframe), recommended usage is
    //   to bind it inside a $(document).ready() callback.

    jq_event_special[str_hashchange] = $.extend(jq_event_special[str_hashchange], {

        // Called only when the first 'hashchange' event is bound to window.
        setup: function () {
            // If window.onhashchange is supported natively, there's nothing to do..
            if (supports_onhashchange) { return false; }

            // Otherwise, we need to create our own. And we don't want to call this
            // until the user binds to the event, just in case they never do, since it
            // will create a polling loop and possibly even a hidden Iframe.
            $(fake_onhashchange.start);
        },

        // Called only when the last 'hashchange' event is unbound from window.
        teardown: function () {
            // If window.onhashchange is supported natively, there's nothing to do..
            if (supports_onhashchange) { return false; }

            // Otherwise, we need to stop ours (if possible).
            $(fake_onhashchange.stop);
        }

    });

    // fake_onhashchange does all the work of triggering the window.onhashchange
    // event for browsers that don't natively support it, including creating a
    // polling loop to watch for hash changes and in IE 6/7 creating a hidden
    // Iframe to enable back and forward.
    fake_onhashchange = (function () {
        var self = {},
      timeout_id,
      iframe,
      set_history,
      get_history;

        // Initialize. In IE 6/7, creates a hidden Iframe for history handling.
        function init() {
            // Most browsers don't need special methods here..
            set_history = get_history = function (val) { return val; };

            // But IE6/7 do!
            if (is_old_ie) {

                // Create hidden Iframe after the end of the body to prevent initial
                // page load from scrolling unnecessarily.
                iframe = $('<iframe src="javascript:0"/>').hide().insertAfter('body')[0].contentWindow;

                // Get history by looking at the hidden Iframe's location.hash.
                get_history = function () {
                    return get_fragment(iframe.document[str_location][str_href]);
                };

                // Set a new history item by opening and then closing the Iframe
                // document, *then* setting its location.hash.
                set_history = function (hash, history_hash) {
                    if (hash !== history_hash) {
                        var doc = iframe.document;
                        doc.open().close();
                        doc[str_location].hash = '#' + hash;
                    }
                };

                // Set initial history.
                set_history(get_fragment());
            }
        };

        // Start the polling loop.
        self.start = function () {
            // Polling loop is already running!
            if (timeout_id) { return; }

            // Remember the initial hash so it doesn't get triggered immediately.
            var last_hash = get_fragment();

            // Initialize if not yet initialized.
            set_history || init();

            // This polling loop checks every $.hashchangeDelay milliseconds to see if
            // location.hash has changed, and triggers the 'hashchange' event on
            // window when necessary.
            (function loopy() {
                var hash = get_fragment(),
          history_hash = get_history(last_hash);

                if (hash !== last_hash) {
                    set_history(last_hash = hash, history_hash);

                    $(window).trigger(str_hashchange);

                } else if (history_hash !== last_hash) {
                    window[str_location][str_href] = window[str_location][str_href].replace(/#.*/, '') + '#' + history_hash;
                }

                timeout_id = setTimeout(loopy, $[str_hashchange + 'Delay']);
            })();
        };

        // Stop the polling loop, but only if an IE6/7 Iframe wasn't created. In
        // that case, even if there are no longer any bound event handlers, the
        // polling loop is still necessary for back/next to work at all!
        self.stop = function () {
            if (!iframe) {
                timeout_id && clearTimeout(timeout_id);
                timeout_id = 0;
            }
        };

        return self;
    })();

})(jQuery, this);

