(function ($, undefined) {
    /*
     * Unobtrusive scripting adapter for jQuery, largely taken from
     * the wonderful https://github.com/rails/jquery-ujs
     *
     *
     * Released under the MIT license
     *
     */
    'use strict';
    if ($.web2py !== undefined) {
        $.error('web2py.js has already been loaded!');
    }

    var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
    var animateIn = 'fadeIn';
    // var animateIn = 'slideDown';

    String.prototype.reverse = function () {
        return this.split('').reverse().join('');
    };
    var web2py;

    $.web2py = web2py = {

        isUndefined: function (obj) {
            /* grabbed from underscore.js */
            return obj === void 0;
        },
        popup: function (url) {
            /* popup a window */
            var newwindow = window.open(url, 'name', 'height=400,width=600');
            if (window.focus) newwindow.focus();
            return false;
        },
        collapse: function (id) {
            /* toggle an element */
            $('#' + id).slideToggle();
        },
        fade: function (id, value) {
            /*fade something*/
            if (value > 0) $('#' + id).hide().fadeIn('slow');
            else $('#' + id).show().fadeOut('slow');
        },
        ajax: function (u, s, t, options) {
            /*simple ajax function*/

            // set options default value
            options = typeof options !== 'undefined' ? options : {};

            var query = '';
            if (typeof s == 'string') {
                var d = $(s).serialize();
                if (d) {
                    query = d;
                }
            } else {
                var pcs = [];
                if (s !== null && !web2py.isUndefined(s))
                    for (var i = 0; i < s.length; i++) {
                        var q = $('[name=' + s[i] + ']').serialize();
                        if (q) {
                            pcs.push(q);
                        }
                    }
                if (pcs.length > 0) {
                    query = pcs.join('&');
                }
            }

            // default success action
            var success_function = function (msg) {
                if (t) {
                    if (t == ':eval') eval(msg);
                    else if (typeof t == 'string') $('#' + t).html(msg);
                    else t(msg);
                }
            };

            // declare success actions as array
            var success = [success_function];

            // add user success actions
            if ($.isArray(options.done)){
                success = $.merge(success, options.done);
            } else {
                success.push(options.done);
            }

            // default jquery ajax options
            var ajax_options = {
                type: 'POST',
                url: u,
                data: query,
                success: success
            };

            //remove custom "done" option if exists
            delete options.done;

            // merge default ajax options with user custom options
            for (var attrname in options) {
                    ajax_options[attrname] = options[attrname];
            }

            // call ajax function
            $.ajax(ajax_options);
        },
        ajax_fields: function (target) {
            /*
             *this attaches something to a newly loaded fragment/page
             * Ideally all events should be bound to the document, so we can avoid calling
             * this over and over... all will be bound to the document
             */
            /*adds btn class to buttons*/
            $('button:not([class^="btn"])', target).addClass('btn');
            $(
                'form input[type="submit"]:not([class^="btn"]), form input[type="button"]:not([class^="btn"])',
                target).addClass('btn');
            /* javascript for PasswordWidget*/
            $('input[type=password][data-w2p_entropy]', target).each(function () {
                web2py.validate_entropy($(this));
            });
            /* javascript for ListWidget*/
            $('ul.w2p_list', target).each(function () {
                function pe(ul, e) {
                    var new_line = ml(ul);
                    rel(ul);
                    if ($(e.target).parent().is(':visible')) {
                        /* make sure we didn't delete the element before we insert after */
                        new_line.insertAfter($(e.target).parent());
                    } else {
                        /* the line we clicked on was deleted, just add to end of list */
                        new_line.appendTo(ul);
                    }
                    new_line.find(':text').focus();
                    return false;
                }

                function rl(ul, e) {
                    if ($(ul).children().length > 1) {
                        /* only remove if we have more than 1 item so the list is never empty */
                        $(e.target).parent().remove();
                    }
                }

                function ml(ul) {
                    /* clone the first field */
                    var line = $(ul).find('li:first').clone(true);
                    line.find(':text').val('');
                    return line;
                }

                function rel(ul) {
                    /* keep only as many as needed*/
                    $(ul).find('li').each(function () {
                        var trimmed = $.trim($(this.firstChild).val());
                        if (trimmed === '') $(this).remove();
                        else $(this.firstChild).val(trimmed);
                    });
                }
                var ul = this;
                $(ul).find(':text').after('<a href="#">+</a>&nbsp;<a href="#">-</a>').keypress(
                    function (e) {
                        return (e.which == 13) ? pe(ul, e) : true;
                    }).next().click(function (e) {
                    pe(ul, e);
                    e.preventDefault();
                }).next().click(function (e) {
                    rl(ul, e);
                    e.preventDefault();
                });
            });
        },
        ajax_init: function (target) {
            /*called whenever a fragment gets loaded */
            $('.w2p_hidden', target).hide();
            web2py.manage_errors(target);
            web2py.ajax_fields(target);
            web2py.show_if_handler(target);
            web2py.component_handler(target);
        },
        /* manage errors in forms */
        manage_errors: function (target) {
            $('div.error', target).hide()[animateIn]('slow');
        },
        after_ajax: function (xhr) {
            /* called whenever an ajax request completes */
            var command = xhr.getResponseHeader('web2py-component-command');
            var flash = xhr.getResponseHeader('web2py-component-flash');
            if (command !== null) {
                eval(decodeURIComponent(command));
            }
            if (flash) {
                web2py.flash(decodeURIComponent(flash));
            }
        },
        event_handlers: function () {
            /*
             * This is called once for page
             * Ideally it should bound all the things that are needed
             * and require no dom manipulations
             */
            var doc = $(document);
            doc.on('click', '.w2p_flash', function (event) {
                event.preventDefault();
                var t = $(this);
                if (t.css('top') == '0px') t.slideUp('slow');
                else t.fadeOut();
            });
            doc.on('keyup', 'input.integer', function () {
                var nvalue = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse();
                if (this.value != nvalue) this.value = nvalue;
            });
            doc.on('keyup', 'input.double, input.decimal', function () {
                var nvalue = this.value.reverse().replace(
                    /[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g, '').reverse();
                if (this.value != nvalue) this.value = nvalue;
            });
            var confirm_message = !web2py.isUndefined(w2p_ajax_confirm_message) ? w2p_ajax_confirm_message :
                'Are you sure you want to delete this object?';
            doc.on('click', 'input[type="checkbox"].delete', function () {
                if (this.checked)
                    if (!web2py.confirm(confirm_message)) this.checked = false;
            });
            var datetime_format = !web2py.isUndefined(w2p_ajax_datetime_format) ? w2p_ajax_datetime_format :
                '%Y-%m-%d %H:%M:%S';
            doc.on('click', 'input.datetime', function () {
                var tformat = $(this).data('w2p_datetime_format');
                var active = $(this).data('w2p_datetime');
                var format = !web2py.isUndefined(tformat) ? tformat : datetime_format;
                if (active === undefined) {
                    Calendar.setup({
                        inputField: this,
                        ifFormat: format,
                        showsTime: true,
                        timeFormat: '24'
                    });
                    $(this).attr('autocomplete', 'off');
                    $(this).data('w2p_datetime', 1);
                    $(this).trigger('click');
                }
            });
            var date_format = !web2py.isUndefined(w2p_ajax_date_format) ? w2p_ajax_date_format : '%Y-%m-%d';
            doc.on('click', 'input.date', function () {
                var tformat = $(this).data('w2p_date_format');
                var active = $(this).data('w2p_date');
                var format = !web2py.isUndefined(tformat) ? tformat : date_format;
                if (active === undefined) {
                    Calendar.setup({
                        inputField: this,
                        ifFormat: format,
                        showsTime: false
                    });
                    $(this).data('w2p_date', 1);
                    $(this).attr('autocomplete', 'off');
                    $(this).trigger('click');
                }
            });
            doc.on('focus', 'input.time', function () {
                var active = $(this).data('w2p_time');
                if (web2py.isUndefined(active)) {
                    $(this).timeEntry({
                        spinnerImage: ''
                    }).attr('autocomplete', 'off');
                    $(this).data('w2p_time', 1);
                }
            });
            /* help preventing double form submission for normal form (not LOADed) */
            $(doc).on('submit', 'form', function (e) {
                var submit_buttons = $(this).find(web2py.formInputClickSelector);
                submit_buttons.each(function() {
                    web2py.disableElement($(this));
                })
                /* safeguard in case the form doesn't trigger a refresh,
                see https://github.com/web2py/web2py/issues/1100 */
                setTimeout(function () {
                    submit_buttons.each(function() {
                        web2py.enableElement($(this));
                    });
                }, 5000);
            });
            doc.ajaxSuccess(function (e, xhr) {
                var redirect = xhr.getResponseHeader('web2py-redirect-location');
                if (redirect !== null) {
                    if (!redirect.endsWith('#')) {
                        window.location.href = redirect;
                    } else {
                        window.location.reload();
                    }
                }
                /* run this here only if this Ajax request is NOT for a web2py component. */
                if (xhr.getResponseHeader('web2py-component-content') === null) {
                    web2py.after_ajax(xhr);
                }
            });

            doc.ajaxError(function (e, xhr, settings, exception) {
                /*personally I don't like it.
                 *if there's an error it it flashed and can be removed
                 *as any other message
                 *doc.off('click', '.w2p_flash')
                 */
                switch (xhr.status) {
                case 500:
                    web2py.flash(ajax_error_500);
                }
            });

        },
        trap_form: function (action, target) {
            /* traps any LOADed form */
            $('#' + target + ' form').each(function () {
                var form = $(this);
                if (form.hasClass('no_trap')) {
                    return;
                }

                var w2p_target = $(this).attr('data-w2p_target');
                if (web2py.isUndefined(w2p_target) || w2p_target === false) {
                    form.attr('data-w2p_target', target);
                } else {
                    target = w2p_target;
                }

                var url = form.attr('action');
                if ((url === '') || (url === '#') || web2py.isUndefined(url)) {
                    /* form has no action. Use component url. */
                    url = action;
                }

                form.submit(function (e) {
                    web2py.disableElement(form.find(web2py.formInputClickSelector));
                    web2py.hide_flash();

                    var formData;
                    if (FORMDATA_IS_SUPPORTED) {
                        formData = new FormData(form[0]); // Allows file uploads.
                    } else {
                        formData = form.serialize(); // Fallback for older browsers.
                    }
                    web2py.ajax_page('post', url, formData, target);

                    e.preventDefault();
                });
                form.on('click', web2py.formInputClickSelector, function (e) {
                    e.preventDefault();
                    var input_name = $(this).attr('name');
                    if (!web2py.isUndefined(input_name)) {
                        $('<input type="hidden" />').attr('name', input_name)
                            .attr('value', $(this).val()).appendTo(form);
                    }
                    form.trigger('submit');
                });
            });
        },
        ajax_page: function (method, action, data, target, element) {
            /* element is a new parameter, but should be put be put in front */
            if (web2py.isUndefined(element)) element = $(document);
            /* if target is not there, fill it with something that there isn't in the page*/
            if (web2py.isUndefined(target) || target === '') target = 'w2p_none';

            /* processData and contentType must be set to false when passing a FormData
               object to jQuery.ajax. */
            var isFormData = Object.prototype.toString.call(data) === '[object FormData]';
            var contentType = isFormData ? false : 'application/x-www-form-urlencoded; charset=UTF-8';
            if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
                $.ajax({
                    'type': method,
                    'url': action,
                    'data': data,
                    'processData': !isFormData,
                    'contentType': contentType,
                    'xhr': function() {
                        var xhr = new window.XMLHttpRequest();

                        xhr.upload.addEventListener("progress", function(evt) {
                            if (evt.lengthComputable) {
                                var percentComplete = evt.loaded / evt.total;
                                percentComplete = parseInt(percentComplete * 100);
                                web2py.fire(element, 'w2p:uploadProgress', [percentComplete], target);

                                if (percentComplete === 100) {
                                    web2py.fire(element, 'w2p:uploadComplete', [], target);
                                }

                            }
                        }, false);

                        return xhr;
                    },
                    'beforeSend': function (xhr, settings) {
                        xhr.setRequestHeader('web2py-component-location', document.location);
                        xhr.setRequestHeader('web2py-component-element', target);
                        web2py.fire(element, 'w2p:componentBegin', [xhr, settings], target);
                        return web2py.fire(element, 'ajax:beforeSend', [xhr, settings], target); //test a usecase, should stop here if returns false
                    },
                    'success': function (data, status, xhr) {
                        /*bummer for form submissions....the element is not there after complete
                         *because it gets replaced by the new response....
                         */
                        web2py.fire(element, 'ajax:success', [data, status, xhr], target);
                    },
                    'error': function (xhr, status, error) {
                        /*bummer for form submissions....in addition to the element being not there after
                         *complete because it gets replaced by the new response, standard form
                         *handling just returns the same status code for good and bad
                         *form submissions (i.e. that triggered a validator error)
                         */
                        web2py.fire(element, 'ajax:error', [xhr, status, error], target);
                    },
                    'complete': function (xhr, status) {
                        web2py.fire(element, 'ajax:complete', [xhr, status], target);
                        web2py.updatePage(xhr, target); /* Parse and load the html received */
                        web2py.trap_form(action, target);
                        web2py.ajax_init('#' + target);
                        web2py.after_ajax(xhr);
                        web2py.fire(element, 'w2p:componentComplete', [xhr, status], target); // Let us know the component is finished loading
                    }
                });
            }
        },
        component: function (action, target, timeout, times, el) {
            /* element is a new parameter, but should be put in front */
            $(function () {
                var jelement = $('#' + target);
                var element = jelement.get(0);
                var statement = 'jQuery("#' + target + '").get(0).reload();';
                element.reload = function () {
                    /* Continue if times is Infinity or
                     * the times limit is not reached
                     */
                    if (element.reload_check()) {
                        web2py.ajax_page('get', action, null, target, el);
                    }
                };
                /* Method to check timing limit */
                element.reload_check = function () {
                    if (jelement.hasClass('w2p_component_stop')) {
                        clearInterval(this.timing);
                        return false;
                    }
                    if (this.reload_counter == Infinity) {
                        return true;
                    } else {
                        if (!isNaN(this.reload_counter)) {
                            this.reload_counter -= 1;
                            if (this.reload_counter < 0) {
                                if (!this.run_once) {
                                    clearInterval(this.timing);
                                    return false;
                                }
                            } else {
                                return true;
                            }
                        }
                    }
                    return false;
                };
                if (!isNaN(timeout)) {
                    element.timeout = timeout;
                    element.reload_counter = times;
                    if (times > 1) {
                        /* Multiple or infinite reload
                         * Run first iteration
                         */
                        web2py.ajax_page('get', action, null, target, el);
                        element.run_once = false;
                        element.timing = setInterval(statement, timeout);
                        element.reload_counter -= 1;
                    } else if (times == 1) {
                        /* Run once with timeout */
                        element.run_once = true;
                        element.setTimeout = setTimeout;
                        element.timing = setTimeout(statement, timeout);
                    }
                } else {
                    /* run once (no timeout specified) */
                    element.reload_counter = Infinity;
                    web2py.ajax_page('get', action, null, target, el);
                }
            });
        },
        updatePage: function (xhr, target) {
            var t = $('#' + target);
            var html = $.parseHTML(xhr.responseText, document, true);
            var title_elements = $(html).filter('title').add($(html).find('title'));
            var title = title_elements.last().text();
            if (title) {
                title_elements.remove(); /* Remove any title elements from the response */
                document.title = $.trim(title); /* Set the new document title */
            }
            var content = xhr.getResponseHeader('web2py-component-content');
            if (content == 'prepend') t.prepend(xhr.responseText);
            else if (content == 'append') t.append(xhr.responseText);
            else if (content != 'hide') t.html(html);
        },
        calc_entropy: function (mystring) {
            /* calculate a simple entropy for a given string */
            var csets = new Array(
                'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
                '0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/',
                '0123456789abcdefghijklmnopqrstuvwxyz');
            var score = 0,
                other = {},
                seen = {},
                lastset = null,
                mystringlist = mystring.split('');
            for (var i = 0; i < mystringlist.length; i++) { /* classify this character */
                var c = mystringlist[i],
                    inset = 5;
                for (var j = 0; j < csets.length; j++)
                    if (csets[j].indexOf(c) != -1) {
                        inset = j;
                        break;
                    }
                    /*calculate effect of character on alphabet size */
                if (!(inset in seen)) {
                    seen[inset] = 1;
                    score += csets[inset].length;
                } else if (!(c in other)) {
                    score += 1;
                    other[c] = 1;
                }
                if (inset != lastset) {
                    score += 1;
                    lastset = inset;
                }
            }
            var entropy = mystring.length * Math.log(score) / 0.6931471805599453;
            return Math.round(entropy * 100) / 100;
        },
        validate_entropy: function (myfield, req_entropy) {
            if (!web2py.isUndefined(myfield.data('w2p_entropy'))) req_entropy = myfield.data('w2p_entropy');
            var validator = function () {
                var v = (web2py.calc_entropy(myfield.val()) || 0) / req_entropy;
                var r = 0,
                    g = 0,
                    b = 0,
                    rs = function (x) {
                        return Math.round(x * 15).toString(16);
                    };
                if (v <= 0.5) {
                    r = 1.0;
                    g = 2.0 * v;
                } else {
                    r = (1.0 - 2.0 * (Math.max(v, 0) - 0.5));
                    g = 1.0;
                }
                var color = '#' + rs(r) + rs(g) + rs(b);
                myfield.css('background-color', color);
                var entropy_callback = myfield.data('entropy_callback');
                if (entropy_callback) entropy_callback(v);
            };
            if (!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator)
                .addClass('entropy_check');
        },
        web2py_websocket: function (url, onmessage, onopen, onclose) {
            if ('WebSocket' in window) {
                var ws = new WebSocket(url);
                ws.onopen = onopen ? onopen : (function () {});
                ws.onmessage = onmessage;
                ws.onclose = onclose ? onclose : (function () {});
                return true; /* supported */
            } else return false; /* not supported */
        },
        /* new from here */
        /* Form input elements bound by web2py.js */
        formInputClickSelector: 'input[type=submit], input[type=image], button[type=submit], button:not([type])',
        /* Form input elements disabled during form submission */
        disableSelector: 'input, button, textarea, select',
        /* Form input elements re-enabled after form submission */
        enableSelector: 'input:disabled, button:disabled, textarea:disabled, select:disabled',
        /* Triggers an event on an element and returns false if the event result is false */
        fire: function (obj, type, data, target) {
            var event = $.Event(type, {
                'containerTarget': $('#' + target)[0]
            });
            obj.trigger(event, data);
            return event.result !== false;
        },
        /* Helper function, needed to provide consistent behavior in IE */
        stopEverything: function (e) {
            $(e.target).trigger('w2p:everythingStopped');
            e.stopImmediatePropagation();
            return false;
        },
        confirm: function (message) {
            return confirm(message);
        },
        /* replace element's html with the 'data-disable-with' after storing original html
         * and prevent clicking on it */
        disableElement: function (el) {
            if (!web2py.isUndefined(el.data('w2p_disable'))) {
                return false;
            }
            el.addClass('disabled');
            var method = el.is('input') ? 'val' : 'html';
            //method = el.attr('name') ? 'html' : 'val';
            var disable_with_message = (!web2py.isUndefined(w2p_ajax_disable_with_message)) ?
                w2p_ajax_disable_with_message : 'Working...';
            /*store enabled state if not already disabled */
            if (web2py.isUndefined(el.data('w2p_enable_with'))) {
                el.data('w2p_enable_with', el[method]());
            }
            /*if you don't want to see "working..." on buttons, replace the following
             * two lines with this one
             * el.data('w2p_disable_with', el[method]());
             */
            if ((el.data('w2p_disable_with') == 'default') || (web2py.isUndefined(el.data(
                    'w2p_disable_with')))) {
                el.data('w2p_disable_with', disable_with_message);
            }

            /* set to disabled state*/
            el[method](el.data('w2p_disable_with'));

            el.bind('click.w2pDisable', function (e) { /* prevent further clicking*/
                return web2py.stopEverything(e);
            });
        },

        /* restore element to its original state which was disabled by 'disableElement' above*/
        enableElement: function (el) {
            var method = el.is('input') ? 'val' : 'html';
            if (!web2py.isUndefined(el.data('w2p_enable_with'))) {
                /* set to old enabled state */
                el[method](el.data('w2p_enable_with'));
                el.removeData('w2p_enable_with');
            }
            el.removeClass('disabled');
            el.unbind('click.w2pDisable');
        },
        /*convenience wrapper, internal use only */
        simple_component: function (action, target, element) {
            web2py.component(action, target, 0, 1, element);
        },
        /*helper for flash messages*/
        flash: function (message, status) {
            var flash = $('.w2p_flash');
            web2py.hide_flash();
            flash.text(message).addClass(status);
            if (flash.html()) flash.append('<span id="closeflash"> &times; </span>')[animateIn]();
        },
        hide_flash: function () {
            $('.w2p_flash').fadeOut(0).html('');
        },
        show_if_handler: function (target) {
            var triggers = {};
            var show_if = function () {
                var t = $(this);
                var id = t.attr('id');
                t.attr('value', t.val());
                for (var k = 0; k < triggers[id].length; k++) {
                    var dep = $('#' + triggers[id][k], target);
                    var tr = $('#' + triggers[id][k] + '__row', target);
                    if (t.is(dep.attr('data-show-if'))) tr[animateIn]();
                    else tr.hide();
                }
            };
            $('[data-show-trigger]', target).each(function () {
                var name = $(this).attr('data-show-trigger');
                // The field exists only when creating/editing a row
                if ($('#' + name).length) {
                    if (!triggers[name]) triggers[name] = [];
                    triggers[name].push($(this).attr('id'));
                }
            });
            for (var name in triggers) {
                $('#' + name, target).change(show_if).keyup(show_if);
                show_if.call($('#' + name, target));
            }
        },
        component_handler: function (target) {
            $('div[data-w2p_remote]', target).each(function () {
                var remote, times, timeout, target;
                var el = $(this);
                remote = el.data('w2p_remote');
                times = el.data('w2p_times');
                timeout = el.data('w2p_timeout');
                target = el.attr('id');
                web2py.component(remote, target, timeout, times, $(this));
            });
        },
        a_handler: function (el, e) {
            e.preventDefault();
            var method = el.data('w2p_method');
            var action = el.attr('href');
            var target = el.data('w2p_target');
            var confirm_message = el.data('w2p_confirm');

            var pre_call = el.data('w2p_pre_call');
            if (!web2py.isUndefined(pre_call)) {
                eval(pre_call);
            }
            if (confirm_message) {
                if (confirm_message == 'default') {
                    confirm_message = !web2py.isUndefined(w2p_ajax_confirm_message) ?  
                    w2p_ajax_confirm_message : 'Are you sure you want to delete this object?';
                }
                if (!web2py.confirm(confirm_message)) {
                    web2py.stopEverything(e);
                    return;
                }
            }
            if (web2py.isUndefined(target)) {
                if (method == 'GET') {
                    web2py.ajax_page('get', action, [], '', el);
                } else if (method == 'POST') {
                    web2py.ajax_page('post', action, [], '', el);
                }
            } else {
                if (method == 'GET') {
                    web2py.ajax_page('get', action, [], target, el);
                } else if (method == 'POST') {
                    web2py.ajax_page('post', action, [], target, el);
                }
            }
        },
        a_handlers: function () {
            var el = $(document);
            el.on('click', 'a[data-w2p_method]', function (e) {
                web2py.a_handler($(this), e);
            });
            /* removal of element should happen only on success */
            el.on('ajax:success', 'a[data-w2p_method][data-w2p_remove]', function () {
                var el = $(this);
                var toremove = el.data('w2p_remove');
                if (!web2py.isUndefined(toremove)) {
                    toremove = el.closest(toremove);
                    if (!toremove.length) {
                        /*this enables removal of whatever selector if a closest is not found */
                        toremove = $(toremove);
                    }
                    toremove.remove();
                }
            });
            el.on('ajax:beforeSend', 'a[data-w2p_method][data-w2p_disable_with]', function () {
                web2py.disableElement($(this));
            });
            /*re-enable click on completion*/
            el.on('ajax:complete', 'a[data-w2p_method][data-w2p_disable_with]', function () {
                web2py.enableElement($(this));
            });
        },
        /* Disables form elements:
        - Does not disable elements with 'data-w2p_disable' attribute
        - Caches element value in 'w2p_enable_with' data store
        - Replaces element text with value of 'data-w2p_disable_with' attribute
        - Sets disabled property to true
        */
        disableFormElements: function (form) {
            form.find(web2py.disableSelector).each(function () {
                var element = $(this),
                    method = element.is('button') ? 'html' : 'val';
                var disable_with = element.data('w2p_disable_with');
                var disable = element.data('w2p_disable');
                if (!web2py.isUndefined(disable)) {
                    return false;
                }
                if (!element.is(':file')) { // Altering file input values is not allowed.
                    if (web2py.isUndefined(disable_with)) {
                        element.data('w2p_disable_with', element[method]());
                    }
                    if (web2py.isUndefined(element.data('w2p_enable_with'))) {
                        element.data('w2p_enable_with', element[method]());
                    }
                    element[method](element.data('w2p_disable_with'));
                }
                element.prop('disabled', true);
            });
        },

        /* Re-enables disabled form elements:
        - Replaces element text with cached value from 'w2p_enable_with' data store (created in `disableFormElements`)
        - Sets disabled property to false
        */
        enableFormElements: function (form) {
            form.find(web2py.enableSelector).each(function () {
                var element = $(this),
                    method = element.is('button') ? 'html' : 'val';
                if (element.data('w2p_enable_with')) {
                    element[method](element.data('w2p_enable_with'));
                    element.removeData('w2p_enable_with');
                }
                element.prop('disabled', false);
            });
        },
        form_handlers: function () {
            var el = $(document);
            el.on('ajax:beforeSend', 'form[data-w2p_target]', function () {
                web2py.disableFormElements($(this));
            });
            el.on('ajax:complete', 'form[data-w2p_target]', function () {
                web2py.enableFormElements($(this));
            });
        },
        /* Invalidate and force reload of a web2py component
         */
        invalidate: function (target) {
            $('div[data-w2p_remote]', target).each(function () {
                var el = $('#' + $(this).attr('id')).get(0);
                if (!web2py.isUndefined(el.timing)) { // Block triggering regular routines
                    clearInterval(el.timing);
                }
            });
            $.web2py.component_handler(target);
        },
        main_hook: function () {
            var flash = $('.w2p_flash');
            flash.hide();
            if (flash.html()) web2py.flash(flash.html());
            web2py.ajax_init(document);
            web2py.event_handlers();
            web2py.a_handlers();
            web2py.form_handlers();
        }
    };
    /*end of functions */
    /*main hook*/
    $(function () {
        web2py.main_hook();
    });

})(jQuery);

/* compatibility code - start */
ajax = jQuery.web2py.ajax;
web2py_component = jQuery.web2py.component;
web2py_websocket = jQuery.web2py.web2py_websocket;
web2py_ajax_page = jQuery.web2py.ajax_page;
/*needed for IS_STRONG(entropy)*/
web2py_validate_entropy = jQuery.web2py.validate_entropy;
/*needed for crud.search and SQLFORM.grid's search*/
web2py_ajax_fields = jQuery.web2py.ajax_fields;
/*used for LOAD(ajax=False)*/
web2py_trap_form = jQuery.web2py.trap_form;

/*undocumented - rare*/
popup = jQuery.web2py.popup;
collapse = jQuery.web2py.collapse;
fade = jQuery.web2py.fade;

/* internals - shouldn't be needed
web2py_ajax_init = jQuery.web2py.ajax_init;
web2py_event_handlers = jQuery.web2py.event_handlers;
web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/
/* compatibility code - end*/
