From a6b50dcdcd3d0e0fa52e41f513d0770602d06d0d Mon Sep 17 00:00:00 2001 From: niphlod Date: Sat, 8 Nov 2014 23:04:58 +0100 Subject: [PATCH] streamline behaviour of component (trapped) forms with multiple submit buttons this is because jquery can't know what button was pressed when the submit event is triggered. We circumvent it adding an hidden field before triggering the submission BTW: don't know why the commit seems huge. I just changed a bit the trap_form function to handle the corner case and streamlined what disableElement, enableElement, disableFormElements, enableFormElements do, along with formInputClickSelector --- applications/admin/static/js/web2py.js | 1324 +++++++++++---------- applications/examples/static/js/web2py.js | 1324 +++++++++++---------- applications/welcome/static/js/web2py.js | 1324 +++++++++++---------- 3 files changed, 1992 insertions(+), 1980 deletions(-) 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(); });