From cdf8ee8d853da5e14b01245c545cc559e2c3fa9c Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 26 Jun 2013 05:12:47 -0500 Subject: [PATCH] web2py.js with no inline code, thanks Niphlod (this upgrade required upgrading web2py.js --- Makefile | 2 +- VERSION | 2 +- applications/admin/static/js/web2py.js | 829 ++++++++++++++++------ applications/examples/static/js/web2py.js | 829 ++++++++++++++++------ applications/welcome/static/js/web2py.js | 829 ++++++++++++++++------ gluon/html.py | 38 +- gluon/tests/test_html.py | 5 +- 7 files changed, 1829 insertions(+), 705 deletions(-) diff --git a/Makefile b/Makefile index 844548ac..aa14e01b 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ update: echo "remember that pymysql was tweaked" src: ### Use semantic versioning - echo 'Version 2.5.1-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION + echo 'Version 2.6.0-development+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION ### rm -f all junk files make clean ### clean up baisc apps diff --git a/VERSION b/VERSION index 1f0bfa2d..34a64fc1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.5.1-stable+timestamp.2013.06.26.04.01.26 +Version 2.6.0-development+timestamp.2013.06.26.05.11.38 diff --git a/applications/admin/static/js/web2py.js b/applications/admin/static/js/web2py.js index d6a59c33..3641a01f 100644 --- a/applications/admin/static/js/web2py.js +++ b/applications/admin/static/js/web2py.js @@ -1,242 +1,617 @@ -function popup(url) { - newwindow=window.open(url,'name','height=400,width=600'); - if (window.focus) newwindow.focus(); - return false; -} -function collapse(id) { jQuery('#'+id).slideToggle(); } -function fade(id,value) { if(value>0) jQuery('#'+id).hide().fadeIn('slow'); else jQuery('#'+id).show().fadeOut('slow'); } -function ajax(u,s,t) { - query = ''; - if (typeof s == "string") { - d = jQuery(s).serialize(); - if(d){ query = d; } - } else { +(function ($, undefined) { + /* + * Unobtrusive scripting adapter for jQuery, largely taken from + * the wonderful https://github.com/rails/jquery-ujs + * + * + * Released under the MIT license + * + */ + + String.prototype.reverse = function () { + return this.split('').reverse().join(''); + }; + var web2py; + + $.web2py = web2py = { + + popup: function (url) { + newwindow = window.open(url, 'name', 'height=400,width=600'); + if(window.focus) newwindow.focus(); + return false; + }, + collapse: function () { + $('#' + id).slideToggle(); + }, + fade: function (id, value) { + if(value > 0) $('#' + id).hide().fadeIn('slow'); + else $('#' + id).show().fadeOut('slow'); + }, + ajax: function (u, s, t) { + query = ''; + if(typeof s == "string") { + d = $(s).serialize(); + if(d) { + query = d; + } + } else { pcs = []; - if (s != null && s != undefined) for(i=0; i 0) { + query = pcs.join("&"); } - if (pcs.length>0){query = pcs.join("&");} - } - jQuery.ajax({type: "POST", url: u, data: query, success: function(msg) { if(t) { if(t==':eval') eval(msg); else if(typeof t=='string') jQuery("#"+t).html(msg); else t(msg); } } }); -} - -String.prototype.reverse = function () { return this.split('').reverse().join('');}; -function web2py_ajax_fields(target) { - 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"; - jQuery("input.date",target).each(function() {Calendar.setup({inputField:this, ifFormat:date_format, showsTime:false });}); - jQuery("input.datetime",target).each(function() {Calendar.setup({inputField:this, ifFormat:datetime_format, showsTime: true, timeFormat: "24" });}); - jQuery("input.time",target).each(function(){jQuery(this).timeEntry();}); - -}; - -function web2py_ajax_init(target) { - jQuery('.hidden', target).hide(); - jQuery('.error', target).hide().slideDown('slow'); - web2py_ajax_fields(target); - web2py_show_if(target); -}; - -function web2py_event_handlers() { - var doc = jQuery(document) - doc.on('click', '.flash', function(e){var t=jQuery(this); if(t.css('top')=='0px') t.slideUp('slow'); else t.fadeOut(); e.preventDefault();}); - doc.on('keyup', 'input.integer', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();}); - doc.on('keyup', 'input.double, input.decimal', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();}); - 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(!confirm(confirm_message)) this.checked=false;}); - - 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) { - jQuery('.flash') - .html(decodeURIComponent(flash)) - .append('×') - .slideDown(); - } - }); - - doc.ajaxError(function(e, xhr, settings, exception) { - doc.off('click', '.flash') - switch(xhr.status){ - case 500: - $('.flash').html(ajax_error_500).slideDown(); } - }); -}; - -jQuery(function() { - var flash = jQuery('.flash'); - flash.hide(); - if(flash.html()) flash.append('×').slideDown(); - web2py_ajax_init(document); - web2py_event_handlers(); -}); - -function web2py_trap_form(action,target) { - jQuery('#'+target+' form').each(function(i){ - var form=jQuery(this); - if(!form.hasClass('no_trap')) - form.submit(function(e){ - jQuery('.flash').hide().html(''); - web2py_ajax_page('post',action,form.serialize(),target); - e.preventDefault(); + $.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); + } + } }); - }); -} - -function web2py_trap_link(target) { - jQuery('#'+target+' a.w2p_trap').each(function(i){ - var link=jQuery(this); - link.click(function(e) { - jQuery('.flash').hide().html(''); - web2py_ajax_page('get',link.attr('href'),[],target); - e.preventDefault(); - }); + }, + 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 + */ + 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 () { + Calendar.setup({ + inputField: this, + ifFormat: date_format, + showsTime: false }); -} + }); + $("input.datetime", target).each(function () { + Calendar.setup({ + inputField: this, + ifFormat: datetime_format, + showsTime: true, + timeFormat: "24" + }); + }); + $("input.time", target).each(function () { + $(this).timeEntry(); + }); + /*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*/ + $('input[type=password][data-w2p_entropy]', target).each(function () { + web2py.validate_entropy($(this)); + }); + /*no more inline 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 web2py_ajax_page(method, action, data, target) { - jQuery.ajax({'type':method, 'url':action, 'data':data, - 'beforeSend':function(xhr) { - xhr.setRequestHeader('web2py-component-location', document.location); - xhr.setRequestHeader('web2py-component-element', target);}, - 'complete':function(xhr,text){ - var html=xhr.responseText; - var content=xhr.getResponseHeader('web2py-component-content'); - var t = jQuery('#'+target); - if(content=='prepend') t.prepend(html); - else if(content=='append') t.append(html); - else if(content!='hide') t.html(html); - web2py_trap_form(action,target); - web2py_trap_link(target); - web2py_ajax_init('#'+target); - } - }); -} + 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 web2py_component(action, target, timeout, times){ - jQuery(function(){ - var jelement = jQuery("#" + target); - var element = jelement.get(0); - var statement = "jQuery('#" + target + "').get(0).reload();"; - jelement.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);} }; // reload - // 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; - } + function ml(ul) { + var line = $(ul).find("li:first").clone(true); + line.find(':text').val(''); + return line; + } + + function rel(ul) { + $(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) { + $('.hidden', target).hide(); + web2py.manage_errors(target); + web2py.ajax_fields(target); + }, + //manage errors in forms + manage_errors: function(target) { + $('.error', target).hide().slideDown('slow'); + //$('.error', target).hide().fadeIn('slow'); + }, + event_handlers: function () { + /* This is called once for page + * Ideally it should bound all the things that are needed + */ + var doc = $(document); + doc.on('click', '.flash', function (e) { + console.log('das'); + 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(); + }); + doc.on('keyup', 'input.integer', function () { + this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse(); + }); + doc.on('keyup', 'input.double, input.decimal', function () { + this.value = this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g, '').reverse(); + }); + 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; + }); + + 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)) + } + }); + + 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) { + $('#' + 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...'); + form.submit(function (e) { + web2py.hide_flash(); + web2py.ajax_page('post', action, form.serialize(), target, form); + e.preventDefault(); + }); + } + }); + }, + trap_link: function (target) { + $('#' + target + ' a.w2p_trap').each(function (i) { + var link = $(this); + link.click(function (e) { + web2py.hide_flash(); + web2py.ajax_page('get', link.attr('href'), [], target); + 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(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.... + 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) + element.trigger('ajax:error', [xhr, status, error]); + }, + 'complete': function (xhr, status) { + element.trigger('ajax:complete', [xhr, status]); + var html = xhr.responseText; + var content = xhr.getResponseHeader('web2py-component-content'); + var t = $('#' + target); + if(content == 'prepend') t.prepend(html); + else if(content == 'append') t.append(html); + else if(content != 'hide') t.html(html); + web2py.trap_form(action, target); + web2py.trap_link(target); + web2py.ajax_init('#' + target); + } + }); + } + }, + 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(this.reload_check()) { + web2py.ajax_page('get', action, null, target, el); + } + }; // reload + // 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;}; // reload check - 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); - element.run_once = false; - element.timing = setInterval(statement, timeout); - element.reload_counter -= 1; + } else { + return true; + } + } + } + return false; + }; // reload check + 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); } - else if (times == 1) { - // Run once with timeout - element.run_once = true; - element.setTimeout = setTimeout; - element.timing = setTimeout(statement, timeout); + }); + }, + 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; } - } else { - // run once (no timeout specified) - element.reload_counter = Infinity; - web2py_ajax_page('get', action, null, target); - } }); } + 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) { + //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; + 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 jquery-ujs + 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, name, data) { + var event = $.Event(name); + 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'; + // 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...'); + } + // set to disabled state + el[method](el.data('w2p_disable_with')); -function web2py_websocket(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 -} + 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'); // clean up cache + } + el.removeClass('disabled'); + el.unbind('click.w2pDisable'); // enable element + }, + //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').hide().html(''); + }, + 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'); -function web2py_calc_entropy(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;i0) jQuery('#'+id).hide().fadeIn('slow'); else jQuery('#'+id).show().fadeOut('slow'); } -function ajax(u,s,t) { - query = ''; - if (typeof s == "string") { - d = jQuery(s).serialize(); - if(d){ query = d; } - } else { +(function ($, undefined) { + /* + * Unobtrusive scripting adapter for jQuery, largely taken from + * the wonderful https://github.com/rails/jquery-ujs + * + * + * Released under the MIT license + * + */ + + String.prototype.reverse = function () { + return this.split('').reverse().join(''); + }; + var web2py; + + $.web2py = web2py = { + + popup: function (url) { + newwindow = window.open(url, 'name', 'height=400,width=600'); + if(window.focus) newwindow.focus(); + return false; + }, + collapse: function () { + $('#' + id).slideToggle(); + }, + fade: function (id, value) { + if(value > 0) $('#' + id).hide().fadeIn('slow'); + else $('#' + id).show().fadeOut('slow'); + }, + ajax: function (u, s, t) { + query = ''; + if(typeof s == "string") { + d = $(s).serialize(); + if(d) { + query = d; + } + } else { pcs = []; - if (s != null && s != undefined) for(i=0; i 0) { + query = pcs.join("&"); } - if (pcs.length>0){query = pcs.join("&");} - } - jQuery.ajax({type: "POST", url: u, data: query, success: function(msg) { if(t) { if(t==':eval') eval(msg); else if(typeof t=='string') jQuery("#"+t).html(msg); else t(msg); } } }); -} - -String.prototype.reverse = function () { return this.split('').reverse().join('');}; -function web2py_ajax_fields(target) { - 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"; - jQuery("input.date",target).each(function() {Calendar.setup({inputField:this, ifFormat:date_format, showsTime:false });}); - jQuery("input.datetime",target).each(function() {Calendar.setup({inputField:this, ifFormat:datetime_format, showsTime: true, timeFormat: "24" });}); - jQuery("input.time",target).each(function(){jQuery(this).timeEntry();}); - -}; - -function web2py_ajax_init(target) { - jQuery('.hidden', target).hide(); - jQuery('.error', target).hide().slideDown('slow'); - web2py_ajax_fields(target); - web2py_show_if(target); -}; - -function web2py_event_handlers() { - var doc = jQuery(document) - doc.on('click', '.flash', function(e){var t=jQuery(this); if(t.css('top')=='0px') t.slideUp('slow'); else t.fadeOut(); e.preventDefault();}); - doc.on('keyup', 'input.integer', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();}); - doc.on('keyup', 'input.double, input.decimal', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();}); - 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(!confirm(confirm_message)) this.checked=false;}); - - 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) { - jQuery('.flash') - .html(decodeURIComponent(flash)) - .append('×') - .slideDown(); - } - }); - - doc.ajaxError(function(e, xhr, settings, exception) { - doc.off('click', '.flash') - switch(xhr.status){ - case 500: - $('.flash').html(ajax_error_500).slideDown(); } - }); -}; - -jQuery(function() { - var flash = jQuery('.flash'); - flash.hide(); - if(flash.html()) flash.append('×').slideDown(); - web2py_ajax_init(document); - web2py_event_handlers(); -}); - -function web2py_trap_form(action,target) { - jQuery('#'+target+' form').each(function(i){ - var form=jQuery(this); - if(!form.hasClass('no_trap')) - form.submit(function(e){ - jQuery('.flash').hide().html(''); - web2py_ajax_page('post',action,form.serialize(),target); - e.preventDefault(); + $.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); + } + } }); - }); -} - -function web2py_trap_link(target) { - jQuery('#'+target+' a.w2p_trap').each(function(i){ - var link=jQuery(this); - link.click(function(e) { - jQuery('.flash').hide().html(''); - web2py_ajax_page('get',link.attr('href'),[],target); - e.preventDefault(); - }); + }, + 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 + */ + 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 () { + Calendar.setup({ + inputField: this, + ifFormat: date_format, + showsTime: false }); -} + }); + $("input.datetime", target).each(function () { + Calendar.setup({ + inputField: this, + ifFormat: datetime_format, + showsTime: true, + timeFormat: "24" + }); + }); + $("input.time", target).each(function () { + $(this).timeEntry(); + }); + /*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*/ + $('input[type=password][data-w2p_entropy]', target).each(function () { + web2py.validate_entropy($(this)); + }); + /*no more inline 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 web2py_ajax_page(method, action, data, target) { - jQuery.ajax({'type':method, 'url':action, 'data':data, - 'beforeSend':function(xhr) { - xhr.setRequestHeader('web2py-component-location', document.location); - xhr.setRequestHeader('web2py-component-element', target);}, - 'complete':function(xhr,text){ - var html=xhr.responseText; - var content=xhr.getResponseHeader('web2py-component-content'); - var t = jQuery('#'+target); - if(content=='prepend') t.prepend(html); - else if(content=='append') t.append(html); - else if(content!='hide') t.html(html); - web2py_trap_form(action,target); - web2py_trap_link(target); - web2py_ajax_init('#'+target); - } - }); -} + 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 web2py_component(action, target, timeout, times){ - jQuery(function(){ - var jelement = jQuery("#" + target); - var element = jelement.get(0); - var statement = "jQuery('#" + target + "').get(0).reload();"; - jelement.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);} }; // reload - // 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; - } + function ml(ul) { + var line = $(ul).find("li:first").clone(true); + line.find(':text').val(''); + return line; + } + + function rel(ul) { + $(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) { + $('.hidden', target).hide(); + web2py.manage_errors(target); + web2py.ajax_fields(target); + }, + //manage errors in forms + manage_errors: function(target) { + $('.error', target).hide().slideDown('slow'); + //$('.error', target).hide().fadeIn('slow'); + }, + event_handlers: function () { + /* This is called once for page + * Ideally it should bound all the things that are needed + */ + var doc = $(document); + doc.on('click', '.flash', function (e) { + console.log('das'); + 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(); + }); + doc.on('keyup', 'input.integer', function () { + this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse(); + }); + doc.on('keyup', 'input.double, input.decimal', function () { + this.value = this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g, '').reverse(); + }); + 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; + }); + + 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)) + } + }); + + 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) { + $('#' + 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...'); + form.submit(function (e) { + web2py.hide_flash(); + web2py.ajax_page('post', action, form.serialize(), target, form); + e.preventDefault(); + }); + } + }); + }, + trap_link: function (target) { + $('#' + target + ' a.w2p_trap').each(function (i) { + var link = $(this); + link.click(function (e) { + web2py.hide_flash(); + web2py.ajax_page('get', link.attr('href'), [], target); + 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(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.... + 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) + element.trigger('ajax:error', [xhr, status, error]); + }, + 'complete': function (xhr, status) { + element.trigger('ajax:complete', [xhr, status]); + var html = xhr.responseText; + var content = xhr.getResponseHeader('web2py-component-content'); + var t = $('#' + target); + if(content == 'prepend') t.prepend(html); + else if(content == 'append') t.append(html); + else if(content != 'hide') t.html(html); + web2py.trap_form(action, target); + web2py.trap_link(target); + web2py.ajax_init('#' + target); + } + }); + } + }, + 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(this.reload_check()) { + web2py.ajax_page('get', action, null, target, el); + } + }; // reload + // 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;}; // reload check - 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); - element.run_once = false; - element.timing = setInterval(statement, timeout); - element.reload_counter -= 1; + } else { + return true; + } + } + } + return false; + }; // reload check + 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); } - else if (times == 1) { - // Run once with timeout - element.run_once = true; - element.setTimeout = setTimeout; - element.timing = setTimeout(statement, timeout); + }); + }, + 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; } - } else { - // run once (no timeout specified) - element.reload_counter = Infinity; - web2py_ajax_page('get', action, null, target); - } }); } + 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) { + //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; + 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 jquery-ujs + 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, name, data) { + var event = $.Event(name); + 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'; + // 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...'); + } + // set to disabled state + el[method](el.data('w2p_disable_with')); -function web2py_websocket(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 -} + 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'); // clean up cache + } + el.removeClass('disabled'); + el.unbind('click.w2pDisable'); // enable element + }, + //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').hide().html(''); + }, + 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'); -function web2py_calc_entropy(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;i0) jQuery('#'+id).hide().fadeIn('slow'); else jQuery('#'+id).show().fadeOut('slow'); } -function ajax(u,s,t) { - query = ''; - if (typeof s == "string") { - d = jQuery(s).serialize(); - if(d){ query = d; } - } else { +(function ($, undefined) { + /* + * Unobtrusive scripting adapter for jQuery, largely taken from + * the wonderful https://github.com/rails/jquery-ujs + * + * + * Released under the MIT license + * + */ + + String.prototype.reverse = function () { + return this.split('').reverse().join(''); + }; + var web2py; + + $.web2py = web2py = { + + popup: function (url) { + newwindow = window.open(url, 'name', 'height=400,width=600'); + if(window.focus) newwindow.focus(); + return false; + }, + collapse: function () { + $('#' + id).slideToggle(); + }, + fade: function (id, value) { + if(value > 0) $('#' + id).hide().fadeIn('slow'); + else $('#' + id).show().fadeOut('slow'); + }, + ajax: function (u, s, t) { + query = ''; + if(typeof s == "string") { + d = $(s).serialize(); + if(d) { + query = d; + } + } else { pcs = []; - if (s != null && s != undefined) for(i=0; i 0) { + query = pcs.join("&"); } - if (pcs.length>0){query = pcs.join("&");} - } - jQuery.ajax({type: "POST", url: u, data: query, success: function(msg) { if(t) { if(t==':eval') eval(msg); else if(typeof t=='string') jQuery("#"+t).html(msg); else t(msg); } } }); -} - -String.prototype.reverse = function () { return this.split('').reverse().join('');}; -function web2py_ajax_fields(target) { - 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"; - jQuery("input.date",target).each(function() {Calendar.setup({inputField:this, ifFormat:date_format, showsTime:false });}); - jQuery("input.datetime",target).each(function() {Calendar.setup({inputField:this, ifFormat:datetime_format, showsTime: true, timeFormat: "24" });}); - jQuery("input.time",target).each(function(){jQuery(this).timeEntry();}); - -}; - -function web2py_ajax_init(target) { - jQuery('.hidden', target).hide(); - jQuery('.error', target).hide().slideDown('slow'); - web2py_ajax_fields(target); - web2py_show_if(target); -}; - -function web2py_event_handlers() { - var doc = jQuery(document) - doc.on('click', '.flash', function(e){var t=jQuery(this); if(t.css('top')=='0px') t.slideUp('slow'); else t.fadeOut(); e.preventDefault();}); - doc.on('keyup', 'input.integer', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();}); - doc.on('keyup', 'input.double, input.decimal', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();}); - 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(!confirm(confirm_message)) this.checked=false;}); - - 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) { - jQuery('.flash') - .html(decodeURIComponent(flash)) - .append('×') - .slideDown(); - } - }); - - doc.ajaxError(function(e, xhr, settings, exception) { - doc.off('click', '.flash') - switch(xhr.status){ - case 500: - $('.flash').html(ajax_error_500).slideDown(); } - }); -}; - -jQuery(function() { - var flash = jQuery('.flash'); - flash.hide(); - if(flash.html()) flash.append('×').slideDown(); - web2py_ajax_init(document); - web2py_event_handlers(); -}); - -function web2py_trap_form(action,target) { - jQuery('#'+target+' form').each(function(i){ - var form=jQuery(this); - if(!form.hasClass('no_trap')) - form.submit(function(e){ - jQuery('.flash').hide().html(''); - web2py_ajax_page('post',action,form.serialize(),target); - e.preventDefault(); + $.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); + } + } }); - }); -} - -function web2py_trap_link(target) { - jQuery('#'+target+' a.w2p_trap').each(function(i){ - var link=jQuery(this); - link.click(function(e) { - jQuery('.flash').hide().html(''); - web2py_ajax_page('get',link.attr('href'),[],target); - e.preventDefault(); - }); + }, + 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 + */ + 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 () { + Calendar.setup({ + inputField: this, + ifFormat: date_format, + showsTime: false }); -} + }); + $("input.datetime", target).each(function () { + Calendar.setup({ + inputField: this, + ifFormat: datetime_format, + showsTime: true, + timeFormat: "24" + }); + }); + $("input.time", target).each(function () { + $(this).timeEntry(); + }); + /*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*/ + $('input[type=password][data-w2p_entropy]', target).each(function () { + web2py.validate_entropy($(this)); + }); + /*no more inline 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 web2py_ajax_page(method, action, data, target) { - jQuery.ajax({'type':method, 'url':action, 'data':data, - 'beforeSend':function(xhr) { - xhr.setRequestHeader('web2py-component-location', document.location); - xhr.setRequestHeader('web2py-component-element', target);}, - 'complete':function(xhr,text){ - var html=xhr.responseText; - var content=xhr.getResponseHeader('web2py-component-content'); - var t = jQuery('#'+target); - if(content=='prepend') t.prepend(html); - else if(content=='append') t.append(html); - else if(content!='hide') t.html(html); - web2py_trap_form(action,target); - web2py_trap_link(target); - web2py_ajax_init('#'+target); - } - }); -} + 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 web2py_component(action, target, timeout, times){ - jQuery(function(){ - var jelement = jQuery("#" + target); - var element = jelement.get(0); - var statement = "jQuery('#" + target + "').get(0).reload();"; - jelement.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);} }; // reload - // 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; - } + function ml(ul) { + var line = $(ul).find("li:first").clone(true); + line.find(':text').val(''); + return line; + } + + function rel(ul) { + $(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) { + $('.hidden', target).hide(); + web2py.manage_errors(target); + web2py.ajax_fields(target); + }, + //manage errors in forms + manage_errors: function(target) { + $('.error', target).hide().slideDown('slow'); + //$('.error', target).hide().fadeIn('slow'); + }, + event_handlers: function () { + /* This is called once for page + * Ideally it should bound all the things that are needed + */ + var doc = $(document); + doc.on('click', '.flash', function (e) { + console.log('das'); + 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(); + }); + doc.on('keyup', 'input.integer', function () { + this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse(); + }); + doc.on('keyup', 'input.double, input.decimal', function () { + this.value = this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g, '').reverse(); + }); + 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; + }); + + 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)) + } + }); + + 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) { + $('#' + 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...'); + form.submit(function (e) { + web2py.hide_flash(); + web2py.ajax_page('post', action, form.serialize(), target, form); + e.preventDefault(); + }); + } + }); + }, + trap_link: function (target) { + $('#' + target + ' a.w2p_trap').each(function (i) { + var link = $(this); + link.click(function (e) { + web2py.hide_flash(); + web2py.ajax_page('get', link.attr('href'), [], target); + 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(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.... + 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) + element.trigger('ajax:error', [xhr, status, error]); + }, + 'complete': function (xhr, status) { + element.trigger('ajax:complete', [xhr, status]); + var html = xhr.responseText; + var content = xhr.getResponseHeader('web2py-component-content'); + var t = $('#' + target); + if(content == 'prepend') t.prepend(html); + else if(content == 'append') t.append(html); + else if(content != 'hide') t.html(html); + web2py.trap_form(action, target); + web2py.trap_link(target); + web2py.ajax_init('#' + target); + } + }); + } + }, + 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(this.reload_check()) { + web2py.ajax_page('get', action, null, target, el); + } + }; // reload + // 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;}; // reload check - 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); - element.run_once = false; - element.timing = setInterval(statement, timeout); - element.reload_counter -= 1; + } else { + return true; + } + } + } + return false; + }; // reload check + 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); } - else if (times == 1) { - // Run once with timeout - element.run_once = true; - element.setTimeout = setTimeout; - element.timing = setTimeout(statement, timeout); + }); + }, + 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; } - } else { - // run once (no timeout specified) - element.reload_counter = Infinity; - web2py_ajax_page('get', action, null, target); - } }); } + 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) { + //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; + 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 jquery-ujs + 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, name, data) { + var event = $.Event(name); + 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'; + // 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...'); + } + // set to disabled state + el[method](el.data('w2p_disable_with')); -function web2py_websocket(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 -} + 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'); // clean up cache + } + el.removeClass('disabled'); + el.unbind('click.w2pDisable'); // enable element + }, + //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').hide().html(''); + }, + 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'); -function web2py_calc_entropy(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>> from validators import * >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml() - + >>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml()
  • doc
  • cat
  • mouse
>>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml() diff --git a/gluon/tests/test_html.py b/gluon/tests/test_html.py index 23b4c83c..51a75526 100644 --- a/gluon/tests/test_html.py +++ b/gluon/tests/test_html.py @@ -46,7 +46,7 @@ class TestBareHelpers(unittest.TestCase): def testA(self): self.assertEqual(A('<>', _a='1', _b='2').xml(), - '<>') + '<>') def testB(self): self.assertEqual(B('<>', _a='1', _b='2').xml(), @@ -209,8 +209,7 @@ class TestBareHelpers(unittest.TestCase): class TestData(unittest.TestCase): def testAdata(self): - self.assertEqual(A('<>', data=dict(abc='', cde='standard'), _a='1', _b='2').xml(), - '<>') + self.assertEqual(A('<>', data=dict(abc='', cde='standard'), _a='1', _b='2').xml(),'<>') if __name__ == '__main__':