diff --git a/VERSION b/VERSION index de05d58c..46c0f5e5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.08.28.09.56.28 +Version 2.6.0-development+timestamp.2013.08.29.21.31.47 diff --git a/applications/admin/static/js/web2py.js b/applications/admin/static/js/web2py.js index 507be9f5..de2a6e25 100644 --- a/applications/admin/static/js/web2py.js +++ b/applications/admin/static/js/web2py.js @@ -20,18 +20,22 @@ $.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(); @@ -65,14 +69,15 @@ }); }, ajax_fields: function (target) { - /*this attaches something to a newly loaded fragment/page + /* + *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 */ var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; $("input.date", target).each(function () { - $(this).attr('autocomplete','off'); + $(this).attr('autocomplete', 'off'); Calendar.setup({ inputField: this, ifFormat: date_format, @@ -80,7 +85,7 @@ }); }); $("input.datetime", target).each(function () { - $(this).attr('autocomplete','off'); + $(this).attr('autocomplete', 'off'); Calendar.setup({ inputField: this, ifFormat: datetime_format, @@ -89,25 +94,25 @@ }); }); $("input.time", target).each(function () { - $(this).timeEntry().attr('autocomplete','off'); + $(this).timeEntry().attr('autocomplete', 'off'); }); /*adds btn class to buttons*/ $('button', target).addClass('btn'); $('form input[type="submit"], form input[type="button"]', target).addClass('btn'); - /*no more inline javascript for PasswordWidget*/ + /* javascript for PasswordWidget*/ $('input[type=password][data-w2p_entropy]', target).each(function () { web2py.validate_entropy($(this)); }); - /*no more inline javascript for ListWidget*/ + /* 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 + /* 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 + /* the line we clicked on was deleted, just add to end of list */ new_line.appendTo(ul); } new_line.find(":text").focus(); @@ -116,18 +121,20 @@ function rl(ul, e) { if($(ul).children().length > 1) { - //only remove if we have more than 1 item so the list is never empty + /* 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(); @@ -147,19 +154,32 @@ }); }, 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 in forms */ manage_errors: function (target) { $('.error', target).hide().slideDown('slow'); - //jQuery('.error', target).hide().fadeIn('slow'); + /* jQuery('.error', target).hide().fadeIn('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 + /* + * This is called once for page * Ideally it should bound all the things that are needed */ var doc = $(document); @@ -167,9 +187,12 @@ var t = $(this); if(t.css('top') == '0px') t.slideUp('slow'); else t.fadeOut(); - //if I want to display a clickable something - //inside flash, I should not be prevented to follow it - //e.preventDefault(); + /* if I want to display a clickable something + * inside flash, I should not be prevented to follow it + * + * e.preventDefault(); + */ + }); doc.on('keyup', 'input.integer', function () { this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse(); @@ -185,24 +208,21 @@ doc.ajaxSuccess(function (e, xhr) { var redirect = xhr.getResponseHeader('web2py-redirect-location'); - var command = xhr.getResponseHeader('web2py-component-command'); - var flash = xhr.getResponseHeader('web2py-component-flash'); if(redirect !== null) { window.location = redirect; }; - if(command !== null) { - eval(decodeURIComponent(command)); - } - if(flash) { - web2py.flash(decodeURIComponent(flash)) - } + /* 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') + /*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); @@ -211,12 +231,14 @@ }, trap_form: function (action, target) { + /* traps any LOADed form */ + var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working..."; $('#' + target + ' form').each(function (i) { var form = $(this); form.attr('data-w2p_target', target); if(!form.hasClass('no_trap')) { - //should be there by default ? - form.find('input[type=submit]').attr('data-w2p_disable_with', 'Working...'); + /* should be there by default */ + form.find('input[type=submit]').attr('data-w2p_disable_with', disable_with_message); form.submit(function (e) { web2py.hide_flash(); web2py.ajax_page('post', action, form.serialize(), target, form); @@ -236,31 +258,30 @@ }); }, ajax_page: function (method, action, data, target, element) { - //element is a new parameter, but should be put be put in front + /* element is a new parameter, but should be put be put in front */ if(element == undefined) element = $(document); - if(web2py.fire(element, 'ajax:before')) { //test a usecase, should stop here if returns false + if(web2py.fire(element, 'ajax:before')) { /*test a usecase, should stop here if returns false */ $.ajax({ 'type': method, 'url': action, 'data': data, 'beforeSend': function (xhr, settings) { - //added xhr.setRequestHeader('web2py-component-location', document.location); xhr.setRequestHeader('web2py-component-element', target); return web2py.fire(element, 'ajax:beforeSend', [xhr, settings]); //test a usecase, should stop here if returns false }, - //added 'success': function (data, status, xhr) { - //bummer for form submissions....the element is not there after complete - //because it gets replaced by the new response.... + /*bummer for form submissions....the element is not there after complete + *because it gets replaced by the new response.... + */ element.trigger('ajax:success', [data, status, xhr]); }, - //added '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) + /*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) + */ element.trigger('ajax:error', [xhr, status, error]); }, 'complete': function (xhr, status) { @@ -274,24 +295,26 @@ web2py.trap_form(action, target); web2py.trap_link(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 + /* 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 + /* Continue if times is Infinity or + * the times limit is not reached + */ if(element.reload_check()) { web2py.ajax_page('get', action, null, target, el); } - }; // reload - // Method to check timing limit + }; + /* Method to check timing limit */ element.reload_check = function () { if(jelement.hasClass('w2p_component_stop')) { clearInterval(this.timing); @@ -313,32 +336,33 @@ } } return false; - }; // reload check + }; if(!isNaN(timeout)) { element.timeout = timeout; element.reload_counter = times; if(times > 1) { - // Multiple or infinite reload - // Run first iteration + /* 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 + /* Run once with timeout */ element.run_once = true; element.setTimeout = setTimeout; element.timing = setTimeout(statement, timeout); } } else { - // run once (no timeout specified) + /* run once (no timeout specified) */ element.reload_counter = Infinity; web2py.ajax_page('get', action, null, target, el); } }); }, calc_entropy: function (mystring) { - //calculate a simple entropy for a given string + /* calculate a simple entropy for a given string */ var csets = new Array( 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/', @@ -346,7 +370,7 @@ var score = 0, other = {}, seen = {}, lastset = null, mystringlist = mystring.split(''); - for(var i = 0; i < mystringlist.length; i++) { // classify this character + 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++) @@ -354,7 +378,7 @@ inset = j; break; } - //calculate effect of character on alphabet size + /*calculate effect of character on alphabet size */ if(!(inset in seen)) { seen[inset] = 1; score += csets[inset].length; @@ -371,7 +395,6 @@ return Math.round(entropy * 100) / 100 }, validate_entropy: function (myfield, req_entropy) { - //added 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; @@ -401,23 +424,23 @@ ws.onopen = onopen ? onopen : (function () {}); ws.onmessage = onmessage; ws.onclose = onclose ? onclose : (function () {}); - return true; // supported - } else return false; // not supported + return true; /* supported */ + } else return false; /* not supported */ }, /* new from here */ - // Form input elements bound by jquery-ujs + /* Form input elements bound by jquery-uj */ formInputClickSelector: 'input[type=submit], input[type=image], button[type=submit], button:not([type])', - // Form input elements disabled during form submission + /* Form input elements disabled during form submission */ disableSelector: 'input, button, textarea, select', - // Form input elements re-enabled after form submission + /* 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 + /* Triggers an event on an element and returns false if the event result is false */ fire: function (obj, name, data) { var event = $.Event(name); obj.trigger(event, data); return event.result !== false; }, - // Helper function, needed to provide consistent behavior in IE + /* Helper function, needed to provide consistent behavior in IE */ stopEverything: function (e) { $(e.target).trigger('w2p:everythingStopped'); e.stopImmediatePropagation(); @@ -426,41 +449,42 @@ confirm: function (message) { return confirm(message); }, - // replace element's html with the 'data-disable-with' after storing original html - // and prevent clicking on it + /* 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'; - // store enabled state + var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working..."; + /*store enabled state*/ el.data('w2p:enable-with', el[method]()); /* little addition by default*/ if((el.data('w2p_disable_with') == 'default') || (el.data('w2p_disable_with') === undefined)) { - el.data('w2p_disable_with', 'Working...'); + el.data('w2p_disable_with', disable_with_message); } - // set to disabled state + /* set to disabled state*/ el[method](el.data('w2p_disable_with')); - el.bind('click.w2pDisable', function (e) { // prevent further clicking + 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 + /* 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 + /* set to old enabled state */ el[method](el.data('w2p:enable-with')); - el.removeData('w2p:enable-with'); // clean up cache + el.removeData('w2p:enable-with'); } el.removeClass('disabled'); - el.unbind('click.w2pDisable'); // enable element + el.unbind('click.w2pDisable'); }, - //convenience wrapper, internal use only + /*convenience wrapper, internal use only */ simple_component: function (action, target, element) { web2py.component(action, target, 0, 1, element); }, - //helper for flash messages + /*helper for flash messages*/ flash: function (message, status) { var flash = $('.flash'); web2py.hide_flash(); @@ -525,34 +549,16 @@ if(target == undefined) { if(method == 'GET') { web2py.ajax_page('get', action, [], 'bogus', el); //fixme? - //web2py.simple_component(action, el.attr('id'), el); //not working with original } else if(method == 'POST') { - //should be web2py.ajax(action, [], ''); but it's too simple web2py.ajax_page('post', action, [], 'bogus', el); //fixme? } } else { if(method == 'GET') { web2py.ajax_page('get', action, [], target, el); - //web2py.simple_component(action, target, el); } else if(method == 'POST') { - //should be web2py.ajax(action, [], target); but it's too simple web2py.ajax_page('post', action, [], target, el); } } - /*this should happen only on ajaxsuccess - * and should block subsequent clicks until ajaxcomplete - * NB: introduce the first incompatibility because normally - * the element would be removed in either case - /* removal code moved to the ajax:success event - START - if(toremove != undefined) { - toremove = el.closest(toremove); - if(!toremove.length) { - //this enables removal of whatever selector if a closest is not found - toremove = jQuery(toremove); - } - toremove.remove(); - } - /* removal code moved to the ajax:success event - END */ }, a_handlers: function () { var el = $(document); @@ -566,7 +572,7 @@ if(toremove != undefined) { toremove = el.closest(toremove); if(!toremove.length) { - //this enables removal of whatever selector if a closest is not found + /*this enables removal of whatever selector if a closest is not found */ toremove = $(toremove); } toremove.remove(); @@ -622,8 +628,8 @@ } } - //end of functions - //main hook + /*end of functions */ + /*main hook*/ $(function () { var flash = $('.flash'); flash.hide(); @@ -641,11 +647,11 @@ ajax = jQuery.web2py.ajax; web2py_component = jQuery.web2py.component; web2py_websocket = jQuery.web2py.websocket; web2py_ajax_page = jQuery.web2py.ajax_page; -//needed for IS_STRONG(entropy) +/*needed for IS_STRONG(entropy)*/ web2py_validate_entropy = jQuery.web2py.validate_entropy; -//needed for crud.search and SQLFORM.grid's search +/*needed for crud.search and SQLFORM.grid's search*/ web2py_ajax_fields = jQuery.web2py.ajax_fields; -//used for LOAD(ajax=False) +/*used for LOAD(ajax=False)*/ web2py_trap_form = jQuery.web2py.trap_form; /*undocumented - rare*/ diff --git a/applications/examples/static/js/web2py.js b/applications/examples/static/js/web2py.js index 507be9f5..de2a6e25 100644 --- a/applications/examples/static/js/web2py.js +++ b/applications/examples/static/js/web2py.js @@ -20,18 +20,22 @@ $.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(); @@ -65,14 +69,15 @@ }); }, ajax_fields: function (target) { - /*this attaches something to a newly loaded fragment/page + /* + *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 */ var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; $("input.date", target).each(function () { - $(this).attr('autocomplete','off'); + $(this).attr('autocomplete', 'off'); Calendar.setup({ inputField: this, ifFormat: date_format, @@ -80,7 +85,7 @@ }); }); $("input.datetime", target).each(function () { - $(this).attr('autocomplete','off'); + $(this).attr('autocomplete', 'off'); Calendar.setup({ inputField: this, ifFormat: datetime_format, @@ -89,25 +94,25 @@ }); }); $("input.time", target).each(function () { - $(this).timeEntry().attr('autocomplete','off'); + $(this).timeEntry().attr('autocomplete', 'off'); }); /*adds btn class to buttons*/ $('button', target).addClass('btn'); $('form input[type="submit"], form input[type="button"]', target).addClass('btn'); - /*no more inline javascript for PasswordWidget*/ + /* javascript for PasswordWidget*/ $('input[type=password][data-w2p_entropy]', target).each(function () { web2py.validate_entropy($(this)); }); - /*no more inline javascript for ListWidget*/ + /* 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 + /* 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 + /* the line we clicked on was deleted, just add to end of list */ new_line.appendTo(ul); } new_line.find(":text").focus(); @@ -116,18 +121,20 @@ function rl(ul, e) { if($(ul).children().length > 1) { - //only remove if we have more than 1 item so the list is never empty + /* 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(); @@ -147,19 +154,32 @@ }); }, 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 in forms */ manage_errors: function (target) { $('.error', target).hide().slideDown('slow'); - //jQuery('.error', target).hide().fadeIn('slow'); + /* jQuery('.error', target).hide().fadeIn('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 + /* + * This is called once for page * Ideally it should bound all the things that are needed */ var doc = $(document); @@ -167,9 +187,12 @@ var t = $(this); if(t.css('top') == '0px') t.slideUp('slow'); else t.fadeOut(); - //if I want to display a clickable something - //inside flash, I should not be prevented to follow it - //e.preventDefault(); + /* if I want to display a clickable something + * inside flash, I should not be prevented to follow it + * + * e.preventDefault(); + */ + }); doc.on('keyup', 'input.integer', function () { this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse(); @@ -185,24 +208,21 @@ doc.ajaxSuccess(function (e, xhr) { var redirect = xhr.getResponseHeader('web2py-redirect-location'); - var command = xhr.getResponseHeader('web2py-component-command'); - var flash = xhr.getResponseHeader('web2py-component-flash'); if(redirect !== null) { window.location = redirect; }; - if(command !== null) { - eval(decodeURIComponent(command)); - } - if(flash) { - web2py.flash(decodeURIComponent(flash)) - } + /* 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') + /*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); @@ -211,12 +231,14 @@ }, trap_form: function (action, target) { + /* traps any LOADed form */ + var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working..."; $('#' + target + ' form').each(function (i) { var form = $(this); form.attr('data-w2p_target', target); if(!form.hasClass('no_trap')) { - //should be there by default ? - form.find('input[type=submit]').attr('data-w2p_disable_with', 'Working...'); + /* should be there by default */ + form.find('input[type=submit]').attr('data-w2p_disable_with', disable_with_message); form.submit(function (e) { web2py.hide_flash(); web2py.ajax_page('post', action, form.serialize(), target, form); @@ -236,31 +258,30 @@ }); }, ajax_page: function (method, action, data, target, element) { - //element is a new parameter, but should be put be put in front + /* element is a new parameter, but should be put be put in front */ if(element == undefined) element = $(document); - if(web2py.fire(element, 'ajax:before')) { //test a usecase, should stop here if returns false + if(web2py.fire(element, 'ajax:before')) { /*test a usecase, should stop here if returns false */ $.ajax({ 'type': method, 'url': action, 'data': data, 'beforeSend': function (xhr, settings) { - //added xhr.setRequestHeader('web2py-component-location', document.location); xhr.setRequestHeader('web2py-component-element', target); return web2py.fire(element, 'ajax:beforeSend', [xhr, settings]); //test a usecase, should stop here if returns false }, - //added 'success': function (data, status, xhr) { - //bummer for form submissions....the element is not there after complete - //because it gets replaced by the new response.... + /*bummer for form submissions....the element is not there after complete + *because it gets replaced by the new response.... + */ element.trigger('ajax:success', [data, status, xhr]); }, - //added '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) + /*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) + */ element.trigger('ajax:error', [xhr, status, error]); }, 'complete': function (xhr, status) { @@ -274,24 +295,26 @@ web2py.trap_form(action, target); web2py.trap_link(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 + /* 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 + /* Continue if times is Infinity or + * the times limit is not reached + */ if(element.reload_check()) { web2py.ajax_page('get', action, null, target, el); } - }; // reload - // Method to check timing limit + }; + /* Method to check timing limit */ element.reload_check = function () { if(jelement.hasClass('w2p_component_stop')) { clearInterval(this.timing); @@ -313,32 +336,33 @@ } } return false; - }; // reload check + }; if(!isNaN(timeout)) { element.timeout = timeout; element.reload_counter = times; if(times > 1) { - // Multiple or infinite reload - // Run first iteration + /* 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 + /* Run once with timeout */ element.run_once = true; element.setTimeout = setTimeout; element.timing = setTimeout(statement, timeout); } } else { - // run once (no timeout specified) + /* run once (no timeout specified) */ element.reload_counter = Infinity; web2py.ajax_page('get', action, null, target, el); } }); }, calc_entropy: function (mystring) { - //calculate a simple entropy for a given string + /* calculate a simple entropy for a given string */ var csets = new Array( 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/', @@ -346,7 +370,7 @@ var score = 0, other = {}, seen = {}, lastset = null, mystringlist = mystring.split(''); - for(var i = 0; i < mystringlist.length; i++) { // classify this character + 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++) @@ -354,7 +378,7 @@ inset = j; break; } - //calculate effect of character on alphabet size + /*calculate effect of character on alphabet size */ if(!(inset in seen)) { seen[inset] = 1; score += csets[inset].length; @@ -371,7 +395,6 @@ return Math.round(entropy * 100) / 100 }, validate_entropy: function (myfield, req_entropy) { - //added 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; @@ -401,23 +424,23 @@ ws.onopen = onopen ? onopen : (function () {}); ws.onmessage = onmessage; ws.onclose = onclose ? onclose : (function () {}); - return true; // supported - } else return false; // not supported + return true; /* supported */ + } else return false; /* not supported */ }, /* new from here */ - // Form input elements bound by jquery-ujs + /* Form input elements bound by jquery-uj */ formInputClickSelector: 'input[type=submit], input[type=image], button[type=submit], button:not([type])', - // Form input elements disabled during form submission + /* Form input elements disabled during form submission */ disableSelector: 'input, button, textarea, select', - // Form input elements re-enabled after form submission + /* 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 + /* Triggers an event on an element and returns false if the event result is false */ fire: function (obj, name, data) { var event = $.Event(name); obj.trigger(event, data); return event.result !== false; }, - // Helper function, needed to provide consistent behavior in IE + /* Helper function, needed to provide consistent behavior in IE */ stopEverything: function (e) { $(e.target).trigger('w2p:everythingStopped'); e.stopImmediatePropagation(); @@ -426,41 +449,42 @@ confirm: function (message) { return confirm(message); }, - // replace element's html with the 'data-disable-with' after storing original html - // and prevent clicking on it + /* 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'; - // store enabled state + var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working..."; + /*store enabled state*/ el.data('w2p:enable-with', el[method]()); /* little addition by default*/ if((el.data('w2p_disable_with') == 'default') || (el.data('w2p_disable_with') === undefined)) { - el.data('w2p_disable_with', 'Working...'); + el.data('w2p_disable_with', disable_with_message); } - // set to disabled state + /* set to disabled state*/ el[method](el.data('w2p_disable_with')); - el.bind('click.w2pDisable', function (e) { // prevent further clicking + 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 + /* 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 + /* set to old enabled state */ el[method](el.data('w2p:enable-with')); - el.removeData('w2p:enable-with'); // clean up cache + el.removeData('w2p:enable-with'); } el.removeClass('disabled'); - el.unbind('click.w2pDisable'); // enable element + el.unbind('click.w2pDisable'); }, - //convenience wrapper, internal use only + /*convenience wrapper, internal use only */ simple_component: function (action, target, element) { web2py.component(action, target, 0, 1, element); }, - //helper for flash messages + /*helper for flash messages*/ flash: function (message, status) { var flash = $('.flash'); web2py.hide_flash(); @@ -525,34 +549,16 @@ if(target == undefined) { if(method == 'GET') { web2py.ajax_page('get', action, [], 'bogus', el); //fixme? - //web2py.simple_component(action, el.attr('id'), el); //not working with original } else if(method == 'POST') { - //should be web2py.ajax(action, [], ''); but it's too simple web2py.ajax_page('post', action, [], 'bogus', el); //fixme? } } else { if(method == 'GET') { web2py.ajax_page('get', action, [], target, el); - //web2py.simple_component(action, target, el); } else if(method == 'POST') { - //should be web2py.ajax(action, [], target); but it's too simple web2py.ajax_page('post', action, [], target, el); } } - /*this should happen only on ajaxsuccess - * and should block subsequent clicks until ajaxcomplete - * NB: introduce the first incompatibility because normally - * the element would be removed in either case - /* removal code moved to the ajax:success event - START - if(toremove != undefined) { - toremove = el.closest(toremove); - if(!toremove.length) { - //this enables removal of whatever selector if a closest is not found - toremove = jQuery(toremove); - } - toremove.remove(); - } - /* removal code moved to the ajax:success event - END */ }, a_handlers: function () { var el = $(document); @@ -566,7 +572,7 @@ if(toremove != undefined) { toremove = el.closest(toremove); if(!toremove.length) { - //this enables removal of whatever selector if a closest is not found + /*this enables removal of whatever selector if a closest is not found */ toremove = $(toremove); } toremove.remove(); @@ -622,8 +628,8 @@ } } - //end of functions - //main hook + /*end of functions */ + /*main hook*/ $(function () { var flash = $('.flash'); flash.hide(); @@ -641,11 +647,11 @@ ajax = jQuery.web2py.ajax; web2py_component = jQuery.web2py.component; web2py_websocket = jQuery.web2py.websocket; web2py_ajax_page = jQuery.web2py.ajax_page; -//needed for IS_STRONG(entropy) +/*needed for IS_STRONG(entropy)*/ web2py_validate_entropy = jQuery.web2py.validate_entropy; -//needed for crud.search and SQLFORM.grid's search +/*needed for crud.search and SQLFORM.grid's search*/ web2py_ajax_fields = jQuery.web2py.ajax_fields; -//used for LOAD(ajax=False) +/*used for LOAD(ajax=False)*/ web2py_trap_form = jQuery.web2py.trap_form; /*undocumented - rare*/ diff --git a/applications/welcome/static/js/web2py.js b/applications/welcome/static/js/web2py.js index d96e685d..de2a6e25 100644 --- a/applications/welcome/static/js/web2py.js +++ b/applications/welcome/static/js/web2py.js @@ -20,18 +20,22 @@ $.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(); @@ -65,14 +69,15 @@ }); }, ajax_fields: function (target) { - /*this attaches something to a newly loaded fragment/page + /* + *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 */ var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; $("input.date", target).each(function () { - $(this).attr('autocomplete','off'); + $(this).attr('autocomplete', 'off'); Calendar.setup({ inputField: this, ifFormat: date_format, @@ -80,7 +85,7 @@ }); }); $("input.datetime", target).each(function () { - $(this).attr('autocomplete','off'); + $(this).attr('autocomplete', 'off'); Calendar.setup({ inputField: this, ifFormat: datetime_format, @@ -89,25 +94,25 @@ }); }); $("input.time", target).each(function () { - $(this).timeEntry().attr('autocomplete','off'); + $(this).timeEntry().attr('autocomplete', 'off'); }); /*adds btn class to buttons*/ $('button', target).addClass('btn'); $('form input[type="submit"], form input[type="button"]', target).addClass('btn'); - /*no more inline javascript for PasswordWidget*/ + /* javascript for PasswordWidget*/ $('input[type=password][data-w2p_entropy]', target).each(function () { web2py.validate_entropy($(this)); }); - /*no more inline javascript for ListWidget*/ + /* 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 + /* 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 + /* the line we clicked on was deleted, just add to end of list */ new_line.appendTo(ul); } new_line.find(":text").focus(); @@ -116,18 +121,20 @@ function rl(ul, e) { if($(ul).children().length > 1) { - //only remove if we have more than 1 item so the list is never empty + /* 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(); @@ -147,19 +154,32 @@ }); }, 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 in forms */ manage_errors: function (target) { $('.error', target).hide().slideDown('slow'); - //jQuery('.error', target).hide().fadeIn('slow'); + /* jQuery('.error', target).hide().fadeIn('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 + /* + * This is called once for page * Ideally it should bound all the things that are needed */ var doc = $(document); @@ -167,9 +187,12 @@ var t = $(this); if(t.css('top') == '0px') t.slideUp('slow'); else t.fadeOut(); - //if I want to display a clickable something - //inside flash, I should not be prevented to follow it - //e.preventDefault(); + /* if I want to display a clickable something + * inside flash, I should not be prevented to follow it + * + * e.preventDefault(); + */ + }); doc.on('keyup', 'input.integer', function () { this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse(); @@ -185,24 +208,21 @@ doc.ajaxSuccess(function (e, xhr) { var redirect = xhr.getResponseHeader('web2py-redirect-location'); - var command = xhr.getResponseHeader('web2py-component-command'); - var flash = xhr.getResponseHeader('web2py-component-flash'); if(redirect !== null) { window.location = redirect; }; - if(command !== null) { - eval(decodeURIComponent(command)); - } - if(flash) { - web2py.flash(decodeURIComponent(flash)) - } + /* 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') + /*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); @@ -211,12 +231,13 @@ }, trap_form: function (action, target) { + /* traps any LOADed form */ var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working..."; $('#' + target + ' form').each(function (i) { var form = $(this); form.attr('data-w2p_target', target); if(!form.hasClass('no_trap')) { - //should be there by default ? + /* should be there by default */ form.find('input[type=submit]').attr('data-w2p_disable_with', disable_with_message); form.submit(function (e) { web2py.hide_flash(); @@ -237,31 +258,30 @@ }); }, ajax_page: function (method, action, data, target, element) { - //element is a new parameter, but should be put be put in front + /* element is a new parameter, but should be put be put in front */ if(element == undefined) element = $(document); - if(web2py.fire(element, 'ajax:before')) { //test a usecase, should stop here if returns false + if(web2py.fire(element, 'ajax:before')) { /*test a usecase, should stop here if returns false */ $.ajax({ 'type': method, 'url': action, 'data': data, 'beforeSend': function (xhr, settings) { - //added xhr.setRequestHeader('web2py-component-location', document.location); xhr.setRequestHeader('web2py-component-element', target); return web2py.fire(element, 'ajax:beforeSend', [xhr, settings]); //test a usecase, should stop here if returns false }, - //added 'success': function (data, status, xhr) { - //bummer for form submissions....the element is not there after complete - //because it gets replaced by the new response.... + /*bummer for form submissions....the element is not there after complete + *because it gets replaced by the new response.... + */ element.trigger('ajax:success', [data, status, xhr]); }, - //added '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) + /*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) + */ element.trigger('ajax:error', [xhr, status, error]); }, 'complete': function (xhr, status) { @@ -275,24 +295,26 @@ web2py.trap_form(action, target); web2py.trap_link(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 + /* 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 + /* Continue if times is Infinity or + * the times limit is not reached + */ if(element.reload_check()) { web2py.ajax_page('get', action, null, target, el); } - }; // reload - // Method to check timing limit + }; + /* Method to check timing limit */ element.reload_check = function () { if(jelement.hasClass('w2p_component_stop')) { clearInterval(this.timing); @@ -314,32 +336,33 @@ } } return false; - }; // reload check + }; if(!isNaN(timeout)) { element.timeout = timeout; element.reload_counter = times; if(times > 1) { - // Multiple or infinite reload - // Run first iteration + /* 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 + /* Run once with timeout */ element.run_once = true; element.setTimeout = setTimeout; element.timing = setTimeout(statement, timeout); } } else { - // run once (no timeout specified) + /* run once (no timeout specified) */ element.reload_counter = Infinity; web2py.ajax_page('get', action, null, target, el); } }); }, calc_entropy: function (mystring) { - //calculate a simple entropy for a given string + /* calculate a simple entropy for a given string */ var csets = new Array( 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/', @@ -347,7 +370,7 @@ var score = 0, other = {}, seen = {}, lastset = null, mystringlist = mystring.split(''); - for(var i = 0; i < mystringlist.length; i++) { // classify this character + 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++) @@ -355,7 +378,7 @@ inset = j; break; } - //calculate effect of character on alphabet size + /*calculate effect of character on alphabet size */ if(!(inset in seen)) { seen[inset] = 1; score += csets[inset].length; @@ -372,7 +395,6 @@ return Math.round(entropy * 100) / 100 }, validate_entropy: function (myfield, req_entropy) { - //added 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; @@ -402,23 +424,23 @@ ws.onopen = onopen ? onopen : (function () {}); ws.onmessage = onmessage; ws.onclose = onclose ? onclose : (function () {}); - return true; // supported - } else return false; // not supported + return true; /* supported */ + } else return false; /* not supported */ }, /* new from here */ - // Form input elements bound by jquery-ujs + /* Form input elements bound by jquery-uj */ formInputClickSelector: 'input[type=submit], input[type=image], button[type=submit], button:not([type])', - // Form input elements disabled during form submission + /* Form input elements disabled during form submission */ disableSelector: 'input, button, textarea, select', - // Form input elements re-enabled after form submission + /* 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 + /* Triggers an event on an element and returns false if the event result is false */ fire: function (obj, name, data) { var event = $.Event(name); obj.trigger(event, data); return event.result !== false; }, - // Helper function, needed to provide consistent behavior in IE + /* Helper function, needed to provide consistent behavior in IE */ stopEverything: function (e) { $(e.target).trigger('w2p:everythingStopped'); e.stopImmediatePropagation(); @@ -427,42 +449,42 @@ confirm: function (message) { return confirm(message); }, - // replace element's html with the 'data-disable-with' after storing original html - // and prevent clicking on it + /* 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 + /*store enabled state*/ el.data('w2p:enable-with', el[method]()); /* little addition by default*/ 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 + /* set to disabled state*/ el[method](el.data('w2p_disable_with')); - el.bind('click.w2pDisable', function (e) { // prevent further clicking + 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 + /* 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 + /* set to old enabled state */ el[method](el.data('w2p:enable-with')); - el.removeData('w2p:enable-with'); // clean up cache + el.removeData('w2p:enable-with'); } el.removeClass('disabled'); - el.unbind('click.w2pDisable'); // enable element + el.unbind('click.w2pDisable'); }, - //convenience wrapper, internal use only + /*convenience wrapper, internal use only */ simple_component: function (action, target, element) { web2py.component(action, target, 0, 1, element); }, - //helper for flash messages + /*helper for flash messages*/ flash: function (message, status) { var flash = $('.flash'); web2py.hide_flash(); @@ -527,34 +549,16 @@ if(target == undefined) { if(method == 'GET') { web2py.ajax_page('get', action, [], 'bogus', el); //fixme? - //web2py.simple_component(action, el.attr('id'), el); //not working with original } else if(method == 'POST') { - //should be web2py.ajax(action, [], ''); but it's too simple web2py.ajax_page('post', action, [], 'bogus', el); //fixme? } } else { if(method == 'GET') { web2py.ajax_page('get', action, [], target, el); - //web2py.simple_component(action, target, el); } else if(method == 'POST') { - //should be web2py.ajax(action, [], target); but it's too simple web2py.ajax_page('post', action, [], target, el); } } - /*this should happen only on ajaxsuccess - * and should block subsequent clicks until ajaxcomplete - * NB: introduce the first incompatibility because normally - * the element would be removed in either case - /* removal code moved to the ajax:success event - START - if(toremove != undefined) { - toremove = el.closest(toremove); - if(!toremove.length) { - //this enables removal of whatever selector if a closest is not found - toremove = jQuery(toremove); - } - toremove.remove(); - } - /* removal code moved to the ajax:success event - END */ }, a_handlers: function () { var el = $(document); @@ -568,7 +572,7 @@ if(toremove != undefined) { toremove = el.closest(toremove); if(!toremove.length) { - //this enables removal of whatever selector if a closest is not found + /*this enables removal of whatever selector if a closest is not found */ toremove = $(toremove); } toremove.remove(); @@ -624,8 +628,8 @@ } } - //end of functions - //main hook + /*end of functions */ + /*main hook*/ $(function () { var flash = $('.flash'); flash.hide(); @@ -643,11 +647,11 @@ ajax = jQuery.web2py.ajax; web2py_component = jQuery.web2py.component; web2py_websocket = jQuery.web2py.websocket; web2py_ajax_page = jQuery.web2py.ajax_page; -//needed for IS_STRONG(entropy) +/*needed for IS_STRONG(entropy)*/ web2py_validate_entropy = jQuery.web2py.validate_entropy; -//needed for crud.search and SQLFORM.grid's search +/*needed for crud.search and SQLFORM.grid's search*/ web2py_ajax_fields = jQuery.web2py.ajax_fields; -//used for LOAD(ajax=False) +/*used for LOAD(ajax=False)*/ web2py_trap_form = jQuery.web2py.trap_form; /*undocumented - rare*/ diff --git a/gluon/globals.py b/gluon/globals.py index 6dfe788c..ab6fe743 100644 --- a/gluon/globals.py +++ b/gluon/globals.py @@ -86,7 +86,7 @@ class SortingPickler(Pickler): self._batch_setitems([(key,obj[key]) for key in sorted(obj)]) SortingPickler.dispatch = copy.copy(Pickler.dispatch) -SortingPickler.dispatch[DictionaryType] = SortingPickler.save_dict +SortingPickler.dispatch[DictionaryType] = SortingPickler.save_dict def sorting_dumps(obj, protocol=None): file = cStringIO.StringIO() @@ -975,6 +975,7 @@ class Session(Storage): def _try_store_in_cookie(self, request, response): if self._forget or self._unchanged(response): + self.save_session_id_cookie() return False name = response.session_data_name compression_level = response.session_cookie_compression_level @@ -1006,6 +1007,7 @@ class Session(Storage): global_settings.db_sessions is not True and response.session_masterapp in global_settings.db_sessions): global_settings.db_sessions.remove(response.session_masterapp) + self.save_session_id_cookie() return False table = response.session_db_table @@ -1043,6 +1045,7 @@ class Session(Storage): def _try_store_in_file(self, request, response): try: if not response.session_id or self._forget or self._unchanged(response): + self.save_session_id_cookie() return False if response.session_new or not response.session_file: # Tests if the session sub-folder exists, if not, create it diff --git a/gluon/languages.py b/gluon/languages.py index fe968666..c20918b4 100644 --- a/gluon/languages.py +++ b/gluon/languages.py @@ -423,6 +423,10 @@ class lazyT(object): return lazyT(self) return lazyT(self.m, symbols, self.T, self.f, self.t, self.M) +def pickle_lazyT(c): + return str, (c.xml(),) + +copy_reg.pickle(lazyT, pickle_lazyT) class translator(object): """ @@ -926,18 +930,6 @@ def findT(path, language=DEFAULT_LANGUAGE): else sentences['!langcode!']) write_dict(lang_file, sentences) -### important to allow safe session.flash=T(....) - - -def lazyT_unpickle(data): - return marshal.loads(data) - - -def lazyT_pickle(data): - return lazyT_unpickle, (marshal.dumps(str(data)),) -copy_reg.pickle(lazyT, lazyT_pickle, lazyT_unpickle) - - def update_all_languages(application_path): path = pjoin(application_path, 'languages/') for language in oslistdir(path): diff --git a/gluon/main.py b/gluon/main.py index 62af77c7..21669463 100644 --- a/gluon/main.py +++ b/gluon/main.py @@ -454,57 +454,58 @@ def wsgibase(environ, responder): serve_controller(request, response, session) except HTTP, http_response: - + if static_file: return http_response.to(responder, env=env) if request.body: request.body.close() - # ################################################## - # on success, try store session in database - # ################################################## - session._try_store_in_db(request, response) + if hasattr(current,'request'): - # ################################################## - # on success, commit database - # ################################################## + # ################################################## + # on success, try store session in database + # ################################################## + session._try_store_in_db(request, response) - if response.do_not_commit is True: - BaseAdapter.close_all_instances(None) - # elif response._custom_commit: - # response._custom_commit() - elif response.custom_commit: - BaseAdapter.close_all_instances(response.custom_commit) - else: - BaseAdapter.close_all_instances('commit') + # ################################################## + # on success, commit database + # ################################################## + + if response.do_not_commit is True: + BaseAdapter.close_all_instances(None) + elif response.custom_commit: + BaseAdapter.close_all_instances(response.custom_commit) + else: + BaseAdapter.close_all_instances('commit') - # ################################################## - # if session not in db try store session on filesystem - # this must be done after trying to commit database! - # ################################################## + # ################################################## + # if session not in db try store session on filesystem + # this must be done after trying to commit database! + # ################################################## - session._try_store_in_cookie_or_file(request, response) + session._try_store_in_cookie_or_file(request, response) - if request.cid: - if response.flash: - http_response.headers['web2py-component-flash'] = \ - urllib2.quote(xmlescape(response.flash)\ - .replace('\n','')) - if response.js: - http_response.headers['web2py-component-command'] = \ - urllib2.quote(response.js.replace('\n','')) + if request.cid: + if response.flash: + http_response.headers['web2py-component-flash'] = \ + urllib2.quote(xmlescape(response.flash)\ + .replace('\n','')) + if response.js: + http_response.headers['web2py-component-command'] = \ + urllib2.quote(response.js.replace('\n','')) + + # ################################################## + # store cookies in headers + # ################################################## - # ################################################## - # store cookies in headers - # ################################################## + rcookies = response.cookies + if session._forget and response.session_id_name in rcookies: + del rcookies[response.session_id_name] + elif session._secure: + rcookies[response.session_id_name]['secure'] = True + http_response.cookies2headers(rcookies) - rcookies = response.cookies - if session._forget and response.session_id_name in rcookies: - del rcookies[response.session_id_name] - elif session._secure: - rcookies[response.session_id_name]['secure'] = True - http_response.cookies2headers(rcookies) ticket = None except RestrictedError, e: diff --git a/gluon/tests/test_web.py b/gluon/tests/test_web.py index 4c9a99f6..274a2cd4 100644 --- a/gluon/tests/test_web.py +++ b/gluon/tests/test_web.py @@ -122,7 +122,7 @@ class TestWeb(LiveTest): client.get('index') # COMMENTED BECAUSE FAILS BUT WHY? - # self.assertTrue('Welcome Homer' in client.text) + self.assertTrue('Welcome Homer' in client.text) client = WebClient('http://127.0.0.1:8000/admin/default/') client.post('index', data=dict(password='hello')) diff --git a/gluon/tools.py b/gluon/tools.py index 42e1cb11..11f02c5e 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -1304,44 +1304,55 @@ class Auth(object): else: raise HTTP(404) - def navbar(self, mode='Default', action=None, prefix='Welcome', referrer_actions=DEFAULT, user_identifier=DEFAULT): - """Navbar with support for more templates + def navbar(self, mode='Default', action=None, prefix='Welcome', + referrer_actions=DEFAULT, user_identifier=DEFAULT): + """ Navbar with support for more templates This uses some code from the old navbar. Keyword arguments: mode -- see options for list of """ - items = [] #Hold all menu items in a list - self.bar = '' #The final + items = [] # Hold all menu items in a list + self.bar = '' # The final T = current.T referrer_actions = [] if not referrer_actions else referrer_actions if not action: action = self.url(self.settings.function) - + request = current.request if URL() == action: next = '' else: next = '?_next=' + urllib.quote(URL(args=request.args, vars=request.get_vars)) - href = lambda function: '%s/%s%s' % (action, function, - next if referrer_actions is DEFAULT or function in referrer_actions else '') + href = lambda function: '%s/%s%s' % (action, function, next + if referrer_actions is DEFAULT + or function in referrer_actions + else '') if isinstance(prefix, str): prefix = T(prefix) if prefix: prefix = prefix.strip() + ' ' - def Anr(*a,**b): - b['_rel']='nofollow' - return A(*a,**b) - - if self.user_id: #User is logged in - items.append({'name': T('Logout'), 'href': '%s/logout?_next=%s' % (action, urllib.quote(self.settings.logout_next)), 'icon': 'icon-off'}) + def Anr(*a, **b): + b['_rel'] = 'nofollow' + return A(*a, **b) + + if self.user_id: # User is logged in + logout_next = self.settings.logout_next + items.append({'name': T('Logout'), + 'href': '%s/logout?_next=%s' % (action, + urllib.quote( + logout_next)), + 'icon': 'icon-off'}) if not 'profile' in self.settings.actions_disabled: - items.append({'name': T('Profile'), 'href': href('profile'), 'icon': 'icon-user'}) + items.append({'name': T('Profile'), 'href': href('profile'), + 'icon': 'icon-user'}) if not 'change_password' in self.settings.actions_disabled: - items.append({'name': T('Password'), 'href': href('change_password'), 'icon': 'icon-lock'}) + items.append({'name': T('Password'), + 'href': href('change_password'), + 'icon': 'icon-lock'}) if user_identifier is DEFAULT: user_identifier = '%(first_name)s' @@ -1353,42 +1364,144 @@ class Auth(object): user_identifier = user_identifier % self.user if not user_identifier: user_identifier = '' - else: #User is not logged in - items.append({'name': T('Login'), 'href': href('login'), 'icon': 'icon-off'}) + else: # User is not logged in + items.append({'name': T('Login'), 'href': href('login'), + 'icon': 'icon-off'}) if not 'register' in self.settings.actions_disabled: - items.append({'name': T('Register'), 'href': href('register'), 'icon': 'icon-user'}) + items.append({'name': T('Register'), 'href': href('register'), + 'icon': 'icon-user'}) if not 'request_reset_password' in self.settings.actions_disabled: - items.append({'name': T('Lost password?'), 'href': href('request_reset_password'), 'icon': 'icon-lock'}) - if self.settings.use_username and not 'retrieve_username' in self.settings.actions_disabled: - items.append({'name': T('Forgot username?'), 'href': href('retrieve_username'), 'icon': 'icon-edit'}) - - def menu(): #For inclusion in MENU + items.append({'name': T('Lost password?'), + 'href': href('request_reset_password'), + 'icon': 'icon-lock'}) + if (self.settings.use_username and not + 'retrieve_username' in self.settings.actions_disabled): + items.append({'name': T('Forgot username?'), + 'href': href('retrieve_username'), + 'icon': 'icon-edit'}) + + def menu(): # For inclusion in MENU self.bar = [(items[0]['name'], False, items[0]['href'], [])] del items[0] for item in items: self.bar[0][3].append((item['name'], False, item['href'])) - def bootstrap(): #Default web2py scaffolding - self.bar = UL(LI(Anr(I(_class=items[0]['icon']), ' ' + items[0]['name'], _href=items[0]['href'])), _class='dropdown-menu') + def bootstrap(): # Default web2py scaffolding + self.bar = UL(LI(Anr(I(_class=items[0]['icon']), + ' ' + items[0]['name'], + _href=items[0]['href'])), + _class='dropdown-menu') del items[0] for item in items: - self.bar.insert(-1, LI(Anr(I(_class=item['icon']), ' ' + item['name'], _href=item['href']))) + self.bar.insert(-1, LI(Anr(I(_class=item['icon']), + ' ' + item['name'], + _href=item['href']))) self.bar.insert(-1, LI('', _class='divider')) if self.user_id: - self.bar = LI(Anr(prefix, user_identifier, _href='#'), self.bar, _class='dropdown') + self.bar = LI(Anr(prefix, user_identifier, _href='#'), + self.bar, + _class='dropdown') else: - self.bar = LI(Anr(T('Login'), _href='#'), self.bar, _class='dropdown') + self.bar = LI(Anr(T('Login'), _href='#'), self.bar, + _class='dropdown') + + def bare(): + """ In order to do advanced customization we only need the + prefix, the user_identifier and the href attribute of items + + Example: + + # in module custom_layout.py + from gluon import * + def navbar(auth_navbar): + bar = auth_navbar + user = bar["user"] + + if not user: + btn_login = A(current.T("Login"), + _href=bar["login"], + _class="btn btn-success", + _rel="nofollow") + btn_register = A(current.T("Sign up"), + _href=bar["register"], + _class="btn btn-primary", + _rel="nofollow") + return DIV(btn_register, btn_login, _class="btn-group") + else: + toggletext = "%s back %s" % (bar["prefix"], user) + toggle = A(toggletext, + _href="#", + _class="dropdown-toggle", + _rel="nofollow", + **{"_data-toggle": "dropdown"}) + li_profile = LI(A(I(_class="icon-user"), ' ', + current.T("Account details"), + _href=bar["profile"], _rel="nofollow")) + li_custom = LI(A(I(_class="icon-book"), ' ', + current.T("My Agenda"), + _href="#", rel="nofollow")) + li_logout = LI(A(I(_class="icon-off"), ' ', + current.T("logout"), + _href=bar["logout"], _rel="nofollow")) + dropdown = UL(li_profile, + li_custom, + LI('', _class="divider"), + li_logout, + _class="dropdown-menu", _role="menu") + + return LI(toggle, dropdown, _class="dropdown") + + # in models db.py + import custom_layout as custom + + # in layout.html + + + """ + bare = {} + + bare['prefix'] = prefix + bare['user'] = user_identifier if self.user_id else None + + for i in items: + if i['name'] == T('Login'): + k = 'login' + elif i['name'] == T('Register'): + k = 'register' + elif i['name'] == T('Lost password?'): + k = 'request_reset_password' + elif i['name'] == T('Forgot username?'): + k = 'retrieve_username' + elif i['name'] == T('Logout'): + k = 'logout' + elif i['name'] == T('Profile'): + k = 'profile' + elif i['name'] == T('Password'): + k = 'change_password' + + bare[k] = i['href'] + + self.bar = bare + + options = {'asmenu': menu, + 'dropdown': bootstrap, + 'bare': bare + } # Define custom modes. - options = {'asmenu' : menu, - 'dropdown' : bootstrap - } #Define custom modes. try: options[mode]() - except KeyError: #KeyError if mode is not in options (do Default) + except KeyError: # KeyError if mode is not in options (do Default) if self.user_id: - self.bar = SPAN(prefix, user_identifier, '[', Anr(items[0]['name'], _href=items[0]['href']), ']', _class='auth_navbar') + self.bar = SPAN(prefix, user_identifier, '[', + Anr(items[0]['name'], + _href=items[0]['href']), ']', + _class='auth_navbar') else: - self.bar = SPAN('[', Anr(items[0]['name'], _href=items[0]['href']), ']', _class='auth_navbar') + self.bar = SPAN('[', Anr(items[0]['name'], + _href=items[0]['href']), ']', + _class='auth_navbar') del items[0] for item in items: self.bar.insert(-1, ']') @@ -1436,9 +1549,11 @@ class Auth(object): does automatically. """ - tables = [table for table in tables] for table in tables: - if '_id' in table.fields() and 'modified_on' in table.fields() and not current_record in table.fields(): + fieldnames = table.fields() + if ('id' in fieldnames and + 'modified_on' in fieldnames and + not current_record in fieldnames): table._enable_record_versioning( archive_db=archive_db, archive_name=archive_names, @@ -5514,7 +5629,7 @@ class Wiki(object): return dict(content=content) def media(self, id): - request, db = current.request, self.auth.db + request, response, db = current.request, current.response, self.auth.db media = db.wiki_media(id) if media: if self.settings.manage_permissions: @@ -5522,7 +5637,15 @@ class Wiki(object): if not self.can_read(page): return self.not_authorized(page) request.args = [media.filename] - return current.response.download(request, db) + m = response.download(request, db) + current.session.forget() # get rid of the cookie + response.headers['Last-Modified'] = \ + request.utcnow.strftime("%a, %d %b %Y %H:%M:%S GMT") + if 'Content-Disposition' in response.headers: + del response.headers['Content-Disposition'] + response.headers['Pragma'] = 'cache' + response.headers['Cache-Control'] = 'private' + return m else: raise HTTP(404)