
// remap jQuery to $
(function ($) {

    /**
    * Copyright (c) 2009 Sergiy Kovalchuk (serg472@gmail.com)
    * 
    * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
    * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
    *  
    * Following code is based on Element.mask() implementation from ExtJS framework (http://extjs.com/)
    *
    */
    /**
    * Displays loading mask over selected element(s). Accepts both single and multiple selectors.
    *
    * @param label Text message that will be displayed on top of the mask besides a spinner (optional). 
    * 				If not provided only mask will be displayed without a label or a spinner.  	
    * @param delay Delay in milliseconds before element is masked (optional). If unmask() is called 
    *              before the delay times out, no mask is displayed. This can be used to prevent unnecessary 
    *              mask display for quick processes.   	
    */
    $.fn.mask = function (label, delay) {
        $(this).each(function () {
            if (delay !== undefined && delay > 0) {
                var element = $(this);
                element.data("_mask_timeout", setTimeout(function () { $.maskElement(element, label) }, delay));
            } else {
                $.maskElement($(this), label);
            }
        });
    };

    /**
    * Removes mask from the element(s). Accepts both single and multiple selectors.
    */
    $.fn.unmask = function () {
        $(this).each(function () {
            $.unmaskElement($(this));
        });
    };

    /**
    * Checks if a single element is masked. Returns false if mask is delayed or not displayed. 
    */
    $.fn.isMasked = function () {
        return this.hasClass("masked");
    };

    $.maskElement = function (element, label) {

        //if this element has delayed mask scheduled then remove it and display the new one
        if (element.data("_mask_timeout") !== undefined) {
            clearTimeout(element.data("_mask_timeout"));
            element.removeData("_mask_timeout");
        }

        if (element.isMasked()) {
            $.unmaskElement(element);
        }

        if (element.css("position") == "static") {
            element.addClass("masked-relative");
        }

        element.addClass("masked");

        var maskDiv = $('<div class="loadmask"></div>');

        //auto height fix for IE
        if (navigator.userAgent.toLowerCase().indexOf("msie") > -1) {
            maskDiv.height(element.height() + parseInt(element.css("padding-top")) + parseInt(element.css("padding-bottom")));
            maskDiv.width(element.width() + parseInt(element.css("padding-left")) + parseInt(element.css("padding-right")));
        }

        //fix for z-index bug with selects in IE6
        if (navigator.userAgent.toLowerCase().indexOf("msie 6") > -1) {
            element.find("select").addClass("masked-hidden");
        }

        element.append(maskDiv);

        if (label !== undefined) {
            var maskMsgDiv = $('<div class="loadmask-msg" style="display:none;"></div>');
            maskMsgDiv.append('<div>' + label + '</div>');
            element.append(maskMsgDiv);

            //calculate center position
            maskMsgDiv.css("top", Math.round(element.height() / 2 - (maskMsgDiv.height() - parseInt(maskMsgDiv.css("padding-top")) - parseInt(maskMsgDiv.css("padding-bottom"))) / 2) + "px");
            maskMsgDiv.css("left", Math.round(element.width() / 2 - (maskMsgDiv.width() - parseInt(maskMsgDiv.css("padding-left")) - parseInt(maskMsgDiv.css("padding-right"))) / 2) + "px");

            maskMsgDiv.show();
        }

    };

    $.unmaskElement = function (element) {
        //if this element has delayed mask scheduled then remove it
        if (element.data("_mask_timeout") !== undefined) {
            clearTimeout(element.data("_mask_timeout"));
            element.removeData("_mask_timeout");
        }

        element.find(".loadmask-msg,.loadmask").remove();
        element.removeClass("masked");
        element.removeClass("masked-relative");
        element.find("select").removeClass("masked-hidden");
    };
    /** End of Loadmask plugin */

    /** Password strength **/

    $.fn.extend({
        pwdstr: function (el) {
            return this.each(function () {

                var Frequency_Table = new Array();

                function Get_Index(c) {
                    c = c.charAt(0).toLowerCase();
                    if (c < 'a' || c > 'z') {
                        return 0;
                    }
                    return c.charCodeAt(0) - 'a'.charCodeAt(0) + 1;
                }

                function Get_Charset_Size(pass) {
                    var a = 0, u = 0, n = 0, ns = 0, r = 0, sp = 0, s = 0, chars = 0;

                    for (var i = 0; i < pass.length; i++) {
                        var c = pass.charAt(i);

                        if (a == 0 && 'abcdefghijklmnopqrstuvwxyz'.indexOf(c) >= 0) {
                            chars += 26;
                            a = 1;
                        }
                        if (u == 0 && 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.indexOf(c) >= 0) {
                            chars += 26;
                            u = 1;
                        }
                        if (n == 0 && '0123456789'.indexOf(c) >= 0) {
                            chars += 10;
                            n = 1;
                        }
                        if (ns == 0 && '!@#$%^&*()'.indexOf(c) >= 0) {
                            chars += 10;
                            ns = 1;
                        }
                        if (r == 0 && "`~-_=+[{]}\\|;:'\",<.>/?".indexOf(c) >= 0) {
                            chars += 20;
                            r = 1;
                        }
                        if (sp == 0 && c == ' ') {
                            chars += 1;
                            sp = 1;
                        }
                        if (s == 0 && (c < ' ' || c > '~')) {
                            chars += 32 + 128;
                            s = 1;
                        }
                    }

                    return chars;
                }

                // The frequency thing is a bit more interesting, but still not too complex.
                // Each three letters are base-95 encoded number representing the chance that
                // this combination comes next.  Subtract the value of ' ' from each of the
                // three, then ((((first_value * 95) + second_value) * 95) + third_value) will
                // give you the odds that this pair is grouped together.  The first is "  "
                // (non-alpha chars), then " a", " b", etc. " y", " z", "a ", "aa", "ab", and
                // so on.  If you decrypt the table successfully, you should see a really large
                // number for "qu".
                function Parse_Frequency_Token() {
                    var c;

                    c = Frequency_List.charCodeAt(0) - ' '.charCodeAt(0);
                    c /= 95;
                    c += Frequency_List.charCodeAt(1) - ' '.charCodeAt(0);
                    c /= 95;
                    c += Frequency_List.charCodeAt(2) - ' '.charCodeAt(0);
                    c /= 95;

                    Frequency_List = Frequency_List.substr(3, Frequency_List.length);

                    Frequency_Table[Frequency_Table.length] = c;
                }

                var Frequency_List = "gL6-A$A:#@'$KT#<c\"06\"8*\"Wb\"5-$Kr t%#f+\"S4$7;$ v!R=%!6 g@#s}&'P#rM\"a8\"K'!3= #V }o wx*bZ\"X=\"De#2 #~B!dL!\\\\\"(u qj#Pf L_\"e (1K$9--x6 SE\"x- .$)/s%rK(w\"\"QV\"oQ ;; {_ [| q@(8t-G7#nU MW cS2$9 UJ GH <]*#S !: >!'kQ \\Z @A)O8 5& E (L\"\"YZ r_&g2 66 ~& *L!n* zO,|Y)B8 V.#X: qI'V0 B7 s@4<B)>= I-$wt!;A =h ?G+y: p/ |k\"}} &w\"]?#72 W) j$ @w S !I700V'gf (Q cf!9{9cL Qa .i #Z, F *M NZ!0c RX!+%((N S$ ]o#hU#{o Z2#j_ 6P d% I!!wX _Z.nH!i.!@y!6T#a$\"Yt ?]!kv Jl\"]{ sh!2$&x~\"5F.8X AF!n2 :A1G@('.%5} 24!TZ XY \\P zq G,).J)>} GD _o Xu*di&m0!%Q  L-KA @I ei%4P <S 8m+NI l& Gg&m+\"nS$T{%&? YT e( Mu g2 ~., B(\\P N6 .d .M:^6 gg\"]s\"k2':6 LU /h#k_ 6-#$3%p9 W$ 7x&)(%_-\">[#Q8 VM y( 3q c* vn/va-K> ^N Il LQ/y9 qF *< /k+^O zp K;\"bZ!M\\!>(*NW L' .-#=<\"Uz$^:$o? C] F' /)\"+) FC+8H$M&!l1&2'#n\"&K<!d-#\"_ jw A{ J(#~e$Yi#(v.Ng#NF!V8 zM\"Q4*RH&.X E\"\"n; ^5 41 gM!xj(\"Q1![ bn #!\";?1la yU mR A6)GK @=\"w5!k$!bX\"b[)K-!R) Mq Fl&uz Zx&p\\ <A X' f&!B_ xj)GW.3F <G *5 .e-}6 (6 C-!U>)S< :r#=;#:C 9Q!xJ,7< w\" Tp#*1%|F#Ze&JW Dn /$ ](\"4( H{&>!.@h W[ .F\"G}/#k :_ ,U ;$04E \\,!/P)h9!Hq f4( f S& 18 M^\":'#w|#]o 7= !# Y]\"*4 YZ-<E0$]\"B^ QO bn/$G )> zO e>-|+ \\? SY (d$nj b%)p+%aQ 2P oa!U\\ mp$x: S: *$ &.!9( 3-3KD&WW WG\"jB'`),:t I<'T_ I=($C Pd!;M 5I 3a\"h %_G $, .E (_%wZ(WC!*] w> \"$ s} WT h;*Kd UY!JW\")u\",)\"v6!AU\"Mv RS\"hZ \"(\"Ya&e/%5S-w\\\"/W#b1 lF*fp%a6$3c$aH#uf!e> cH!]v 58*En+r< Ic v< gh,&\\ +A ;a$2!)I. !8 :w$>E 2Q fH+LA$)$ Gl+NL\"y9\"43$30 \"8 d% 8l u% cV$pm 6C 2O uH .S K@ $= DB B(!f6 =9 <R *I uH A@ dG )A `K NX 3L !sopL %: o1 h0 9/ (U(5?-Q8!&=!iX\"f(0\\[ ~V!ii )\\,<? 2W!l*!}#\"\\8#NX)pn S0 Iv!{M$Rs#A2#s\"!$R k& ]J!Yi {#4jR%m` --$\\D ;(*>X ]K lS\"H~'c; ;K#hP!/^!(l !S#z+##d [H F\\%Z3/b-#)f /O c% 66!YO 2w+{3,}? {N I1  O3^6 !? xV\"s5-3I `R wn (E wj Kq';: KA XU&_'#b|$<N$2C qX w# iu!yO Fr($1\"T@\"pg\"eA#+3'g<!w$\":w `7$\\ !Z)#qf%I4%$x*pf!kN\"/* T9(Z])+?&O5\"9\"!nP 2< {9 5q R-'0~5)4 ^= j8 us9#/ @1 }3 Ul0+- U6 *K!&? +K!]p()0 Z$ va!e} Y< fl!rQ *) C( KJ#f4 $;'v.5aO W` w*!A<0IC yG ru!KR-*/ <\\ ,-!&3!Ap&Jc'+` V& s5\"gB$gF!`>!B0 hO g. ;w\"I5 |v7:@&:o %h#zz H\"(wx tL rk!q\\*w9 {D Q-!T_!lF!O$%vF&cB 8 \"Mp!SZ'he!HP rq!>N\"BA\"Y3 wR8^U$.~ ee#K[\";,#IZ ;f!KI!eE!XQ d-#s/$JY%Pz%/F$+Y#2$ XX#Ez'5g$z9\"9$!1y vB 51!La @T*\\f0)j ek L3!^@1t- II Zn m@,08 )0!-@!<-! |\"Fp&Oj A& N_ U[ P>!-h$D` 3a!K' `d#uc%";

                function Parse_Frequency() {
                    while (Frequency_List.length != 0) {
                        Parse_Frequency_Token();
                    }
                }

                Parse_Frequency();

                $(this).keyup(function () {
                    var weak = 10;
                    var medium = 32;

                    var pass = $(this).val();
                    var plower = pass.toLowerCase();
                    var s = 0;
                    if (pass.match(/^(?=.*\d)(?=.*[a-zA-Z]).{6,20}$/)) {
                        var c, aidx = 0, bits = 0, charSet;
                        charSet = Math.log(Get_Charset_Size(pass)) / Math.log(2);
                        aidx = Get_Index(plower.charAt(0));
                        for (var b = 1; b < plower.length; b++) {
                            var bidx = Get_Index(plower.charAt(b));
                            c = 1.0 - Frequency_Table[aidx * 27 + bidx];
                            bits += charSet * c * c;  // Squared = assmume they are good guessers
                            aidx = bidx;
                        }
                        s = Math.round(bits);
                    }


                    var text;
                    if (0 == s) {
                        $(el).addClass("password-strength-none");
                        $(el).removeClass("password-strength-weak");
                        $(el).removeClass("password-strength-medium");
                        $(el).removeClass("password-strength-strong");
                        text = ''
                    } else if (0 < s && s <= weak) {
                        $(el).removeClass("password-strength-none");
                        $(el).addClass("password-strength-weak");
                        $(el).removeClass("password-strength-medium");
                        $(el).removeClass("password-strength-strong");
                        text = "Weak";
                    } else if (weak < s && s <= medium) {
                        $(el).removeClass("password-strength-none");
                        $(el).removeClass("password-strength-weak");
                        $(el).addClass("password-strength-medium");
                        $(el).removeClass("password-strength-strong");
                        text = "OK";
                    } else {
                        $(el).removeClass("password-strength-none");
                        $(el).removeClass("password-strength-weak");
                        $(el).removeClass("password-strength-medium");
                        $(el).addClass("password-strength-strong");
                        text = "Strong";
                    }

                    $(el).html(text);
                });

            });
        }
    });

    /** End of password strength **/

    /** Start of requiredif **/
    $.validator.addMethod('requiredif',
        function (value, element, parameters) {
            var id = '#' + parameters['dependentproperty'];

            // get the target value (as a string, 
            // as that's what actual value will be)
            var targetvalue = parameters['targetvalue'];
            targetvalue =
              (targetvalue == null ? '' : targetvalue).toString();

            // get the actual value of the target control
            // note - this probably needs to cater for more 
            // control types, e.g. radios
            var control = $(id);
            var controltype = control.attr('type');
            var actualvalue =
                controltype === 'checkbox' ?
                control.attr('checked').toString() :
                control.val();

            // if the condition is true, reuse the existing 
            // required field validator functionality
            if (targetvalue === actualvalue)
                return $.validator.methods.required.call(
                  this, value, element, parameters);

            return true;
        }
    );

    $.validator.unobtrusive.adapters.add(
    'requiredif',
    ['dependentproperty', 'targetvalue'],
    function (options) {
        options.rules['requiredif'] = {
            dependentproperty: options.params['dependentproperty'],
            targetvalue: options.params['targetvalue']
        };
        options.messages['requiredif'] = options.message;
    });

    /** End of requiredif **/
           
    /** Start of at least one required attribute **/ 
    $.validator.unobtrusive.adapters.add("atleastonerequired", function (options) {
        //required for checkboxes
        if (options.element.tagName.toUpperCase() == "INPUT" && options.element.type.toUpperCase() == "CHECKBOX") {
            //setValidationValues(options, "required", true);
            options.rules["required"] = true;
            if (options.message) {
                options.messages["required"] = options.message;
            }
        }
    });
    /** End of at least one required attribute **/ 

    /** Start of jQuery Notifications plugin - http://programmingmind.com **/

    var template;
    var counter = 0;

    $.notifications = function (msg, options) {
        counter++;

        var settings = $.extend({}, $.notifications.defaults, options);

        if (!template) {
            template = $('<div id="jquery-notifications"></div>').prependTo(document.body);
        }

        var n = $('<p class="' + settings.type + '" id="jquery-notifications-' + counter + '">' + msg + '</p>').hide().appendTo("#jquery-notifications");
        if (settings.effect == "fade") {
            n.fadeIn(settings.fadeSpeed);
        } else {
            n.slideDown(settings.fadeSpeed);
        }

        if (settings.stick) {
            var close = $('<a href="javascript:void(0);">' + settings.close + '</a>').click(function () {
                if (settings.effect == "fade") {
                    $(this.parentNode).fadeOut(settings.fadeSpeed, function () {
                        $(this).remove();
                    });
                }
                else {
                    $(this.parentNode).slideUp(settings.fadeSpeed, function () {
                        $(this).remove();
                    });
                }
            });
            close.appendTo(n);
        }

        if (!settings.stick) {
            var notificationsDelayer = delayTimer(settings.timeout);
            notificationsDelayer(update, { counter: counter, effect: settings.effect, fadeSpeed: settings.fadeSpeed });
        }

        if ($("#errorExplanation").length) {
            // if there exists an errorExplanation div from rails 3.0,
            // hide the errors and list them down as notifications
            $("#errorExplanation").hide();
            $("#errorExplanation li").each(function (index) {
                $.n.error($(this).text());
            })
        }
    };

    $.notifications.success = function (msg, options) {
        return $.notifications(msg, $.extend({}, options, { type: "success" }));
    };

    $.notifications.error = function (msg, options) {
        return $.notifications(msg, $.extend({ stick: true }, options, { type: "error" }));
    };

    $.notifications.warning = function (msg, options) {
        return $.notifications(msg, $.extend({}, options, { type: "warning" }));
    };

    function update(params) {
        if (params.effect == "fade") {
            $("#jquery-notifications-" + params.counter).fadeOut(params.fadeSpeed, function () {
                $(this).remove();
            });
        } else {
            $("#jquery-notifications-" + params.counter).slideUp(params.fadeSpeed, function () {
                $(this).remove();
            });
        }
    }

    function delayTimer(delay) {
        var timer;
        return function (fn, params) {
            timer = clearTimeout(timer);
            if (fn)
                timer = setTimeout(function () {
                    fn(params);
                }, delay);
            return timer;
        };
    }

    $.notifications.defaults = {
        type: "notice",
        timeout: 10000,
        stick: false,
        fadeSpeed: 800,
        close: "x",
        effect: "fade"
    };

    $.n = $.notifications;

    /** End of jQuery Notifications plugin - http://programmingmind.com **/

    /*
    * selectList jQuery plugin
    * version 0.4.1
    *
    * Copyright (c) 2009-2011 Michal Wojciechowski (odyniec.net)
    *
    * Dual licensed under the MIT (MIT-LICENSE.txt)
    * and GPL (GPL-LICENSE.txt) licenses.
    *
    * http://odyniec.net/projects/selectlist/
    *
    */


    $.selectList = function (select, options) {
        var 

        $selectSingle,

        $list,

        $item, $newItem,

        $option,

        keyEvent,

        ready,

        first = 0,

        change, click, keypress, enter;

        function show($item, callback) {
            if (options.addAnimate && ready)
                if (typeof options.addAnimate == 'function')
                    options.addAnimate($item.hide()[0], callback);
                else
                    $item.hide().fadeIn(300, callback);
            else {
                $item.show();
                if (callback)
                    callback.call($item[0]);
            }
        }

        function hide($item, callback) {
            if (options.removeAnimate && ready)
                if (typeof options.removeAnimate == 'function')
                    options.removeAnimate($item[0], callback);
                else
                    $item.fadeOut(300, callback);
            else {
                $item.hide();
                if (callback)
                    callback.call($item[0]);
            }
        }

        function cmp(item1, item2) {
            return typeof options.sort == 'function' ?
            options.sort(item1, item2)
            : ($(item1).data('text') > $(item2).data('text'))
                == (options.sort != 'desc');
        }

        function add(value, text, callHandler) {
            if ($(value).is('option')) {
                $option = $(value);

                if ($option[0].index < first)
                    return;

                value = $option.val();
                text = $option.text();
            }
            else {
                $option = $selectSingle.find("option[value=\"" +

                    value.replace("'", "\\\"") + "\"]");

                if ($option.length)
                    $option = $option.filter(function () {
                        return !text || $(this).text() == text;
                    })
                .add($option).eq(0);
                else
                    $option = null;
            }

            if (text === undefined)
                text = $option ? $option.text() : value;

            if ($option && !options.duplicates)
                $option.attr('disabled', 'disabled')
                .data('disabled', 1);

            $newItem = $(options.template.replace(/%text%/g,
            $('<b/>').text(text).html()).replace(/%value%/g, value)).hide();

            $newItem.data('value', value).data('text', text).data('option', $option)
            .addClass(options.classPrefix + '-item');

            $newItem.click(function () {
                if (options.clickRemove)
                    remove($(this));
            });

            if (first && !keypress)
                $selectSingle[0].selectedIndex = 0;

            var callback = function () {
                if (callHandler !== false)
                    options.onAdd(select, value, text);

            };

            if (options.sort && ($item = $list.children().eq(0)).length) {
                while ($item.length && cmp($newItem[0], $item[0]))
                    $item = $item.next();

                show($item.length ? $newItem.insertBefore($item)
                : $newItem.appendTo($list), callback);
            }
            else
                show($newItem.appendTo($list), callback);

            $(select).empty();

            $list.children().each(function () {
                $(select).append($("<option/>").attr({ value: $(this).data('value'),
                    selected: "selected"
                }));
            });

            checkValidation();
        }

        function remove($item, callHandler) {
            hide($item, function () {
                var value = $(this).data('value'),
                text = $(this).data('text');

                if ($(this).data('option'))
                    $(this).data('option').removeAttr('disabled')
                    .removeData('disabled');

                $(this).remove();

                $(select).find("option[value=\"" + value + "\"]").remove();

                checkValidation();

                if (callHandler !== false)
                    options.onRemove(select, value, text);
            });
        }

        function checkValidation() {
            if (select.form && typeof ($(select.form).validate) == "function" &&
                  $(select).add($selectSingle).hasClass($(select.form)
                          .validate().settings.errorClass))
                $(select.form).validate().element(select);
        }

        this.val = function () {
            return $(select).val();
        };

        this.add = function (value, text) {
            add(value, text);
        };

        this.remove = function (value) {
            $list.children().each(function () {
                if ($(this).data('value') == value)
                    remove($(this));
            });
        };

        this.setOptions = function (newOptions) {
            var sort = newOptions.sort && newOptions.sort != options.sort;

            options = $.extend(options, newOptions);

            if (sort) {
                var items = [];
                $list.children().each(function () {
                    items[items.length] = $(this).data('value')
                    items[items.length] = $(this).data('text');
                });
                $list.empty();
                for (var i = 0; i < items.length; i += 2)
                    add(items[i], items[i + 1], false);
            }
        };

        this.setOptions(options = $.extend({
            addAnimate: true,
            classPrefix: 'selectlist',
            clickRemove: true,
            removeAnimate: true,
            template: '<li>%text%</li>',
            onAdd: function () { },
            onRemove: function () { }
        }, options));

        $selectSingle = $(select).clone();
        $selectSingle.removeAttr('id').removeAttr('name')
        .addClass(options.classPrefix + '-select').insertAfter($(select));
        $(select).empty().hide();

        ($list = $(options.list || $("<ul/>").insertAfter($selectSingle)))
        .addClass(options.classPrefix + '-list');

        $selectSingle.find(':selected').each(function () {
            add($(this), null, false);
        });

        $selectSingle.removeAttr('multiple');
        $selectSingle.get(0).removeAttribute('size');

        if ($selectSingle.attr("title")) {
            $selectSingle.prepend($("<option/>")
                .text($selectSingle.attr("title")));
            first = 1;
            $selectSingle[0].selectedIndex = 0;
        }

        keyEvent = $.browser.msie || $.browser.safari ? 'keydown' : 'keypress';

        $selectSingle.bind(keyEvent, function (event) {
            keypress = true;

            if ((event.keyCode || event.which) == 13) {
                enter = true;
                $selectSingle.change();
                keypress = true;
                return false;
            }
        })
    .change(function () {
        if (!keypress && !click) return;
        change = true;
        $option = $selectSingle.find("option:selected");
        if (!$option.data("disabled") && (!keypress || enter))
            add($option);

        if (keypress)
            keypress = change = click = false;

        enter = false;
    })
    .mousedown(function () {
        click = true;
    });

        $selectSingle.find('option').click(function (event) {
            if ($.browser.mozilla && event.pageX >= $selectSingle.offset().left &&
                event.pageX <= $selectSingle.offset().left +

                    $selectSingle.outerWidth() &&
                event.pageY >= $selectSingle.offset().top &&
                event.pageY <= $selectSingle.offset().top +

                    $selectSingle.outerHeight())
                return false;

            click = true;

            if (!($(this).attr('disabled') || $(this).data('disabled') || keypress
                || change))

                add($(this));

            if (!keypress)
                change = click = false;

            return false;
        });

        ready = true;
    };

    $.fn.selectList = function (options) {
        options = options || {};

        this.filter('select').each(function () {
            if ($(this).data('selectList'))
                $(this).data('selectList').setOptions(options);
            else
                $(this).data('selectList', new $.selectList(this, options));
        });

        if (options.instance)
            return this.filter('select').data('selectList');

        return this;
    };
    
// Put plugins inside here    
})(jQuery);

// usage: log('inside coolFunc',this,arguments);
// paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
window.log = function(){
  log.history = log.history || [];   // store logs to an array for reference
  log.history.push(arguments);
  if(this.console){
    console.log( Array.prototype.slice.call(arguments) );
  }
};

// catch all document.write() calls
(function(doc){
  var write = doc.write;
  doc.write = function(q){ 
    log('document.write(): ',arguments); 
    if (/docwriteregexwhitelist/.test(q)) write.apply(doc,arguments);  
  };
})(document);

/**
* nimbleLoader 1.0 - Display loading bar where you want with ease
* Version 1.0
* @requires    jQuery v1.3.2 (tested with jquery 1.5.2)
* @description Display a loading bar in whatever block element you want
*
*
***********************************************************************************************************************
*                                                                                                          Use case ? *
***********************************************************************************************************************
*
* Most of the time the nimbleLoader is used when sending ajax request, to warn users that there is something
* happening in the page :
* - A form submission is being performed
* - Update a block content by getting information with ajax request
* - Upload / Download a file
* - ...
*
***********************************************************************************************************************
*                                                                                                  How to configure ? *
***********************************************************************************************************************
*
* 1- Choose your params
* @option   String          loaderClass         (optional)  default:"loading_bar"  | CSS class for the element which will display your loading bar (string)
* @option   Boolean         debug               (optional)  default:false          | useful to display debug info (boolean)
* @option   String/Numeric  speed               (optional)  default:"fast"         | The speed you want tour loading bar to appear/disappear (numeric or 'slow', 'fast'...)
* @option   String/Numeric  needRelativeParent  (optional)  default:true           | Automatically set the CSS property "position" of the loading bar parent to "relative"
*
*    Example : 
*    var params = {
*      loaderClass        : "loading_bar",
*      debug              : false,
*      speed              : 'fast',
*      needRelativeParent : true
*    }
*
* 2- Set your params
*
*    2.1 Global way
*      $.fn.nimbleLoader.setSettings(params);
*
*    2.2 Specific way
*      $("#myDiv").nimbleLoader("show", otherParams);
*
* 3- Don't forget to set the css of your loading bar : see the demo to have an example (style/loader.css)
* 
*
* 
***********************************************************************************************************************
*                                                                                                        How to use ? *
***********************************************************************************************************************
*
* => Showing a loading bar in <div id="myDiv"></div>
* $("#myDiv").nimbleLoader("show");
*
* => Hiding the loading bar
* $("#myDiv").nimbleLoader("hide");
*
*/

if (jQuery) (function ($) {

    // Extend JQuery function : adding nimbleLoader
    $.extend($.fn, {

        nimbleLoader: function (method, options) {

            /*************************************************************************
            *  Plugin Methods
            ************************************************************************/

            // Clone the global settings. $.extend is needed : we extend a new object with global settings
            var settings = $.extend(true, {}, $.fn.nimbleLoader.settings);

            // Catch settings given in parameters
            if (options) { jQuery.extend(settings, options); }

            // Function to init the loader
            function init($nimbleLoader, settings) {
                var loader = new LoadingHandlerGenerator($nimbleLoader, settings);
                $nimbleLoader.data("loader", loader);
            }

            // Function to show the loading bar
            var show = function () {

                return this.each(function () {
                    var $nimbleLoader = $(this);
                    if ($nimbleLoader.data("loader", loader) !== undefined) {
                        var loader = $nimbleLoader.data("loader", loader);
                        loader.showLoading();
                    }
                    else {
                        init($nimbleLoader, settings);
                        $nimbleLoader.nimbleLoader('show');
                    }
                });
            };

            // Function to hide the loading bar
            var hide = function () {
                return this.each(function () {
                    var $nimbleLoader = $(this);
                    if ($nimbleLoader.data("loader", loader) !== undefined) {
                        var loader = $nimbleLoader.data("loader", loader);
                        loader.hideLoading();
                    }
                });
            };

            var methods = {
                show: show,
                hide: hide
            };

            /*************************************************************************
            *  Execution when calling .nimbleLoader()
            ************************************************************************/
            if (methods[method]) {
                return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
            }
            else if (!method) {
                return methods.show.apply(this, Array.prototype.slice.call(arguments, 1));
            }
            else {
                if (window && window.console) {
                    console.log("[jquery.nimble.loader] -> no method '" + method + "' to apply ");
                }
                return false;
            }

            /**
            * Closure function which define a loading bar element
            */
            function LoadingHandlerGenerator($parentSelector, params) {

                /**
                * Vars init
                * $loadingBar         : the loading bar JQuery element
                * debug               : debug option to display in console how many times the loading bar has been called
                * speed               : animation speed when showing/hiding the loading bar
                * needRelativeParent  : true if your loader has an absolute position : so you need its parent relative
                * countNbCall         : the counter
                */

                var $loadingBar;
                var debug = params.debug;
                var speed = params.speed;
                var needRelativeParent = params.needRelativeParent;
                var countNbCall = 0;
                var waitForAnimation = {
                    isAnimated: false,
                    callStack: []
                };


                // Init the loader : set html and place it
                function initLoading() {

                    // If the loader doesn't exists, we create and init it
                    if (!$loadingBar) {

                        // Define loading bar element
                        $loadingBar = $('<div></div>').addClass(params.loaderClass);

                        // Prepend the loading bar in its parent
                        if ($parentSelector !== undefined && $parentSelector.length) {
                            $parentSelector.prepend($loadingBar);
                        }

                        // Set the css "position" property as "relative" of the loader if needed
                        if (needRelativeParent) {
                            $parentSelector.css("position", "relative");
                        }
                    }
                }

                // Log counter element in the loading bar : useful to show the number of time a loading bar has been call
                function logCounter(nbCall) {
                    if (window && window.console) {
                        var idAttr = $parentSelector.attr("id");
                        var classAttr = $parentSelector.attr("class");
                        var params = [];
                        if (idAttr != "") { params.push("#" + idAttr); }
                        if (classAttr != "") { params.push("." + classAttr); }
                        console.log("[jquery.nimble.loader] -> $(" + params.join(" ") + ").logCounter : " + nbCall);
                    }
                }

                // Decrease the call counter and change the debug display if needed
                function decreaseCounter() {
                    var ret = -1;
                    if (countNbCall > 0) {
                        countNbCall--;
                        ret = countNbCall;
                    }
                    if (debug) { logCounter(ret); }
                    return ret;
                }

                // Increase the call counter and change the debug display if needed
                function increaseCounter() {
                    countNbCall++;
                    if (debug) { logCounter(countNbCall); }
                    return countNbCall;
                }

                // Check if there is an action to do in the callStack and do the one of the top of the stack
                function callStack() {
                    if (waitForAnimation.callStack.length > 0) {
                        if (!waitForAnimation.isAnimated) {
                            var actionToDo = waitForAnimation.callStack.pop();

                            if (actionToDo == "hideLoading") {
                                processHide();
                            }
                            else if (actionToDo == "showLoading") {
                                processShow();
                            }
                        }
                    }
                }

                function showLoading() {
                    unshiftAction("showLoading");
                }
                function hideLoading() {
                    unshiftAction("hideLoading");
                }

                function unshiftAction(action) {
                    waitForAnimation.callStack.unshift(action);
                    callStack();
                }

                // Show the loading bar element
                function processShow() {
                    if (increaseCounter() == 1) { // Check if we have to show the loader it's the first
                        initLoading();

                        // We set a param to know that the animation to hide has begin
                        waitForAnimation.isAnimated = true;
                        $loadingBar.fadeIn(speed, function () {

                            // We set a param to know that the animation to show is finished
                            waitForAnimation.isAnimated = false;

                            // During destroying, calls can be made to show the loader. So we let's the loader disappear and then we show it again
                            callStack();

                        });
                    }
                    else {
                        callStack();
                    }
                }

                // Hide the loading bar element
                function processHide() {
                    // Check if we have to destroy the loader (it happens when the counter is equal to 0)
                    if (decreaseCounter() == 0) { // If countNbCall == 0 decreaseCounter() returns -1

                        // We set a param to know that the animation to hide has begin
                        waitForAnimation.isAnimated = true;

                        // We animate the loader to make it disappear
                        $loadingBar.fadeOut(speed, function () {

                            // We set a param to know that the animation to hide is finished
                            waitForAnimation.isAnimated = false;

                            // We destroy the loader
                            destroyLoading();

                            // During destroying, calls can be made to show the loader. So we let's the loader disappear and then we show it again
                            callStack();
                        });
                    }
                    else {
                        callStack();
                    }
                }

                // Destroy the loading bar element
                function destroyLoading() {
                    // Remove the HTML element
                    $loadingBar.remove();
                    // Re-init the loading bar to force a re-initialisation at its next call
                    $loadingBar = undefined;
                }

                // Body of the closure function
                return {
                    showLoading: showLoading,
                    hideLoading: hideLoading,
                    init: initLoading
                };

            }

        }
    });

    $.extend($.fn.nimbleLoader, {
        settings: {
            loaderClass: "loading_bar",
            debug: false,
            speed: 'fast',
            needRelativeParent: true
        },
        setSettings: function (options) {
            $.extend($.fn.nimbleLoader.settings, options);
        }
    });

})(jQuery);




