diff --git a/applications/admin/static/js/web2py.js b/applications/admin/static/js/web2py.js index e6e62386..2e463733 100644 --- a/applications/admin/static/js/web2py.js +++ b/applications/admin/static/js/web2py.js @@ -19,685 +19,689 @@ $.web2py = web2py = { - popup: function(url) { - /* popup a window */ - 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) { - /*simple ajax function*/ - query = ''; - if(typeof s == "string") { - d = $(s).serialize(); - if(d) { - query = d; - } - } else { - pcs = []; - if(s != null && s != undefined) - for(i = 0; i < s.length; i++) { - q = $("[name=" + s[i] + "]").serialize(); - if(q) { - pcs.push(q); - } + popup: function(url) { + /* popup a window */ + 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) { + /*simple ajax function*/ + query = ''; + if(typeof s == "string") { + d = $(s).serialize(); + if(d) { + query = d; } - if(pcs.length > 0) { - query = pcs.join("&"); - } - } - $.ajax({ - type: "POST", - url: u, - data: query, - success: function(msg) { - if(t) { - if(t == ':eval') eval(msg); - else if(typeof t == 'string') $("#" + t).html(msg); - else t(msg); - } - } - }); - }, - 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', target).addClass('btn'); - $('form input[type="submit"], form input[type="button"]', 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('+ -').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 */ - $('.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().slideDown('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', '.flash', function(e) { - 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 = (typeof w2p_ajax_confirm_message != 'undefined') ? 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 = (typeof w2p_ajax_datetime_format != 'undefined') ? 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 = (typeof tformat != 'undefined') ? 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 = (typeof w2p_ajax_date_format != 'undefined') ? 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 = (typeof tformat != 'undefined') ? 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(active === undefined) { - $(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() { - var submit_button = $(this).find(web2py.formInputClickSelector); - web2py.disableElement(submit_button); - }); - doc.ajaxSuccess(function(e, xhr) { - var redirect = xhr.getResponseHeader('web2py-redirect-location'); - if(redirect !== null) { - window.location = redirect; - }; - /* 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', '.flash') - */ - switch(xhr.status) { - case 500: - web2py.flash(ajax_error_500); - } - }); - - }, - - trap_form: function(action, target) { - /* traps any LOADed form */ - $('#' + target + ' form').each(function(i) { - var form = $(this); - if(form.hasClass('no_trap')) { - return; - } - - form.attr('data-w2p_target', target); - var url = form.attr('action'); - - if((url === "") || (url === "#") || (typeof url === 'undefined')) { - /* form has no action. Use component url. */ - url = action; - } - - form.submit(function(e) { - web2py.disableElement(form.find(web2py.formInputClickSelector)); - web2py.hide_flash(); - web2py.ajax_page('post', url, form.serialize(), target, form); - e.preventDefault(); - }); - }); - }, - - - ajax_page: function(method, action, data, target, element) { - /* element is a new parameter, but should be put be put in front */ - if(element == undefined) element = $(document); - /* if target is not there, fill it with something that there isn't in the page*/ - if(target == undefined || target == '') target = 'w2p_none'; - if(web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */ - $.ajax({ - 'type': method, - 'url': action, - 'data': data, - 'beforeSend': function(xhr, settings) { - xhr.setRequestHeader('web2py-component-location', document.location); - xhr.setRequestHeader('web2py-component-element', 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); - } - }); - } - }, - 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; + } else { + pcs = []; + if(s != null && s != undefined) + for(i = 0; i < s.length; i++) { + q = $("[name=" + s[i] + "]").serialize(); + if(q) { + pcs.push(q); } } + if(pcs.length > 0) { + query = pcs.join("&"); } - 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); + } + $.ajax({ + type: "POST", + url: u, + data: query, + success: function(msg) { + if(t) { + if(t == ':eval') eval(msg); + else if(typeof t == 'string') $("#" + t).html(msg); + else t(msg); + } } - } 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; + }); + }, + 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', target).addClass('btn'); + $('form input[type="submit"], form input[type="button"]', 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; } - /*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; + + 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('+ -').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 */ + $('.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().slideDown('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(inset != lastset) { - score += 1; - lastset = inset; + if(flash) { + web2py.flash(decodeURIComponent(flash)) } - } - var entropy = mystring.length * Math.log(score) / 0.6931471805599453; - return Math.round(entropy * 100) / 100 - }, - validate_entropy: function(myfield, req_entropy) { - if(myfield.data('w2p_entropy') != undefined) 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) + }, + 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', '.flash', function(e) { + 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 = (typeof w2p_ajax_confirm_message != 'undefined') ? 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 = (typeof w2p_ajax_datetime_format != 'undefined') ? 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 = (typeof tformat != 'undefined') ? 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 = (typeof w2p_ajax_date_format != 'undefined') ? 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 = (typeof tformat != 'undefined') ? 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(active === undefined) { + $(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() { + var submit_button = $(this).find(web2py.formInputClickSelector); + web2py.disableElement(submit_button); + }); + doc.ajaxSuccess(function(e, xhr) { + var redirect = xhr.getResponseHeader('web2py-redirect-location'); + if(redirect !== null) { + window.location = redirect; }; - 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); - 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]:not([name]), input[type=image]:not([name]), button[type=submit]:not([name]), button:not([type]):not([name])', - /* 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) { - el.addClass('disabled'); - var method = el.prop('type') == 'submit' ? 'val' : 'html'; - var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working..."; - /*store enabled state if not already disabled */ - if(el.data('w2p:enable-with') === undefined) { - 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') || (el.data('w2p_disable_with') === undefined)) { - el.data('w2p_disable_with', disable_with_message); - } + /* 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); + }; + }); - /* 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.prop('type') == 'submit' ? 'val' : 'html'; - if(el.data('w2p:enable-with') !== undefined) { - /* 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 = $('.flash'); - web2py.hide_flash(); - flash.html(message).addClass(status); - if(flash.html()) flash.append(' × ').slideDown(); - }, - hide_flash: function() { - $('.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.slideDown(); - 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(pre_call != undefined) { - eval(pre_call); - } - if(confirm_message != undefined) { - if(confirm_message == 'default') 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(target == undefined) { - 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(e) { - var el = $(this); - var toremove = el.data('w2p_remove'); - if(toremove != undefined) { - toremove = el.closest(toremove); - if(!toremove.length) { - /*this enables removal of whatever selector if a closest is not found */ - toremove = $(toremove); + 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', '.flash') + */ + switch(xhr.status) { + case 500: + web2py.flash(ajax_error_500); } - toremove.remove(); + }); + + }, + trap_form: function(action, target) { + /* traps any LOADed form */ + $('#' + target + ' form').each(function(i) { + var form = $(this); + if(form.hasClass('no_trap')) { + return; + } + form.attr('data-w2p_target', target); + var url = form.attr('action'); + if((url === "") || (url === "#") || (typeof url === 'undefined')) { + /* form has no action. Use component url. */ + url = action; + } + form.submit(function(e) { + web2py.disableElement(form.find(web2py.formInputClickSelector)); + web2py.hide_flash(); + web2py.ajax_page('post', url, form.serialize(), target, form); + e.preventDefault(); + }); + form.on('click', web2py.formInputClickSelector, function(e) { + e.preventDefault(); + var input_name = $(this).attr('name'); + if(input_name != undefined) { + $('').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(element == undefined) element = $(document); + /* if target is not there, fill it with something that there isn't in the page*/ + if(target == undefined || target == '') target = 'w2p_none'; + if(web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */ + $.ajax({ + 'type': method, + 'url': action, + 'data': data, + 'beforeSend': function(xhr, settings) { + xhr.setRequestHeader('web2py-component-location', document.location); + xhr.setRequestHeader('web2py-component-element', 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); + } + }); } - }); - el.on('ajax:beforeSend', 'a[data-w2p_method][data-w2p_disable_with]', function(e) { - web2py.disableElement($(this)); - }); - /*re-enable click on completion*/ - el.on('ajax:complete', 'a[data-w2p_method][data-w2p_disable_with]', function(e) { - web2py.enableElement($(this)); - }); - }, - /* Disables form elements: - - Caches element value in 'ujs:enable-with' data store + }, + 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(myfield.data('w2p_entropy') != undefined) 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); + 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) { + el.addClass('disabled'); + var method = el.is('button') ? 'html' : 'val'; + //method = el.attr('name') ? 'html' : 'val'; + var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working..."; + /*store enabled state if not already disabled */ + if(el.data('w2p:enable-with') === undefined) { + 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') || (el.data('w2p_disable_with') === undefined)) { + 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('button') ? 'val' : 'html'; + if(el.data('w2p:enable-with') !== undefined) { + /* 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 = $('.flash'); + web2py.hide_flash(); + flash.html(message).addClass(status); + if(flash.html()) flash.append(' × ').slideDown(); + }, + hide_flash: function() { + $('.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.slideDown(); + 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(pre_call != undefined) { + eval(pre_call); + } + if(confirm_message != undefined) { + if(confirm_message == 'default') 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(target == undefined) { + 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(e) { + var el = $(this); + var toremove = el.data('w2p_remove'); + if(toremove != undefined) { + 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(e) { + web2py.disableElement($(this)); + }); + /*re-enable click on completion*/ + el.on('ajax:complete', 'a[data-w2p_method][data-w2p_disable_with]', function(e) { + web2py.enableElement($(this)); + }); + }, + /* Disables form elements: + - Caches element value in 'w2p:enable-with' data store - Replaces element text with value of 'data-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'); - if(disable_with == undefined) { - element.data('w2p_disable_with', element[method]()) - } - if(element.data('w2p:enable-with') === undefined) { - element.data('w2p:enable-with', element[method]()); - } - element[method](element.data('w2p_disable_with')); - element.prop('disabled', 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'); + if(disable_with == undefined) { + element.data('w2p_disable_with', element[method]()) + } + if(element.data('w2p:enable-with') === undefined) { + 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 'ujs:enable-with' data store (created in `disableFormElements`) + /* 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(e) { - web2py.disableFormElements($(this)); - }); - el.on('ajax:complete', 'form[data-w2p_target]', function(e) { - 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(el.timing !== undefined) { // Block triggering regular routines - clearInterval(el.timing); - } - }); - $.web2py.component_handler(target); - }, - main_hook : function() { - var flash = $('.flash'); - flash.hide(); - if(flash.html()) web2py.flash(flash.html()); - web2py.ajax_init(document); - web2py.event_handlers(); - web2py.a_handlers(); - web2py.form_handlers(); + 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(e) { + web2py.disableFormElements($(this)); + }); + el.on('ajax:complete', 'form[data-w2p_target]', function(e) { + 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(el.timing !== undefined) { // Block triggering regular routines + clearInterval(el.timing); + } + }); + $.web2py.component_handler(target); + }, + main_hook: function() { + var flash = $('.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 () { + /*end of functions */ + /*main hook*/ + $(function() { web2py.main_hook(); }); diff --git a/applications/examples/static/js/web2py.js b/applications/examples/static/js/web2py.js index e6e62386..2e463733 100644 --- a/applications/examples/static/js/web2py.js +++ b/applications/examples/static/js/web2py.js @@ -19,685 +19,689 @@ $.web2py = web2py = { - popup: function(url) { - /* popup a window */ - 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) { - /*simple ajax function*/ - query = ''; - if(typeof s == "string") { - d = $(s).serialize(); - if(d) { - query = d; - } - } else { - pcs = []; - if(s != null && s != undefined) - for(i = 0; i < s.length; i++) { - q = $("[name=" + s[i] + "]").serialize(); - if(q) { - pcs.push(q); - } + popup: function(url) { + /* popup a window */ + 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) { + /*simple ajax function*/ + query = ''; + if(typeof s == "string") { + d = $(s).serialize(); + if(d) { + query = d; } - if(pcs.length > 0) { - query = pcs.join("&"); - } - } - $.ajax({ - type: "POST", - url: u, - data: query, - success: function(msg) { - if(t) { - if(t == ':eval') eval(msg); - else if(typeof t == 'string') $("#" + t).html(msg); - else t(msg); - } - } - }); - }, - 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', target).addClass('btn'); - $('form input[type="submit"], form input[type="button"]', 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('+ -').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 */ - $('.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().slideDown('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', '.flash', function(e) { - 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 = (typeof w2p_ajax_confirm_message != 'undefined') ? 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 = (typeof w2p_ajax_datetime_format != 'undefined') ? 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 = (typeof tformat != 'undefined') ? 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 = (typeof w2p_ajax_date_format != 'undefined') ? 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 = (typeof tformat != 'undefined') ? 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(active === undefined) { - $(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() { - var submit_button = $(this).find(web2py.formInputClickSelector); - web2py.disableElement(submit_button); - }); - doc.ajaxSuccess(function(e, xhr) { - var redirect = xhr.getResponseHeader('web2py-redirect-location'); - if(redirect !== null) { - window.location = redirect; - }; - /* 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', '.flash') - */ - switch(xhr.status) { - case 500: - web2py.flash(ajax_error_500); - } - }); - - }, - - trap_form: function(action, target) { - /* traps any LOADed form */ - $('#' + target + ' form').each(function(i) { - var form = $(this); - if(form.hasClass('no_trap')) { - return; - } - - form.attr('data-w2p_target', target); - var url = form.attr('action'); - - if((url === "") || (url === "#") || (typeof url === 'undefined')) { - /* form has no action. Use component url. */ - url = action; - } - - form.submit(function(e) { - web2py.disableElement(form.find(web2py.formInputClickSelector)); - web2py.hide_flash(); - web2py.ajax_page('post', url, form.serialize(), target, form); - e.preventDefault(); - }); - }); - }, - - - ajax_page: function(method, action, data, target, element) { - /* element is a new parameter, but should be put be put in front */ - if(element == undefined) element = $(document); - /* if target is not there, fill it with something that there isn't in the page*/ - if(target == undefined || target == '') target = 'w2p_none'; - if(web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */ - $.ajax({ - 'type': method, - 'url': action, - 'data': data, - 'beforeSend': function(xhr, settings) { - xhr.setRequestHeader('web2py-component-location', document.location); - xhr.setRequestHeader('web2py-component-element', 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); - } - }); - } - }, - 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; + } else { + pcs = []; + if(s != null && s != undefined) + for(i = 0; i < s.length; i++) { + q = $("[name=" + s[i] + "]").serialize(); + if(q) { + pcs.push(q); } } + if(pcs.length > 0) { + query = pcs.join("&"); } - 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); + } + $.ajax({ + type: "POST", + url: u, + data: query, + success: function(msg) { + if(t) { + if(t == ':eval') eval(msg); + else if(typeof t == 'string') $("#" + t).html(msg); + else t(msg); + } } - } 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; + }); + }, + 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', target).addClass('btn'); + $('form input[type="submit"], form input[type="button"]', 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; } - /*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; + + 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('+ -').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 */ + $('.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().slideDown('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(inset != lastset) { - score += 1; - lastset = inset; + if(flash) { + web2py.flash(decodeURIComponent(flash)) } - } - var entropy = mystring.length * Math.log(score) / 0.6931471805599453; - return Math.round(entropy * 100) / 100 - }, - validate_entropy: function(myfield, req_entropy) { - if(myfield.data('w2p_entropy') != undefined) 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) + }, + 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', '.flash', function(e) { + 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 = (typeof w2p_ajax_confirm_message != 'undefined') ? 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 = (typeof w2p_ajax_datetime_format != 'undefined') ? 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 = (typeof tformat != 'undefined') ? 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 = (typeof w2p_ajax_date_format != 'undefined') ? 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 = (typeof tformat != 'undefined') ? 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(active === undefined) { + $(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() { + var submit_button = $(this).find(web2py.formInputClickSelector); + web2py.disableElement(submit_button); + }); + doc.ajaxSuccess(function(e, xhr) { + var redirect = xhr.getResponseHeader('web2py-redirect-location'); + if(redirect !== null) { + window.location = redirect; }; - 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); - 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]:not([name]), input[type=image]:not([name]), button[type=submit]:not([name]), button:not([type]):not([name])', - /* 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) { - el.addClass('disabled'); - var method = el.prop('type') == 'submit' ? 'val' : 'html'; - var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working..."; - /*store enabled state if not already disabled */ - if(el.data('w2p:enable-with') === undefined) { - 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') || (el.data('w2p_disable_with') === undefined)) { - el.data('w2p_disable_with', disable_with_message); - } + /* 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); + }; + }); - /* 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.prop('type') == 'submit' ? 'val' : 'html'; - if(el.data('w2p:enable-with') !== undefined) { - /* 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 = $('.flash'); - web2py.hide_flash(); - flash.html(message).addClass(status); - if(flash.html()) flash.append(' × ').slideDown(); - }, - hide_flash: function() { - $('.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.slideDown(); - 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(pre_call != undefined) { - eval(pre_call); - } - if(confirm_message != undefined) { - if(confirm_message == 'default') 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(target == undefined) { - 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(e) { - var el = $(this); - var toremove = el.data('w2p_remove'); - if(toremove != undefined) { - toremove = el.closest(toremove); - if(!toremove.length) { - /*this enables removal of whatever selector if a closest is not found */ - toremove = $(toremove); + 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', '.flash') + */ + switch(xhr.status) { + case 500: + web2py.flash(ajax_error_500); } - toremove.remove(); + }); + + }, + trap_form: function(action, target) { + /* traps any LOADed form */ + $('#' + target + ' form').each(function(i) { + var form = $(this); + if(form.hasClass('no_trap')) { + return; + } + form.attr('data-w2p_target', target); + var url = form.attr('action'); + if((url === "") || (url === "#") || (typeof url === 'undefined')) { + /* form has no action. Use component url. */ + url = action; + } + form.submit(function(e) { + web2py.disableElement(form.find(web2py.formInputClickSelector)); + web2py.hide_flash(); + web2py.ajax_page('post', url, form.serialize(), target, form); + e.preventDefault(); + }); + form.on('click', web2py.formInputClickSelector, function(e) { + e.preventDefault(); + var input_name = $(this).attr('name'); + if(input_name != undefined) { + $('').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(element == undefined) element = $(document); + /* if target is not there, fill it with something that there isn't in the page*/ + if(target == undefined || target == '') target = 'w2p_none'; + if(web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */ + $.ajax({ + 'type': method, + 'url': action, + 'data': data, + 'beforeSend': function(xhr, settings) { + xhr.setRequestHeader('web2py-component-location', document.location); + xhr.setRequestHeader('web2py-component-element', 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); + } + }); } - }); - el.on('ajax:beforeSend', 'a[data-w2p_method][data-w2p_disable_with]', function(e) { - web2py.disableElement($(this)); - }); - /*re-enable click on completion*/ - el.on('ajax:complete', 'a[data-w2p_method][data-w2p_disable_with]', function(e) { - web2py.enableElement($(this)); - }); - }, - /* Disables form elements: - - Caches element value in 'ujs:enable-with' data store + }, + 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(myfield.data('w2p_entropy') != undefined) 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); + 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) { + el.addClass('disabled'); + var method = el.is('button') ? 'html' : 'val'; + //method = el.attr('name') ? 'html' : 'val'; + var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working..."; + /*store enabled state if not already disabled */ + if(el.data('w2p:enable-with') === undefined) { + 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') || (el.data('w2p_disable_with') === undefined)) { + 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('button') ? 'val' : 'html'; + if(el.data('w2p:enable-with') !== undefined) { + /* 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 = $('.flash'); + web2py.hide_flash(); + flash.html(message).addClass(status); + if(flash.html()) flash.append(' × ').slideDown(); + }, + hide_flash: function() { + $('.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.slideDown(); + 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(pre_call != undefined) { + eval(pre_call); + } + if(confirm_message != undefined) { + if(confirm_message == 'default') 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(target == undefined) { + 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(e) { + var el = $(this); + var toremove = el.data('w2p_remove'); + if(toremove != undefined) { + 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(e) { + web2py.disableElement($(this)); + }); + /*re-enable click on completion*/ + el.on('ajax:complete', 'a[data-w2p_method][data-w2p_disable_with]', function(e) { + web2py.enableElement($(this)); + }); + }, + /* Disables form elements: + - Caches element value in 'w2p:enable-with' data store - Replaces element text with value of 'data-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'); - if(disable_with == undefined) { - element.data('w2p_disable_with', element[method]()) - } - if(element.data('w2p:enable-with') === undefined) { - element.data('w2p:enable-with', element[method]()); - } - element[method](element.data('w2p_disable_with')); - element.prop('disabled', 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'); + if(disable_with == undefined) { + element.data('w2p_disable_with', element[method]()) + } + if(element.data('w2p:enable-with') === undefined) { + 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 'ujs:enable-with' data store (created in `disableFormElements`) + /* 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(e) { - web2py.disableFormElements($(this)); - }); - el.on('ajax:complete', 'form[data-w2p_target]', function(e) { - 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(el.timing !== undefined) { // Block triggering regular routines - clearInterval(el.timing); - } - }); - $.web2py.component_handler(target); - }, - main_hook : function() { - var flash = $('.flash'); - flash.hide(); - if(flash.html()) web2py.flash(flash.html()); - web2py.ajax_init(document); - web2py.event_handlers(); - web2py.a_handlers(); - web2py.form_handlers(); + 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(e) { + web2py.disableFormElements($(this)); + }); + el.on('ajax:complete', 'form[data-w2p_target]', function(e) { + 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(el.timing !== undefined) { // Block triggering regular routines + clearInterval(el.timing); + } + }); + $.web2py.component_handler(target); + }, + main_hook: function() { + var flash = $('.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 () { + /*end of functions */ + /*main hook*/ + $(function() { web2py.main_hook(); }); diff --git a/applications/welcome/static/js/web2py.js b/applications/welcome/static/js/web2py.js index e6e62386..2e463733 100644 --- a/applications/welcome/static/js/web2py.js +++ b/applications/welcome/static/js/web2py.js @@ -19,685 +19,689 @@ $.web2py = web2py = { - popup: function(url) { - /* popup a window */ - 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) { - /*simple ajax function*/ - query = ''; - if(typeof s == "string") { - d = $(s).serialize(); - if(d) { - query = d; - } - } else { - pcs = []; - if(s != null && s != undefined) - for(i = 0; i < s.length; i++) { - q = $("[name=" + s[i] + "]").serialize(); - if(q) { - pcs.push(q); - } + popup: function(url) { + /* popup a window */ + 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) { + /*simple ajax function*/ + query = ''; + if(typeof s == "string") { + d = $(s).serialize(); + if(d) { + query = d; } - if(pcs.length > 0) { - query = pcs.join("&"); - } - } - $.ajax({ - type: "POST", - url: u, - data: query, - success: function(msg) { - if(t) { - if(t == ':eval') eval(msg); - else if(typeof t == 'string') $("#" + t).html(msg); - else t(msg); - } - } - }); - }, - 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', target).addClass('btn'); - $('form input[type="submit"], form input[type="button"]', 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('+ -').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 */ - $('.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().slideDown('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', '.flash', function(e) { - 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 = (typeof w2p_ajax_confirm_message != 'undefined') ? 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 = (typeof w2p_ajax_datetime_format != 'undefined') ? 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 = (typeof tformat != 'undefined') ? 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 = (typeof w2p_ajax_date_format != 'undefined') ? 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 = (typeof tformat != 'undefined') ? 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(active === undefined) { - $(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() { - var submit_button = $(this).find(web2py.formInputClickSelector); - web2py.disableElement(submit_button); - }); - doc.ajaxSuccess(function(e, xhr) { - var redirect = xhr.getResponseHeader('web2py-redirect-location'); - if(redirect !== null) { - window.location = redirect; - }; - /* 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', '.flash') - */ - switch(xhr.status) { - case 500: - web2py.flash(ajax_error_500); - } - }); - - }, - - trap_form: function(action, target) { - /* traps any LOADed form */ - $('#' + target + ' form').each(function(i) { - var form = $(this); - if(form.hasClass('no_trap')) { - return; - } - - form.attr('data-w2p_target', target); - var url = form.attr('action'); - - if((url === "") || (url === "#") || (typeof url === 'undefined')) { - /* form has no action. Use component url. */ - url = action; - } - - form.submit(function(e) { - web2py.disableElement(form.find(web2py.formInputClickSelector)); - web2py.hide_flash(); - web2py.ajax_page('post', url, form.serialize(), target, form); - e.preventDefault(); - }); - }); - }, - - - ajax_page: function(method, action, data, target, element) { - /* element is a new parameter, but should be put be put in front */ - if(element == undefined) element = $(document); - /* if target is not there, fill it with something that there isn't in the page*/ - if(target == undefined || target == '') target = 'w2p_none'; - if(web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */ - $.ajax({ - 'type': method, - 'url': action, - 'data': data, - 'beforeSend': function(xhr, settings) { - xhr.setRequestHeader('web2py-component-location', document.location); - xhr.setRequestHeader('web2py-component-element', 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); - } - }); - } - }, - 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; + } else { + pcs = []; + if(s != null && s != undefined) + for(i = 0; i < s.length; i++) { + q = $("[name=" + s[i] + "]").serialize(); + if(q) { + pcs.push(q); } } + if(pcs.length > 0) { + query = pcs.join("&"); } - 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); + } + $.ajax({ + type: "POST", + url: u, + data: query, + success: function(msg) { + if(t) { + if(t == ':eval') eval(msg); + else if(typeof t == 'string') $("#" + t).html(msg); + else t(msg); + } } - } 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; + }); + }, + 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', target).addClass('btn'); + $('form input[type="submit"], form input[type="button"]', 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; } - /*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; + + 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('+ -').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 */ + $('.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().slideDown('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(inset != lastset) { - score += 1; - lastset = inset; + if(flash) { + web2py.flash(decodeURIComponent(flash)) } - } - var entropy = mystring.length * Math.log(score) / 0.6931471805599453; - return Math.round(entropy * 100) / 100 - }, - validate_entropy: function(myfield, req_entropy) { - if(myfield.data('w2p_entropy') != undefined) 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) + }, + 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', '.flash', function(e) { + 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 = (typeof w2p_ajax_confirm_message != 'undefined') ? 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 = (typeof w2p_ajax_datetime_format != 'undefined') ? 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 = (typeof tformat != 'undefined') ? 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 = (typeof w2p_ajax_date_format != 'undefined') ? 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 = (typeof tformat != 'undefined') ? 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(active === undefined) { + $(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() { + var submit_button = $(this).find(web2py.formInputClickSelector); + web2py.disableElement(submit_button); + }); + doc.ajaxSuccess(function(e, xhr) { + var redirect = xhr.getResponseHeader('web2py-redirect-location'); + if(redirect !== null) { + window.location = redirect; }; - 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); - 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]:not([name]), input[type=image]:not([name]), button[type=submit]:not([name]), button:not([type]):not([name])', - /* 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) { - el.addClass('disabled'); - var method = el.prop('type') == 'submit' ? 'val' : 'html'; - var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working..."; - /*store enabled state if not already disabled */ - if(el.data('w2p:enable-with') === undefined) { - 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') || (el.data('w2p_disable_with') === undefined)) { - el.data('w2p_disable_with', disable_with_message); - } + /* 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); + }; + }); - /* 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.prop('type') == 'submit' ? 'val' : 'html'; - if(el.data('w2p:enable-with') !== undefined) { - /* 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 = $('.flash'); - web2py.hide_flash(); - flash.html(message).addClass(status); - if(flash.html()) flash.append(' × ').slideDown(); - }, - hide_flash: function() { - $('.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.slideDown(); - 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(pre_call != undefined) { - eval(pre_call); - } - if(confirm_message != undefined) { - if(confirm_message == 'default') 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(target == undefined) { - 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(e) { - var el = $(this); - var toremove = el.data('w2p_remove'); - if(toremove != undefined) { - toremove = el.closest(toremove); - if(!toremove.length) { - /*this enables removal of whatever selector if a closest is not found */ - toremove = $(toremove); + 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', '.flash') + */ + switch(xhr.status) { + case 500: + web2py.flash(ajax_error_500); } - toremove.remove(); + }); + + }, + trap_form: function(action, target) { + /* traps any LOADed form */ + $('#' + target + ' form').each(function(i) { + var form = $(this); + if(form.hasClass('no_trap')) { + return; + } + form.attr('data-w2p_target', target); + var url = form.attr('action'); + if((url === "") || (url === "#") || (typeof url === 'undefined')) { + /* form has no action. Use component url. */ + url = action; + } + form.submit(function(e) { + web2py.disableElement(form.find(web2py.formInputClickSelector)); + web2py.hide_flash(); + web2py.ajax_page('post', url, form.serialize(), target, form); + e.preventDefault(); + }); + form.on('click', web2py.formInputClickSelector, function(e) { + e.preventDefault(); + var input_name = $(this).attr('name'); + if(input_name != undefined) { + $('').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(element == undefined) element = $(document); + /* if target is not there, fill it with something that there isn't in the page*/ + if(target == undefined || target == '') target = 'w2p_none'; + if(web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */ + $.ajax({ + 'type': method, + 'url': action, + 'data': data, + 'beforeSend': function(xhr, settings) { + xhr.setRequestHeader('web2py-component-location', document.location); + xhr.setRequestHeader('web2py-component-element', 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); + } + }); } - }); - el.on('ajax:beforeSend', 'a[data-w2p_method][data-w2p_disable_with]', function(e) { - web2py.disableElement($(this)); - }); - /*re-enable click on completion*/ - el.on('ajax:complete', 'a[data-w2p_method][data-w2p_disable_with]', function(e) { - web2py.enableElement($(this)); - }); - }, - /* Disables form elements: - - Caches element value in 'ujs:enable-with' data store + }, + 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(myfield.data('w2p_entropy') != undefined) 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); + 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) { + el.addClass('disabled'); + var method = el.is('button') ? 'html' : 'val'; + //method = el.attr('name') ? 'html' : 'val'; + var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working..."; + /*store enabled state if not already disabled */ + if(el.data('w2p:enable-with') === undefined) { + 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') || (el.data('w2p_disable_with') === undefined)) { + 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('button') ? 'val' : 'html'; + if(el.data('w2p:enable-with') !== undefined) { + /* 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 = $('.flash'); + web2py.hide_flash(); + flash.html(message).addClass(status); + if(flash.html()) flash.append(' × ').slideDown(); + }, + hide_flash: function() { + $('.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.slideDown(); + 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(pre_call != undefined) { + eval(pre_call); + } + if(confirm_message != undefined) { + if(confirm_message == 'default') 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(target == undefined) { + 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(e) { + var el = $(this); + var toremove = el.data('w2p_remove'); + if(toremove != undefined) { + 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(e) { + web2py.disableElement($(this)); + }); + /*re-enable click on completion*/ + el.on('ajax:complete', 'a[data-w2p_method][data-w2p_disable_with]', function(e) { + web2py.enableElement($(this)); + }); + }, + /* Disables form elements: + - Caches element value in 'w2p:enable-with' data store - Replaces element text with value of 'data-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'); - if(disable_with == undefined) { - element.data('w2p_disable_with', element[method]()) - } - if(element.data('w2p:enable-with') === undefined) { - element.data('w2p:enable-with', element[method]()); - } - element[method](element.data('w2p_disable_with')); - element.prop('disabled', 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'); + if(disable_with == undefined) { + element.data('w2p_disable_with', element[method]()) + } + if(element.data('w2p:enable-with') === undefined) { + 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 'ujs:enable-with' data store (created in `disableFormElements`) + /* 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(e) { - web2py.disableFormElements($(this)); - }); - el.on('ajax:complete', 'form[data-w2p_target]', function(e) { - 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(el.timing !== undefined) { // Block triggering regular routines - clearInterval(el.timing); - } - }); - $.web2py.component_handler(target); - }, - main_hook : function() { - var flash = $('.flash'); - flash.hide(); - if(flash.html()) web2py.flash(flash.html()); - web2py.ajax_init(document); - web2py.event_handlers(); - web2py.a_handlers(); - web2py.form_handlers(); + 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(e) { + web2py.disableFormElements($(this)); + }); + el.on('ajax:complete', 'form[data-w2p_target]', function(e) { + 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(el.timing !== undefined) { // Block triggering regular routines + clearInterval(el.timing); + } + }); + $.web2py.component_handler(target); + }, + main_hook: function() { + var flash = $('.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 () { + /*end of functions */ + /*main hook*/ + $(function() { web2py.main_hook(); });