From 29f042888973510be25d80b8dd27fe020124b33f Mon Sep 17 00:00:00 2001 From: mdipierro Date: Tue, 25 Jun 2013 09:10:03 -0500 Subject: [PATCH 01/17] fixed Issue 1560:Smartgrid doesn work with Google app engine 1.8.1 --- VERSION | 2 +- gluon/sqlhtml.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 8477ccdf..a1f7d9b7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.5.1-stable+timestamp.2013.06.24.14.23.41 +Version 2.5.1-stable+timestamp.2013.06.25.09.09.16 diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index cb1c1f3b..2f99adc5 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -2615,7 +2615,7 @@ class SQLFORM(FORM): except (KeyError, ValueError, TypeError): redirect(URL(args=table._tablename)) if nargs == len(args) + 1: - query = table._id != None + query = table._db._adapter.id_query(table) # filter out data info for displayed table if table._tablename in constraints: From 198dc8e0cd4a95f8d404cb7e5bc3ecfcd6a5eab6 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Tue, 25 Jun 2013 15:40:06 -0500 Subject: [PATCH 02/17] quoting table names --- VERSION | 2 +- gluon/dal.py | 31 ++++++++++++++++++++----------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/VERSION b/VERSION index a1f7d9b7..6674efce 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.5.1-stable+timestamp.2013.06.25.09.09.16 +Version 2.5.1-stable+timestamp.2013.06.25.15.39.19 diff --git a/gluon/dal.py b/gluon/dal.py index b837a50e..cb9f7e97 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -644,6 +644,8 @@ class BaseAdapter(ConnectionPool): TRUE = 'T' FALSE = 'F' T_SEP = ' ' + QUOTE_TEMPLATE = '"%s"' + types = { 'boolean': 'CHAR(1)', 'string': 'CHAR(%(length)s)', @@ -1046,7 +1048,7 @@ class BaseAdapter(ConnectionPool): if self.dbengine in ('postgres',) and ftype.startswith('geometry'): geotype, parms = ftype[:-1].split('(') schema = parms.split(',')[0] - query = [ "SELECT DropGeometryColumn ('%(schema)s', '%(table)s', '%(field)s');" % + query = [ "SELECT DropGeometryColumn ('%(schema)s', '%(table)s', '%(field)s');" % dict(schema=schema, table=tablename, field=key,) ] elif self.dbengine in ('firebird',): query = ['ALTER TABLE %s DROP %s;' % (tablename, key)] @@ -1097,7 +1099,7 @@ class BaseAdapter(ConnectionPool): db.commit() self.save_dbt(table,sql_fields_current) logfile.write('success!\n') - + elif metadata_change: self.save_dbt(table,sql_fields_current) @@ -1255,7 +1257,7 @@ class BaseAdapter(ConnectionPool): return '(%s LIKE %s)' % (self.expand(first), self.expand('%'+second, 'string')) - def CONTAINS(self,first,second,case_sensitive=False): + def CONTAINS(self,first,second,case_sensitive=False): if first.type in ('string','text', 'json'): if isinstance(second,Expression): second = Expression(None,self.CONCAT('%',Expression( @@ -1361,7 +1363,7 @@ class BaseAdapter(ConnectionPool): def expand(self, expression, field_type=None): if isinstance(expression, Field): - out = '%s.%s' % (expression.tablename, expression.name) + out = '%s.%s' % (expression.table, expression.name) if field_type == 'string' and not expression.type in ( 'string','text','json','password'): out = 'CAST(%s AS %s)' % (out, self.types['text']) @@ -1448,6 +1450,7 @@ class BaseAdapter(ConnectionPool): sql_v = ','.join(['%s=%s' % (field.name, self.expand(value, field.type)) \ for (field, value) in fields]) + tablename = "%s" % self.db[tablename] return 'UPDATE %s SET %s%s;' % (tablename, sql_v, sql_w) def update(self, tablename, query, fields): @@ -2433,6 +2436,8 @@ class MySQLAdapter(BaseAdapter): 'big-reference': 'BIGINT, INDEX %(index_name)s (%(field_name)s), FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', } + QUOTE_TEMPLATE = "`%s`" + def varquote(self,name): return varquote_aux(name,'`%s`') @@ -3082,6 +3087,8 @@ class MSSQLAdapter(BaseAdapter): drivers = ('pyodbc',) T_SEP = 'T' + QUOTE_TEMPLATE = "[%s]" + types = { 'boolean': 'BIT', 'string': 'VARCHAR(%(length)s)', @@ -8492,9 +8499,10 @@ class Table(object): def __str__(self): if self._ot is not None: - if 'Oracle' in str(type(self._db._adapter)): # <<< patch - return '%s %s' % (self._ot, self._tablename) # <<< patch - return '%s AS %s' % (self._ot, self._tablename) + return self._db._adapter.QUOTE_TEMPLATE % self._ot + #if 'Oracle' in str(type(self._db._adapter)): # <<< patch + # return '%s %s' % (self._ot, self._tablename) # <<< patch + #return '%s AS %s' % (self._ot, self._tablename) return self._tablename def _drop(self, mode = ''): @@ -9959,7 +9967,7 @@ class Set(object): fields = table._listify(update_fields,update=True) if not fields: raise SyntaxError("No fields to update") - ret = db._adapter.update(tablename,self.query,fields) + ret = db._adapter.update("%s" % table,self.query,fields) ret and [f(self,update_fields) for f in table._after_update] return ret @@ -9971,7 +9979,8 @@ class Set(object): table = self.db[tablename] fields = table._listify(update_fields,update=True) if not fields: raise SyntaxError("No fields to update") - ret = self.db._adapter.update(tablename,self.query,fields) + + ret = self.db._adapter.update("%s" % table,self.query,fields) return ret def validate_and_update(self, **update_fields): @@ -10301,10 +10310,10 @@ class Rows(object): """ Takes an index and returns a copy of the indexed row with values transformed via the "represent" attributes of the associated fields. - + If no index is specified, a generator is returned for iteration over all the rows. - + fields -- a list of fields to transform (if None, all fields with "represent" attributes will be transformed). """ From 474af626f062717280ea0c3dfd23f8c0181dbc77 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Tue, 25 Jun 2013 16:56:25 -0500 Subject: [PATCH 03/17] fixed a bug in revision b1d117199fdb --- VERSION | 2 +- gluon/dal.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index 6674efce..71226a50 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.5.1-stable+timestamp.2013.06.25.15.39.19 +Version 2.5.1-stable+timestamp.2013.06.25.16.55.36 diff --git a/gluon/dal.py b/gluon/dal.py index cb9f7e97..8862167f 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -1069,12 +1069,13 @@ class BaseAdapter(ConnectionPool): drop_expr = 'ALTER TABLE %s DROP %s;' else: drop_expr = 'ALTER TABLE %s DROP COLUMN %s;' - query = ['ALTER TABLE %s ADD %s__tmp %s;' % (t, key, tt), - 'UPDATE %s SET %s__tmp=%s;' % (t, key, key), + key_tmp = key + '__tmp' + query = ['ALTER TABLE %s ADD %s %s;' % (t, key_tmp, tt), + 'UPDATE %s SET %s=%s;' % (t, key_tmp, key), drop_expr % (t, key), 'ALTER TABLE %s ADD %s %s;' % (t, key, tt), - 'UPDATE %s SET %s=%s__tmp;' % (t, key, key), - drop_expr % (t, key)] + 'UPDATE %s SET %s=%s;' % (t, key, key_tmp), + drop_expr % (t, key_tmp)] metadata_change = True elif sql_fields[key]['type'] != sql_fields_old[key]['type']: sql_fields_current[key] = sql_fields[key] From e3728da256a92859d6c2dad0a5d8798e54c2dda2 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 26 Jun 2013 02:54:31 -0500 Subject: [PATCH 04/17] better german translation for admin, thanks Michael Kallweit --- VERSION | 2 +- applications/admin/languages/de.py | 190 +++++++++++++++++++---------- 2 files changed, 129 insertions(+), 63 deletions(-) diff --git a/VERSION b/VERSION index 71226a50..937aa6f0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.5.1-stable+timestamp.2013.06.25.16.55.36 +Version 2.5.1-stable+timestamp.2013.06.26.02.53.33 diff --git a/applications/admin/languages/de.py b/applications/admin/languages/de.py index eddd03c8..d5750418 100644 --- a/applications/admin/languages/de.py +++ b/applications/admin/languages/de.py @@ -3,54 +3,62 @@ '!langcode!': 'de', '!langname!': 'Deutsch', '"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"Update" ist ein optionaler Ausdruck wie "Feld1 = \'newvalue". JOIN Ergebnisse können nicht aktualisiert oder gelöscht werden', -'%s %%{row} deleted': '%s Zeilen gelöscht', -'%s %%{row} updated': '%s Zeilen aktualisiert', -'%Y-%m-%d': '%Y-%m-%d', -'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', -'(requires internet access)': '(requires internet access)', +'%s %%{row} deleted': '%s %%{row} Zeilen gelöscht', +'%s %%{row} updated': '%s %%{row} Zeilen aktualisiert', +'%Y-%m-%d': '%d.%m.%Y', +'%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S', +'(requires internet access)': '(Internet Zugang wir benötigt)', +'(requires internet access, experimental)': '(benötigt Internet Zugang)', '(something like "it-it")': '(so etwas wie "it-it")', -'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files', +'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(file **gluon/contrib/plural_rules/%s.py** is not found)', +'@markmin\x01Searching: **%s** %%{file}': '@markmin\x01Suche: **%s** Dateien', 'A new version of web2py is available': 'Eine neue Version von web2py ist verfügbar', 'A new version of web2py is available: %s': 'Eine neue Version von web2py ist verfügbar: %s', 'Abort': 'Abbrechen', -'About': 'Über', -'About application': 'Über die Anwendung', -'Additional code for your application': 'Additional code for your application', -'additional code for your application': 'zusätzlicher Code für Ihre Anwendung', -'admin disabled because no admin password': ' admin ist deaktiviert, weil kein Admin-Passwort gesetzt ist', +'About': 'über', +'About application': 'über die Anwendung', +'Additional code for your application': 'zusätzlicher Code für Ihre Anwendung', +'admin disabled because no admin password': 'admin ist deaktiviert, weil kein Admin-Passwort gesetzt ist', 'admin disabled because not supported on google apps engine': 'admin ist deaktiviert, es existiert dafür keine Unterstützung auf der google apps engine', 'admin disabled because unable to access password file': 'admin ist deaktiviert, weil kein Zugriff auf die Passwortdatei besteht', 'Admin is disabled because insecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals', 'Admin is disabled because unsecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals', -'Admin language': 'Admin language', -'administrative interface': 'administrative interface', +'Admin language': 'Admin-Sprache', +'administrative interface': 'Administrative Schnittstelle', 'Administrator Password:': 'Administrator Passwort:', +'An error occured, please %s the page': 'Ein Fehler ist aufgetereten, bitte %s die Seite', 'and rename it (required):': 'und benenne sie um (erforderlich):', 'and rename it:': ' und benenne sie um:', 'appadmin': 'appadmin', 'appadmin is disabled because insecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals', +'Application': 'Anwendung', 'application "%s" uninstalled': 'Anwendung "%s" deinstalliert', 'application compiled': 'Anwendung kompiliert', 'application is compiled and cannot be designed': 'Die Anwendung ist kompiliert kann deswegen nicht mehr geändert werden', -'Application name:': 'Application name:', +'Application name:': 'Name der Applikation:', +'are not used': 'wird nicht verwendet', +'are not used yet': 'wird bisher nicht verwendet', 'Are you sure you want to delete file "%s"?': 'Sind Sie sich sicher, dass Sie diese Datei löschen wollen "%s"?', -'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?', +'Are you sure you want to delete this object?': 'Sind Sie sich sicher, dass Sie dieses Objekt löschen wollen?', 'Are you sure you want to uninstall application "%s"': 'Sind Sie sich sicher, dass Sie diese Anwendung deinstallieren wollen "%s"', 'Are you sure you want to uninstall application "%s"?': 'Sind Sie sich sicher, dass Sie diese Anwendung deinstallieren wollen "%s"?', 'Are you sure you want to upgrade web2py now?': 'Sind Sie sich sicher, dass Sie web2py jetzt upgraden möchten?', 'arguments': 'arguments', +'at char %s': 'at char %s', +'at line %s': 'at line %s', 'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ACHTUNG: Die Einwahl benötigt eine sichere (HTTPS) Verbindung. Es sei denn sie läuft Lokal(localhost).', 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ACHTUNG: Testen ist nicht threadsicher. Führen sie also nicht mehrere Tests gleichzeitig aus.', 'ATTENTION: This is an experimental feature and it needs more testing.': 'ACHTUNG: Dies ist eine experimentelle Funktion und benötigt noch weitere Tests.', 'ATTENTION: you cannot edit the running application!': 'ACHTUNG: Eine laufende Anwendung kann nicht editiert werden!', 'Authentication': 'Authentifizierung', -'Available databases and tables': 'Verfügbare Datenbanken und Tabellen', +'Available databases and tables': 'verfügbare Datenbanken und Tabellen', 'back': 'zurück', -'beautify': 'beautify', -'cache': 'Cache', +'beautify': 'verschönern', +'cache': 'Pufferspeicher', 'cache, errors and sessions cleaned': 'Zwischenspeicher (cache), Fehler und Sitzungen (sessions) gelöscht', -'call': 'call', -'can be a git repo': 'can be a git repo', +'call': 'Aufruf', +'can be a git repo': 'kann ein git Repository sein', +'Cancel': 'Cancel', 'Cannot be empty': 'Darf nicht leer sein', 'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Nicht Kompilierbar:Es sind Fehler in der Anwendung. Beseitigen Sie die Fehler und versuchen Sie es erneut.', 'cannot create file': 'Kann Datei nicht erstellen', @@ -62,29 +70,31 @@ 'Check for upgrades': 'check for upgrades', 'Check to delete': 'Markiere zum löschen', 'Checking for upgrades...': 'Auf Updates überprüfen...', -'Clean': 'löschen', +'Clean': 'leeren', 'click here for online examples': 'hier klicken für online Beispiele', 'click here for the administrative interface': 'hier klicken für die Administrationsoberfläche ', +'Click row to expand traceback': 'Klicke auf die Zeile für Fehlerverfolgung', 'click to check for upgrades': 'hier klicken um nach Upgrades zu suchen', 'Client IP': 'Client IP', 'code': 'code', -'collapse/expand all': 'collapse/expand all', +'collapse/expand all': 'alles zu- bzw. aufklappen', 'Compile': 'kompilieren', 'compiled application removed': 'kompilierte Anwendung gelöscht', 'Controller': 'Controller', 'Controllers': 'Controller', 'controllers': 'Controllers', 'Copyright': 'Urheberrecht', +'Count': 'Anzahl', 'Create': 'erstellen', 'create file with filename:': 'erzeuge Datei mit Dateinamen:', 'create new application:': 'erzeuge neue Anwendung:', 'Create new simple application': 'Erzeuge neue Anwendung', -'created by': 'created by', +'created by': 'erstellt von', 'crontab': 'crontab', 'Current request': 'Aktuelle Anfrage (request)', 'Current response': 'Aktuelle Antwort (response)', 'Current session': 'Aktuelle Sitzung (session)', -'currently running': 'currently running', +'currently running': 'aktuell in Betrieb', 'currently saved or': 'des derzeit gespeicherten oder', 'customize me!': 'pass mich an!', 'data uploaded': 'Daten hochgeladen', @@ -97,23 +107,27 @@ 'DB Model': 'DB Modell', 'Debug': 'Debug', 'defines tables': 'definiere Tabellen', -'Delete': 'Löschen', +'Delete': 'löschen', 'delete': 'löschen', 'delete all checked': 'lösche alle markierten', 'delete plugin': 'Plugin löschen', -'Delete:': 'Löschen:', -'Deploy': 'deploy', +'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)', +'Delete:': 'löschen:', +'Deploy': 'Installieren', 'Deploy on Google App Engine': 'Auf Google App Engine installieren', -'Deploy to OpenShift': 'Deploy to OpenShift', +'Deploy to OpenShift': 'Auf OpenShift installieren', 'Description': 'Beschreibung', 'design': 'design', 'DESIGN': 'design', 'Design for': 'Design für', +'Detailed traceback description': 'Detailed traceback description', 'direction: ltr': 'direction: ltr', -'Disable': 'Disable', +'Disable': 'Deaktivieren', +'docs': 'docs', 'documentation': 'Dokumentation', 'done!': 'fertig!', -'download layouts': 'download layouts', +'Download .w2p': 'Download .w2p', +'download layouts': 'Layouts herunterladen', 'download plugins': 'download plugins', 'E-mail': 'E-mail', 'EDIT': 'BEARBEITEN', @@ -128,16 +142,23 @@ 'Editing file': 'Bearbeite Datei', 'Editing file "%s"': 'Bearbeite Datei "%s"', 'Editing Language file': 'Sprachdatei bearbeiten', +'Enable': 'Enable', 'Enterprise Web Framework': 'Enterprise Web Framework', +'Error': 'Fehler', 'Error logs for "%(app)s"': 'Fehlerprotokoll für "%(app)s"', +'Error snapshot': 'Error snapshot', +'Error ticket': 'Error ticket', 'Errors': 'Fehler', 'escape': 'escape', 'Exception instance attributes': 'Atribute der Ausnahmeinstanz', 'Expand Abbreviation': 'Kürzel erweitern', 'export as csv file': 'Exportieren als CSV-Datei', 'exposes': 'stellt zur Verfügung', +'exposes:': 'exposes:', 'extends': 'erweitert', +'failed to compile file because:': 'failed to compile file because:', 'failed to reload module': 'neu laden des Moduls fehlgeschlagen', +'File': 'Datei', 'file "%(filename)s" created': 'Datei "%(filename)s" erstellt', 'file "%(filename)s" deleted': 'Datei "%(filename)s" gelöscht', 'file "%(filename)s" uploaded': 'Datei "%(filename)s" hochgeladen', @@ -148,25 +169,33 @@ 'file saved on %(time)s': 'Datei gespeichert am %(time)s', 'file saved on %s': 'Datei gespeichert auf %s', 'filter': 'filter', +'Find Next': 'Find Next', +'Find Previous': 'Find Previous', 'First name': 'Vorname', +'Frames': 'Frames', 'Functions with no doctests will result in [passed] tests.': 'Funktionen ohne doctests erzeugen [passed] in Tests', 'Get from URL:': 'Get from URL:', 'Git Pull': 'Git Pull', 'Git Push': 'Git Push', 'Go to Matching Pair': 'gehe zum übereinstimmenden Paar', +'Goto': 'Goto', +'graph model': 'graph model', 'Group ID': 'Gruppen ID', 'Hello World': 'Hallo Welt', 'Help': 'Hilfe', +'Hide/Show Translated strings': 'Hide/Show Translated strings', +'Home': 'Home', 'htmledit': 'htmledit', -'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Falls der obere Test eine Fehler-Ticketnummer enthält deutet das auf einen Fehler in der Ausführung des Controllers hin, noch bevor der Doctest ausgeführt werden konnte. Gewöhnlich führen fehlerhafte Einrückungen oder fehlerhafter Code ausserhalb der Funktion zu solchen Fehlern. Ein grüner Titel deutet darauf hin, dass alle Test(wenn sie vorhanden sind) erfolgreich durchlaufen wurden. In diesem Fall werden die Testresultate nicht angezeigt.', +'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Falls der obere Test eine Fehler-Ticketnummer enthält deutet das auf einen Fehler in der Ausführung des Controllers hin, noch bevor der Doctest ausgeführt werden konnte. Gewöhnlich Führen fehlerhafte Einrückungen oder fehlerhafter Code ausserhalb der Funktion zu solchen Fehlern. Ein grüner Titel deutet darauf hin, dass alle Test(wenn sie vorhanden sind) erfolgreich durchlaufen wurden. In diesem Fall werden die Testresultate nicht angezeigt.', 'If you answer "yes", be patient, it may take a while to download': '', 'If you answer yes, be patient, it may take a while to download': 'If you answer yes, be patient, it may take a while to download', 'Import/Export': 'Importieren/Exportieren', 'includes': 'Einfügen', 'Index': 'Index', 'index': 'index', -'insert new': 'neu einfügen', -'insert new %s': 'neu einfügen %s', +'insert new': 'neu Einfügen', +'insert new %s': 'neu Einfügen %s', +'inspect attributes': 'inspect attributes', 'Install': 'installieren', 'Installed applications': 'Installierte Anwendungen', 'internal error': 'interner Fehler', @@ -175,9 +204,10 @@ 'Invalid email': 'Ungültige Email', 'invalid password': 'Ungültiges Passwort', 'Invalid Query': 'Ungültige Abfrage', -'invalid request': 'ungültige Anfrage', -'invalid ticket': 'ungültiges Ticket', +'invalid request': 'Ungültige Anfrage', +'invalid ticket': 'Ungültiges Ticket', 'Key bindings': 'Tastenbelegungen', +'Key bindings for ZenCoding Plugin': 'Key bindings for ZenCoding Plugin', 'Key bindings for ZenConding Plugin': 'Tastenbelegungen für das ZenConding Plugin', 'language file "%(filename)s" created/updated': 'Sprachdatei "%(filename)s" erstellt/aktualisiert', 'Language files (static strings) updated': 'Sprachdatei (statisch Strings) aktualisiert', @@ -189,6 +219,7 @@ 'Layout': 'Layout', 'License for': 'Lizenz für', 'loading...': 'lade...', +'locals': 'locals', 'located in the file': 'located in Datei', 'Login': 'Anmelden', 'login': 'anmelden', @@ -196,7 +227,8 @@ 'Logout': 'abmelden', 'Lost Password': 'Passwort vergessen', 'lost password?': 'Passwort vergessen?', -'Main Menu': 'Menú principal', +'Main Menu': 'Menü principal', +'Manage': 'Verwalten', 'Match Pair': 'Paare finden', 'Menu Model': 'Menü Modell', 'merge': 'verbinden', @@ -207,22 +239,28 @@ 'modules': 'Module', 'Name': 'Name', 'new application "%s" created': 'neue Anwendung "%s" erzeugt', -'New application wizard': 'New application wizard', +'New application wizard': 'Neue Anwendung per Assistent', +'new plugin installed': 'new plugin installed', 'New Record': 'Neuer Datensatz', 'new record inserted': 'neuer Datensatz eingefügt', -'New simple application': 'New simple application', +'New simple application': 'Neue einfache Anwendung', 'next 100 rows': 'nächsten 100 Zeilen', 'Next Edit Point': 'nächster Bearbeitungsschritt', 'NO': 'NEIN', 'No databases in this application': 'Keine Datenbank in dieser Anwendung', +'No ticket_storage.txt found under /private folder': 'No ticket_storage.txt found under /private folder', +'online designer': 'online designer', +'or alternatively': 'or alternatively', +'Or Get from URL:': 'oder hole es von folgender URL:', 'or import from csv file': 'oder importieren von cvs Datei', 'or provide app url:': 'oder geben Sie eine Anwendungs-URL an:', 'or provide application url:': 'oder geben Sie eine Anwendungs-URL an:', 'Origin': 'Herkunft', -'Original/Translation': 'Original/Übersetzung', +'Original/Translation': 'Original/übersetzung', 'Overwrite installed app': 'installierte Anwendungen überschreiben', 'Pack all': 'verpacke alles', 'Pack compiled': 'Verpacke kompiliert', +'Pack custom': 'Verpacke individuell', 'pack plugin': 'Plugin verpacken', 'Password': 'Passwort', 'Peeking at file': 'Dateiansicht', @@ -230,9 +268,13 @@ 'Plugin "%s" in application': 'Plugin "%s" in Anwendung', 'plugins': 'plugins', 'Plugins': 'Plugins', +'Plural-Forms:': 'Plural-Forms:', 'Powered by': 'Unterstützt von', 'previous 100 rows': 'vorherige 100 zeilen', 'Previous Edit Point': 'vorheriger Bearbeitungsschritt', +'Private files': 'Private files', +'private files': 'private files', +'Project Progress': 'Projekt Fortschritt', 'Query:': 'Abfrage:', 'record': 'Datensatz', 'record does not exist': 'Datensatz existiert nicht', @@ -241,26 +283,39 @@ 'register': 'Registrierung', 'Register': 'registrieren', 'Registration key': 'Registrierungsschlüssel', -'Reload routes': 'Reload routes', -'Remove compiled': 'kompilat gelöscht', +'reload': 'Neu laden', +'Reload routes': 'Routen neu laden', +'Remove compiled': 'Bytecode löschen', +'Replace': 'Replace', +'Replace All': 'Replace All', +'request': 'request', 'Reset Password key': 'Passwortschlüssel zurücksetzen', 'Resolve Conflict file': 'bereinige Konflikt-Datei', +'response': 'Antwort', 'restore': 'wiederherstellen', 'revert': 'zurückkehren', 'Role': 'Rolle', 'Rows in table': 'Zeilen in Tabelle', 'Rows selected': 'Zeilen ausgewählt', -'Running on %s': 'Running on %s', +'rules are not defined': 'rules are not defined', +"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')", +'Running on %s': 'läuft auf %s', +'Save': 'Save', 'save': 'sichern', +'Save file:': 'Save file:', 'Save via Ajax': 'via Ajax sichern', 'Saved file hash:': 'Gespeicherter Datei-Hash:', +'Select Files to Package': 'Dateien zum Paketieren wählen', 'selected': 'ausgewählt(e)', -'session expired': 'Sitzung Abgelaufen', +'session': 'Sitzung', +'session expired': 'Sitzung abgelaufen', 'shell': 'shell', 'Site': 'Seite', 'some files could not be removed': 'einige Dateien konnten nicht gelöscht werden', -'Start wizard': 'start wizard', +'Start searching': 'Start searching', +'Start wizard': 'Assistent starten', 'state': 'Status', +'Static': 'Static', 'static': 'statische Dateien', 'Static files': 'statische Dateien', 'Stylesheet': 'Stylesheet', @@ -283,27 +338,35 @@ 'The data representation, define database tables and sets': 'The data representation, define database tables and sets', 'The output of the file is a dictionary that was rendered by the view': 'The output of the file is a dictionary that was rendered by the view', 'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates', -'the presentations layer, views are also known as templates': 'Die Präsentationsschicht, Views sind auch bekannt als Vorlagen/Templates', +'the presentations layer, views are also known as templates': 'Die präsentationsschicht, Views sind auch bekannt als Vorlagen/Templates', 'There are no controllers': 'Keine Controller vorhanden', 'There are no models': 'Keine Modelle vorhanden', 'There are no modules': 'Keine Module vorhanden', -'There are no plugins': 'There are no plugins', +'There are no plugins': 'Keine Plugins vorhanden', +'There are no private files': 'There are no private files', 'There are no static files': 'Keine statischen Dateien vorhanden', -'There are no translators, only default language is supported': 'Keine Übersetzungen vorhanden, nur die voreingestellte Sprache wird unterstützt', +'There are no translators, only default language is supported': 'Keine übersetzungen vorhanden, nur die voreingestellte Sprache wird Unterstützt', 'There are no views': 'Keine Views vorhanden', -'These files are served without processing, your images go here': 'These files are served without processing, your images go here', +'These files are not served, they are only available from within your app': 'These files are not served, they are only available from within your app', +'These files are served without processing, your images go here': 'Diese Dateien werden ohne Verarbeitung ausgeliefert. Beispielsweise Bilder kommen hier hin.', 'these files are served without processing, your images go here': 'Diese Dateien werden ohne Verarbeitung ausgeliefert. Beispielsweise Bilder kommen hier hin.', 'This is a copy of the scaffolding application': 'Dies ist eine Kopie einer Grundgerüst-Anwendung', 'This is the %(filename)s template': 'Dies ist das Template %(filename)s', 'Ticket': 'Ticket', -'Timestamp': 'Timestamp', +'Ticket ID': 'Ticket ID', +'Timestamp': 'Zeitstempel', 'TM': 'TM', 'to previous version.': 'zu einer früheren Version.', 'To create a plugin, name a file/folder plugin_[name]': 'Um ein Plugin zu erstellen benennen Sie eine(n) Datei/Ordner plugin_[Name]', -'translation strings for the application': 'Übersetzungs-Strings für die Anwendung', -'Translation strings for the application': 'Translation strings for the application', +'toggle breakpoint': 'toggle breakpoint', +'Toggle Fullscreen': 'Toggle Fullscreen', +'Traceback': 'Traceback', +'translation strings for the application': 'übersetzungs-Strings für die Anwendung', +'Translation strings for the application': 'übersetzungs-Strings für die Anwendung', 'try': 'versuche', 'try something like': 'versuche so etwas wie', +'Try the mobile interface': 'Try the mobile interface', +'try view': 'try view', 'Unable to check for upgrades': 'überprüfen von Upgrades nicht möglich', 'unable to create application "%s"': 'erzeugen von Anwendung "%s" nicht möglich', 'unable to delete file "%(filename)s"': 'löschen von Datein "%(filename)s" nicht möglich', @@ -311,37 +374,40 @@ 'Unable to download app': 'herunterladen der Anwendung nicht möglich', 'unable to parse csv file': 'analysieren der cvs Datei nicht möglich', 'unable to uninstall "%s"': 'deinstallieren von "%s" nicht möglich', -'uncheck all': 'alles demarkieren', +'uncheck all': 'Selektionen entfernen', 'Uninstall': 'deinstallieren', 'update': 'aktualisieren', 'update all languages': 'aktualisiere alle Sprachen', 'Update:': 'Aktualisiere:', 'upgrade web2py now': 'jetzt web2py upgraden', 'upload': 'upload', +'Upload': 'Upload', 'Upload & install packed application': 'Verpackte Anwendung hochladen und installieren', -'Upload a package:': 'Upload a package:', -'Upload and install packed application': 'Upload and install packed application', +'Upload a package:': 'Ein Packet hochladen:', +'Upload and install packed application': 'Verpackte Anwendung hochladen und installieren', 'upload application:': 'lade Anwendung hoch:', 'Upload existing application': 'lade existierende Anwendung hoch', 'upload file:': 'lade Datei hoch:', 'upload plugin file:': 'Plugin-Datei hochladen:', 'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Benutze (...)&(...) für AND, (...)|(...) für OR, und ~(...) für NOT, um komplexe Abfragen zu erstellen.', -'Use an url:': 'Use an url:', -'user': 'user', +'Use an url:': 'Verwende URL:', +'user': 'Nutzer', 'User ID': 'Benutzer ID', -'variables': 'variables', +'variables': 'Variablen', 'Version': 'Version', 'Version %s.%s.%s (%s) %s': 'Version %s.%s.%s (%s) %s', 'versioning': 'Versionierung', -'View': 'View', -'view': 'View', -'Views': 'Views', -'views': 'Views', +'Versioning': 'Versionierung', +'View': 'Ansicht', +'view': 'Ansicht', +'Views': 'Ansichten', +'views': 'Ansichten', +'Web Framework': 'Web Framework', 'web2py is up to date': 'web2py ist auf dem neuesten Stand', 'web2py Recent Tweets': 'neuste Tweets von web2py', 'Welcome %s': 'Willkommen %s', 'Welcome to web2py': 'Willkommen zu web2py', -'Which called the function': 'Which called the function', +'Which called the function': 'welche die Funktion aufrief', 'Wrap with Abbreviation': 'mit Kürzel einhüllen', 'xml': 'xml', 'YES': 'JA', From ec4a55fc9e442c9ae1dd3331b5b3ec80e30811c6 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 26 Jun 2013 04:02:17 -0500 Subject: [PATCH 05/17] if not logged-in no [wiki] menu --- VERSION | 2 +- gluon/tools.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index 937aa6f0..1f0bfa2d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.5.1-stable+timestamp.2013.06.26.02.53.33 +Version 2.5.1-stable+timestamp.2013.06.26.04.01.26 diff --git a/gluon/tools.py b/gluon/tools.py index 37069c32..6ec9d4d3 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -5201,12 +5201,13 @@ class Wiki(object): return True def can_see_menu(self): - if self.settings.menu_groups is None: - return True if self.auth.user: - groups = self.auth.user_groups.values() - if any(t in self.settings.menu_groups for t in groups): + if self.settings.menu_groups is None: return True + else: + groups = self.auth.user_groups.values() + if any(t in self.settings.menu_groups for t in groups): + return True return False ### END POLICY From cdf8ee8d853da5e14b01245c545cc559e2c3fa9c Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 26 Jun 2013 05:12:47 -0500 Subject: [PATCH 06/17] 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__': From 81495f7ccafe3abbfe32eca68451b46d9dcb93b8 Mon Sep 17 00:00:00 2001 From: niphlod Date: Thu, 27 Jun 2013 00:00:00 +0200 Subject: [PATCH 07/17] nicer alignment, removed leftovers, new listwidget and passwordwidget --- applications/admin/static/js/web2py.js | 157 +++++++++++++--------- applications/examples/static/js/web2py.js | 157 +++++++++++++--------- applications/welcome/static/js/web2py.js | 157 +++++++++++++--------- gluon/sqlhtml.py | 69 ++-------- 4 files changed, 282 insertions(+), 258 deletions(-) diff --git a/applications/admin/static/js/web2py.js b/applications/admin/static/js/web2py.js index 3641a01f..7cbb2d19 100644 --- a/applications/admin/static/js/web2py.js +++ b/applications/admin/static/js/web2py.js @@ -84,9 +84,9 @@ $("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'); + /*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)); @@ -143,24 +143,24 @@ $('.hidden', target).hide(); web2py.manage_errors(target); web2py.ajax_fields(target); + web2py.show_if_handler(target); + }, + //manage errors in forms + manage_errors: function(target) { + $('.error', target).hide().slideDown('slow'); + //$('.error', target).hide().fadeIn('slow'); }, - //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 + //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 () { @@ -185,15 +185,15 @@ eval(decodeURIComponent(command)); } if(flash) { - web2py.flash(decodeURIComponent(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') + //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); @@ -204,16 +204,16 @@ trap_form: function (action, target) { $('#' + target + ' form').each(function (i) { var form = $(this); - form.attr('data-w2p_target', target); + 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', 'Working...'); form.submit(function (e) { web2py.hide_flash(); web2py.ajax_page('post', action, form.serialize(), target, form); e.preventDefault(); }); - } + } }); }, trap_link: function (target) { @@ -242,27 +242,27 @@ }, //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) { element.trigger('ajax:complete', [xhr, status]); - var html = xhr.responseText; + 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_form(action, target); web2py.trap_link(target); web2py.ajax_init('#' + target); } @@ -275,10 +275,10 @@ var jelement = $("#" + target); var element = jelement.get(0); var statement = "jQuery('#" + target + "').get(0).reload();"; - element.reload = function () { + jelement.reload = function () { // Continue if times is Infinity or // the times limit is not reached - if(this.reload_check()) { + if(element.reload_check()) { web2py.ajax_page('get', action, null, target, el); } }; // reload @@ -420,16 +420,16 @@ // 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]); + 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')); + // set to disabled state + el[method](el.data('w2p_disable_with')); el.bind('click.w2pDisable', function (e) { // prevent further clicking return web2py.stopEverything(e); @@ -438,29 +438,52 @@ // restore element to its original state which was disabled by 'disableElement' above enableElement: function (el) { - var method = el.prop('type') == 'submit' ? 'val' : 'html'; + 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')); + // set to old enabled state + el[method](el.data('w2p:enable-with')); el.removeData('w2p:enable-with'); // clean up cache } - el.removeClass('disabled'); + 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(''); - }, + //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(''); + }, + show_if_handler: function(target) { + var triggers = {}; + var show_if = function () { + var t = $(this); + var id = t.attr('id'); + t.attr('value', t.val()); + for(var k = 0; k < triggers[id].length; k++) { + var dep = $('#' + triggers[id][k], target); + var tr = $('#' + triggers[id][k] + '__row', target); + if(t.is(dep.attr('data-show-if'))) tr.slideDown(); + else tr.hide(); + } + }; + $('[data-show-trigger]', target).each(function () { + var name = $(this).attr('data-show-trigger'); + if(!triggers[name]) triggers[name] = []; + triggers[name].push($(this).attr('id')); + }); + for(var name in triggers) { + $('#' + name, target).change(show_if).keyup(show_if); + show_if.call($('#' + name, target)); + }; + }, a_handler: function (el, e) { e.preventDefault(); var method = el.data('w2p_method'); @@ -535,7 +558,7 @@ web2py.enableElement($(this)); }); }, - /* Disables form elements: + /* Disables form elements: - Caches element value in 'ujs:enable-with' data store - Replaces element text with value of 'data-disable-with' attribute - Sets disabled property to true @@ -543,10 +566,10 @@ disableFormElements: function(form) { form.find(web2py.disableSelector).each(function() { var element = $(this), method = element.is('button') ? 'html' : 'val'; - var disable_with = element.data('w2p_disable_with'); - if (disable_with == undefined) { - element.data('w2p_disable_with', element[method]()) - } + var disable_with = element.data('w2p_disable_with'); + if (disable_with == undefined) { + element.data('w2p_disable_with', element[method]()) + } element.data('w2p:enable-with', element[method]()); element[method](element.data('w2p_disable_with')); element.prop('disabled', true); @@ -554,8 +577,8 @@ }, /* Re-enables disabled form elements: - - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) - - Sets disabled property to false + - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) + - Sets disabled property to false */ enableFormElements: function(form) { form.find(web2py.enableSelector).each(function() { @@ -564,15 +587,15 @@ element.prop('disabled', false); }); }, - form_handlers: function() { - var el = $(document); - el.on('ajax:beforeSend', 'form[data-w2p_target]', function (e) { - web2py.disableFormElements($(this)); - }); - el.on('ajax:complete', 'form[data-w2p_target]', function (e) { - web2py.enableFormElements($(this)); - }); - } + form_handlers: function() { + var el = $(document); + el.on('ajax:beforeSend', 'form[data-w2p_target]', function (e) { + web2py.disableFormElements($(this)); + }); + el.on('ajax:complete', 'form[data-w2p_target]', function (e) { + web2py.enableFormElements($(this)); + }); + } } //end of functions @@ -584,7 +607,7 @@ web2py.ajax_init(document); web2py.event_handlers(); web2py.a_handlers(); - web2py.form_handlers(); + web2py.form_handlers(); }); })(jQuery); diff --git a/applications/examples/static/js/web2py.js b/applications/examples/static/js/web2py.js index 3641a01f..7cbb2d19 100644 --- a/applications/examples/static/js/web2py.js +++ b/applications/examples/static/js/web2py.js @@ -84,9 +84,9 @@ $("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'); + /*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)); @@ -143,24 +143,24 @@ $('.hidden', target).hide(); web2py.manage_errors(target); web2py.ajax_fields(target); + web2py.show_if_handler(target); + }, + //manage errors in forms + manage_errors: function(target) { + $('.error', target).hide().slideDown('slow'); + //$('.error', target).hide().fadeIn('slow'); }, - //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 + //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 () { @@ -185,15 +185,15 @@ eval(decodeURIComponent(command)); } if(flash) { - web2py.flash(decodeURIComponent(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') + //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); @@ -204,16 +204,16 @@ trap_form: function (action, target) { $('#' + target + ' form').each(function (i) { var form = $(this); - form.attr('data-w2p_target', target); + 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', 'Working...'); form.submit(function (e) { web2py.hide_flash(); web2py.ajax_page('post', action, form.serialize(), target, form); e.preventDefault(); }); - } + } }); }, trap_link: function (target) { @@ -242,27 +242,27 @@ }, //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) { element.trigger('ajax:complete', [xhr, status]); - var html = xhr.responseText; + 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_form(action, target); web2py.trap_link(target); web2py.ajax_init('#' + target); } @@ -275,10 +275,10 @@ var jelement = $("#" + target); var element = jelement.get(0); var statement = "jQuery('#" + target + "').get(0).reload();"; - element.reload = function () { + jelement.reload = function () { // Continue if times is Infinity or // the times limit is not reached - if(this.reload_check()) { + if(element.reload_check()) { web2py.ajax_page('get', action, null, target, el); } }; // reload @@ -420,16 +420,16 @@ // 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]); + 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')); + // set to disabled state + el[method](el.data('w2p_disable_with')); el.bind('click.w2pDisable', function (e) { // prevent further clicking return web2py.stopEverything(e); @@ -438,29 +438,52 @@ // restore element to its original state which was disabled by 'disableElement' above enableElement: function (el) { - var method = el.prop('type') == 'submit' ? 'val' : 'html'; + 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')); + // set to old enabled state + el[method](el.data('w2p:enable-with')); el.removeData('w2p:enable-with'); // clean up cache } - el.removeClass('disabled'); + 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(''); - }, + //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(''); + }, + show_if_handler: function(target) { + var triggers = {}; + var show_if = function () { + var t = $(this); + var id = t.attr('id'); + t.attr('value', t.val()); + for(var k = 0; k < triggers[id].length; k++) { + var dep = $('#' + triggers[id][k], target); + var tr = $('#' + triggers[id][k] + '__row', target); + if(t.is(dep.attr('data-show-if'))) tr.slideDown(); + else tr.hide(); + } + }; + $('[data-show-trigger]', target).each(function () { + var name = $(this).attr('data-show-trigger'); + if(!triggers[name]) triggers[name] = []; + triggers[name].push($(this).attr('id')); + }); + for(var name in triggers) { + $('#' + name, target).change(show_if).keyup(show_if); + show_if.call($('#' + name, target)); + }; + }, a_handler: function (el, e) { e.preventDefault(); var method = el.data('w2p_method'); @@ -535,7 +558,7 @@ web2py.enableElement($(this)); }); }, - /* Disables form elements: + /* Disables form elements: - Caches element value in 'ujs:enable-with' data store - Replaces element text with value of 'data-disable-with' attribute - Sets disabled property to true @@ -543,10 +566,10 @@ disableFormElements: function(form) { form.find(web2py.disableSelector).each(function() { var element = $(this), method = element.is('button') ? 'html' : 'val'; - var disable_with = element.data('w2p_disable_with'); - if (disable_with == undefined) { - element.data('w2p_disable_with', element[method]()) - } + var disable_with = element.data('w2p_disable_with'); + if (disable_with == undefined) { + element.data('w2p_disable_with', element[method]()) + } element.data('w2p:enable-with', element[method]()); element[method](element.data('w2p_disable_with')); element.prop('disabled', true); @@ -554,8 +577,8 @@ }, /* Re-enables disabled form elements: - - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) - - Sets disabled property to false + - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) + - Sets disabled property to false */ enableFormElements: function(form) { form.find(web2py.enableSelector).each(function() { @@ -564,15 +587,15 @@ element.prop('disabled', false); }); }, - form_handlers: function() { - var el = $(document); - el.on('ajax:beforeSend', 'form[data-w2p_target]', function (e) { - web2py.disableFormElements($(this)); - }); - el.on('ajax:complete', 'form[data-w2p_target]', function (e) { - web2py.enableFormElements($(this)); - }); - } + form_handlers: function() { + var el = $(document); + el.on('ajax:beforeSend', 'form[data-w2p_target]', function (e) { + web2py.disableFormElements($(this)); + }); + el.on('ajax:complete', 'form[data-w2p_target]', function (e) { + web2py.enableFormElements($(this)); + }); + } } //end of functions @@ -584,7 +607,7 @@ web2py.ajax_init(document); web2py.event_handlers(); web2py.a_handlers(); - web2py.form_handlers(); + web2py.form_handlers(); }); })(jQuery); diff --git a/applications/welcome/static/js/web2py.js b/applications/welcome/static/js/web2py.js index 3641a01f..7cbb2d19 100644 --- a/applications/welcome/static/js/web2py.js +++ b/applications/welcome/static/js/web2py.js @@ -84,9 +84,9 @@ $("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'); + /*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)); @@ -143,24 +143,24 @@ $('.hidden', target).hide(); web2py.manage_errors(target); web2py.ajax_fields(target); + web2py.show_if_handler(target); + }, + //manage errors in forms + manage_errors: function(target) { + $('.error', target).hide().slideDown('slow'); + //$('.error', target).hide().fadeIn('slow'); }, - //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 + //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 () { @@ -185,15 +185,15 @@ eval(decodeURIComponent(command)); } if(flash) { - web2py.flash(decodeURIComponent(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') + //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); @@ -204,16 +204,16 @@ trap_form: function (action, target) { $('#' + target + ' form').each(function (i) { var form = $(this); - form.attr('data-w2p_target', target); + 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', 'Working...'); form.submit(function (e) { web2py.hide_flash(); web2py.ajax_page('post', action, form.serialize(), target, form); e.preventDefault(); }); - } + } }); }, trap_link: function (target) { @@ -242,27 +242,27 @@ }, //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) { element.trigger('ajax:complete', [xhr, status]); - var html = xhr.responseText; + 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_form(action, target); web2py.trap_link(target); web2py.ajax_init('#' + target); } @@ -275,10 +275,10 @@ var jelement = $("#" + target); var element = jelement.get(0); var statement = "jQuery('#" + target + "').get(0).reload();"; - element.reload = function () { + jelement.reload = function () { // Continue if times is Infinity or // the times limit is not reached - if(this.reload_check()) { + if(element.reload_check()) { web2py.ajax_page('get', action, null, target, el); } }; // reload @@ -420,16 +420,16 @@ // 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]); + 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')); + // set to disabled state + el[method](el.data('w2p_disable_with')); el.bind('click.w2pDisable', function (e) { // prevent further clicking return web2py.stopEverything(e); @@ -438,29 +438,52 @@ // restore element to its original state which was disabled by 'disableElement' above enableElement: function (el) { - var method = el.prop('type') == 'submit' ? 'val' : 'html'; + 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')); + // set to old enabled state + el[method](el.data('w2p:enable-with')); el.removeData('w2p:enable-with'); // clean up cache } - el.removeClass('disabled'); + 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(''); - }, + //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(''); + }, + show_if_handler: function(target) { + var triggers = {}; + var show_if = function () { + var t = $(this); + var id = t.attr('id'); + t.attr('value', t.val()); + for(var k = 0; k < triggers[id].length; k++) { + var dep = $('#' + triggers[id][k], target); + var tr = $('#' + triggers[id][k] + '__row', target); + if(t.is(dep.attr('data-show-if'))) tr.slideDown(); + else tr.hide(); + } + }; + $('[data-show-trigger]', target).each(function () { + var name = $(this).attr('data-show-trigger'); + if(!triggers[name]) triggers[name] = []; + triggers[name].push($(this).attr('id')); + }); + for(var name in triggers) { + $('#' + name, target).change(show_if).keyup(show_if); + show_if.call($('#' + name, target)); + }; + }, a_handler: function (el, e) { e.preventDefault(); var method = el.data('w2p_method'); @@ -535,7 +558,7 @@ web2py.enableElement($(this)); }); }, - /* Disables form elements: + /* Disables form elements: - Caches element value in 'ujs:enable-with' data store - Replaces element text with value of 'data-disable-with' attribute - Sets disabled property to true @@ -543,10 +566,10 @@ disableFormElements: function(form) { form.find(web2py.disableSelector).each(function() { var element = $(this), method = element.is('button') ? 'html' : 'val'; - var disable_with = element.data('w2p_disable_with'); - if (disable_with == undefined) { - element.data('w2p_disable_with', element[method]()) - } + var disable_with = element.data('w2p_disable_with'); + if (disable_with == undefined) { + element.data('w2p_disable_with', element[method]()) + } element.data('w2p:enable-with', element[method]()); element[method](element.data('w2p_disable_with')); element.prop('disabled', true); @@ -554,8 +577,8 @@ }, /* Re-enables disabled form elements: - - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) - - Sets disabled property to false + - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) + - Sets disabled property to false */ enableFormElements: function(form) { form.find(web2py.enableSelector).each(function() { @@ -564,15 +587,15 @@ element.prop('disabled', false); }); }, - form_handlers: function() { - var el = $(document); - el.on('ajax:beforeSend', 'form[data-w2p_target]', function (e) { - web2py.disableFormElements($(this)); - }); - el.on('ajax:complete', 'form[data-w2p_target]', function (e) { - web2py.enableFormElements($(this)); - }); - } + form_handlers: function() { + var el = $(document); + el.on('ajax:beforeSend', 'form[data-w2p_target]', function (e) { + web2py.disableFormElements($(this)); + }); + el.on('ajax:complete', 'form[data-w2p_target]', function (e) { + web2py.enableFormElements($(this)); + }); + } } //end of functions @@ -584,7 +607,7 @@ web2py.ajax_init(document); web2py.event_handlers(); web2py.a_handlers(); - web2py.form_handlers(); + web2py.form_handlers(); }); })(jQuery); diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index 2f99adc5..26720803 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -86,8 +86,8 @@ def show_if(cond): if not cond: return None base = "%s_%s" % (cond.first.tablename, cond.first.name) - if ((cond.op.__name__ == 'EQ' and cond.second == True) or - (cond.op.__name__ == 'NE' and cond.second == False)): + if ((cond.op.__name__ == 'EQ' and cond.second == True) or + (cond.op.__name__ == 'NE' and cond.second == False)): return base,":checked" if ((cond.op.__name__ == 'EQ' and cond.second == False) or (cond.op.__name__ == 'NE' and cond.second == True)): @@ -97,7 +97,7 @@ def show_if(cond): if cond.op.__name__ == 'NE': return base,"[value!='%s']" % cond.second if cond.op.__name__ == 'CONTAINS': - return base,"[value~='%s']" % cond.second + return base,"[value~='%s']" % cond.second if cond.op.__name__ == 'BELONGS' and isinstance(cond.second,(list,tuple)): return base,','.join("[value='%s']" % (v) for v in cond.second) raise RuntimeError("Not Implemented Error") @@ -126,7 +126,7 @@ class FormWidget(object): _id='%s_%s' % (field.tablename, field.name), _class=cls._class or widget_class.match(str(field.type)).group(), - _name=field.name, + _name=field.name, requires=field.requires, ) if getattr(field,'show_if',None): @@ -295,57 +295,15 @@ class ListWidget(StringWidget): _class = 'string' requires = field.requires if isinstance( field.requires, (IS_NOT_EMPTY, IS_LIST_OF)) else None - attributes['_style'] = 'list-style:none' nvalue = value or [''] items = [LI(INPUT(_id=_id, _class=_class, _name=_name, value=v, hideerror=k < len(nvalue) - 1, requires=requires), **attributes) for (k, v) in enumerate(nvalue)] - script = SCRIPT(""" -// from http://refactormycode.com/codes/694-expanding-input-list-using-jquery -(function(){ -jQuery.fn.grow_input = function() { - return this.each(function() { - var ul = this; - jQuery(ul).find(":text").after('+ -').keypress(function (e) { return (e.which == 13) ? pe(ul, e) : true; }).next().click(function(e){ pe(ul, e) }).next().click(function(e){ rl(ul, e)}); - }); -}; -function pe(ul, e) { - var new_line = ml(ul); - rel(ul); - if (jQuery(e.target).parent().is(':visible')) { - //make sure we didn't delete the element before we insert after - new_line.insertAfter(jQuery(e.target).parent()); - } else { - //the line we clicked on was deleted, just add to end of list - new_line.appendTo(ul); - } - new_line.find(":text").focus(); - return false; -} -function rl(ul, e) { - if (jQuery(ul).children().length > 1) { - //only remove if we have more than 1 item so the list is never empty - jQuery(e.target).parent().remove(); - } -} -function ml(ul) { - var line = jQuery(ul).find("li:first").clone(true); - line.find(':text').val(''); - return line; -} -function rel(ul) { - jQuery(ul).find("li").each(function() { - var trimmed = jQuery.trim(jQuery(this.firstChild).val()); - if (trimmed=='') jQuery(this).remove(); else jQuery(this.firstChild).val(trimmed); - }); -} -})(); -jQuery(document).ready(function(){jQuery('#%s_grow_input').grow_input();}); -""" % _id) attributes['_id'] = _id + '_grow_input' attributes['_style'] = 'list-style:none' - return TAG[''](UL(*items, **attributes), script) + attributes['_class'] = 'w2p_list' + return TAG[''](UL(*items, **attributes)) class MultipleOptionsWidget(OptionsWidget): @@ -521,7 +479,6 @@ class PasswordWidget(FormWidget): _value=(value and cls.DEFAULT_PASSWORD_DISPLAY) or '', ) attr = cls._attributes(field, default, **attributes) - output = CAT(INPUT(**attr)) # deal with entropy check! requires = field.requires @@ -529,10 +486,9 @@ class PasswordWidget(FormWidget): requires = [requires] is_strong = [r for r in requires if isinstance(r, IS_STRONG)] if is_strong: - output.append(SCRIPT("web2py_validate_entropy(jQuery('#%s'),%s);" % ( - attr['_id'], is_strong[0].entropy - if is_strong[0].entropy else "null"))) + attr['_data-w2p_entropy'] = is_strong[0].entropy if is_strong[0].entropy else "null" # end entropy check + output = INPUT(**attr) return output @@ -651,7 +607,7 @@ class AutocompleteWidget(object): self.help_fields = help_fields or [] self.help_string = help_string if self.help_fields and not self.help_string: - self.help_string = ' '.join('%%(%s)s'%f.name + self.help_string = ' '.join('%%(%s)s'%f.name for f in self.help_fields) self.request = request @@ -1484,7 +1440,7 @@ class SQLFORM(FORM): continue else: f = os.path.join( - current.request.folder, + current.request.folder, os.path.normpath(f)) source_file = open(f, 'rb') original_filename = os.path.split(f)[1] @@ -2141,7 +2097,7 @@ class SQLFORM(FORM): else: rows = dbset.select(left=left, orderby=orderby, cacheable=True, *expcolumns) - + value = exportManager[export_type] clazz = value[0] if hasattr(value, '__getitem__') else value oExp = clazz(rows) @@ -2694,7 +2650,7 @@ class SQLFORM(FORM): elif grid.view_form: header = T('View %(entity)s') % dict( entity=format(grid.view_form.table, - grid.view_form.record)) + grid.view_form.record)) if next: breadcrumbs.append(LI( A(T(header), _class=trap_class(),_href=url()), @@ -3136,4 +3092,3 @@ class ExporterJSON(ExportClass): return self.rows.as_json() else: return 'null' - From 768b49224c439e250ee437a44df14a5e2e1f7f73 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 27 Jun 2013 01:51:03 -0500 Subject: [PATCH 08/17] fixed Issue 1563:Web2py in error when the request contains a json string corresponding to a list, thanks Hono Sandai --- VERSION | 2 +- gluon/main.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 34a64fc1..06191ec8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.06.26.05.11.38 +Version 2.6.0-development+timestamp.2013.06.27.01.50.11 diff --git a/gluon/main.py b/gluon/main.py index 1a9c268f..c0851c02 100644 --- a/gluon/main.py +++ b/gluon/main.py @@ -339,8 +339,9 @@ def parse_get_post_vars(request, environ): json_vars = {} pass # update vars and get_vars with what was posted as json - request.get_vars.update(json_vars) - request.vars.update(json_vars) + if isinstance(json_vars,dict): + request.get_vars.update(json_vars) + request.vars.update(json_vars) # parse POST variables on POST, PUT, BOTH only in post_vars @@ -386,7 +387,7 @@ def parse_get_post_vars(request, environ): if len(pvalue): request.post_vars[key] = (len(pvalue) > 1 and pvalue) or pvalue[0] - if is_json: + if is_json and isinstance(json_vars,dict): # update post_vars with what was posted as json request.post_vars.update(json_vars) From 81fa9b19840c0f498363394e205cd77eb1b80be8 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 27 Jun 2013 07:21:40 -0500 Subject: [PATCH 09/17] removed check for missing driver --- VERSION | 2 +- gluon/dal.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 06191ec8..aa81269c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.06.27.01.50.11 +Version 2.6.0-development+timestamp.2013.06.27.07.20.48 diff --git a/gluon/dal.py b/gluon/dal.py index 8862167f..43521893 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -594,9 +594,9 @@ class ConnectionPool(object): if f is None: f = self.connector - if not hasattr(self, "driver") or self.driver is None: - LOGGER.debug("Skipping connection since there's no driver") - return + # if not hasattr(self, "driver") or self.driver is None: + # LOGGER.debug("Skipping connection since there's no driver") + # return if not self.pool_size: self.connection = f() From c25f337ffe6a11506fb7820e80059d7be1cad444 Mon Sep 17 00:00:00 2001 From: Michele Comitini Date: Thu, 27 Jun 2013 16:57:37 +0200 Subject: [PATCH 10/17] Row code speedups --- gluon/dal.py | 67 +++++++++++++++++++++------------------------------- 1 file changed, 27 insertions(+), 40 deletions(-) diff --git a/gluon/dal.py b/gluon/dal.py index b837a50e..63a93e61 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -6857,23 +6857,26 @@ class Row(object): this is only used to store a Row """ - def __init__(self,*args,**kwargs): - self.__dict__.update(*args,**kwargs) + __init__ = lambda self,*args,**kwargs: self.__dict__.update(*args,**kwargs) - def __getitem__(self, key): - key=str(key) - m = REGEX_TABLE_DOT_FIELD.match(key) - if key in self.get('_extra',{}): - return self._extra[key] - elif m: + def __getitem__(self, k): + key=str(k) + _extra = getattr(self, '_extra', None) + if _extra: + return _extra.get(key) + if v: + return v + #m = REGEX_TABLE_DOT_FIELD.match(key) + idot = key.find('.') + if idot != -1: + m = (key[:idot],key[idot+1:]) try: - return ogetattr(self, m.group(1))[m.group(2)] + return ogetattr(self, m[0])[m[1]] except (KeyError,AttributeError,TypeError): - key = m.group(2) + key = m[1] return ogetattr(self, key) - def __setitem__(self, key, value): - setattr(self, str(key), value) + __setitem__ = lambda self, key, value: setattr(self, str(key), value) __delitem__ = object.__delattr__ @@ -6881,47 +6884,31 @@ class Row(object): __call__ = __getitem__ - def get(self,key,default=None): - return self.__dict__.get(key,default) + get = lambda self, key, default: self.__dict__.get(key,default) - def __contains__(self,key): - return key in self.__dict__ - has_key = __contains__ + has_key = __contains__ = lambda self, key: key in self.__dict__ - def __nonzero__(self): - return len(self.__dict__)>0 + __nonzero__ = lambda self: len(self.__dict__)>0 - def update(self, *args, **kwargs): - self.__dict__.update(*args, **kwargs) + update = lambda self, *args, **kwargs: self.__dict__.update(*args, **kwargs) - def keys(self): - return self.__dict__.keys() + keys = lambda self: self.__dict__.keys() - def items(self): - return self.__dict__.items() + items = lambda self: self.__dict__.items() - def values(self): - return self.__dict__.values() + values = lambda self: self.__dict__.values() - def __iter__(self): - return self.__dict__.__iter__() + __iter__ = lambda self: self.__dict__.__iter__() - def iteritems(self): - return self.__dict__.iteritems() + iteritems = lambda self: self.__dict__.iteritems() - def __str__(self): - ### this could be made smarter - return '' % self.as_dict() + __str__ = __repr__ = lambda self: '' % self.as_dict() - def __repr__(self): - return '' % self.as_dict() + __int__ = lambda self: object.__getattribute__(self,'id') - def __int__(self): - return object.__getattribute__(self,'id') + __long__ = lambda self: long(object.__getattribute__(self,'id')) - def __long__(self): - return long(object.__getattribute__(self,'id')) def __eq__(self,other): try: From c4bff4437fd8b6e2750f4596edb13f14b57c7ebf Mon Sep 17 00:00:00 2001 From: Michele Comitini Date: Thu, 27 Jun 2013 17:19:10 +0200 Subject: [PATCH 11/17] removed useless comment --- gluon/dal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gluon/dal.py b/gluon/dal.py index 63a93e61..295cad44 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -6866,7 +6866,6 @@ class Row(object): return _extra.get(key) if v: return v - #m = REGEX_TABLE_DOT_FIELD.match(key) idot = key.find('.') if idot != -1: m = (key[:idot],key[idot+1:]) From 917d2087c52ab835e123c69a3d9edc9f2ac4ffc4 Mon Sep 17 00:00:00 2001 From: niphlod Date: Thu, 27 Jun 2013 22:31:11 +0200 Subject: [PATCH 12/17] fixed the collapse function, refactored LOAD() to use new web2py.js --- applications/admin/static/js/web2py.js | 17 +++++++++++++++-- applications/examples/static/js/web2py.js | 17 +++++++++++++++-- applications/welcome/static/js/web2py.js | 17 +++++++++++++++-- gluon/compileapp.py | 20 ++++++++++---------- 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/applications/admin/static/js/web2py.js b/applications/admin/static/js/web2py.js index 7cbb2d19..c7570263 100644 --- a/applications/admin/static/js/web2py.js +++ b/applications/admin/static/js/web2py.js @@ -20,7 +20,7 @@ if(window.focus) newwindow.focus(); return false; }, - collapse: function () { + collapse: function (id) { $('#' + id).slideToggle(); }, fade: function (id, value) { @@ -144,6 +144,7 @@ web2py.manage_errors(target); web2py.ajax_fields(target); web2py.show_if_handler(target); + web2py.component_handler(target); }, //manage errors in forms manage_errors: function(target) { @@ -275,7 +276,7 @@ var jelement = $("#" + target); var element = jelement.get(0); var statement = "jQuery('#" + target + "').get(0).reload();"; - jelement.reload = function () { + element.reload = function () { // Continue if times is Infinity or // the times limit is not reached if(element.reload_check()) { @@ -484,6 +485,18 @@ show_if.call($('#' + name, target)); }; }, + component_handler : function (target) { + $('div[data-w2p_remote]', target).each(function () { + var remote, times, timeout, target; + var el = $(this); + remote = el.data('w2p_remote'); + times = el.data('w2p_times'); + timeout = el.data('w2p_timeout'); + target = el.attr('id'); + web2py.component(remote, target, timeout, times, $(this)); + } + ) + }, a_handler: function (el, e) { e.preventDefault(); var method = el.data('w2p_method'); diff --git a/applications/examples/static/js/web2py.js b/applications/examples/static/js/web2py.js index 7cbb2d19..c7570263 100644 --- a/applications/examples/static/js/web2py.js +++ b/applications/examples/static/js/web2py.js @@ -20,7 +20,7 @@ if(window.focus) newwindow.focus(); return false; }, - collapse: function () { + collapse: function (id) { $('#' + id).slideToggle(); }, fade: function (id, value) { @@ -144,6 +144,7 @@ web2py.manage_errors(target); web2py.ajax_fields(target); web2py.show_if_handler(target); + web2py.component_handler(target); }, //manage errors in forms manage_errors: function(target) { @@ -275,7 +276,7 @@ var jelement = $("#" + target); var element = jelement.get(0); var statement = "jQuery('#" + target + "').get(0).reload();"; - jelement.reload = function () { + element.reload = function () { // Continue if times is Infinity or // the times limit is not reached if(element.reload_check()) { @@ -484,6 +485,18 @@ show_if.call($('#' + name, target)); }; }, + component_handler : function (target) { + $('div[data-w2p_remote]', target).each(function () { + var remote, times, timeout, target; + var el = $(this); + remote = el.data('w2p_remote'); + times = el.data('w2p_times'); + timeout = el.data('w2p_timeout'); + target = el.attr('id'); + web2py.component(remote, target, timeout, times, $(this)); + } + ) + }, a_handler: function (el, e) { e.preventDefault(); var method = el.data('w2p_method'); diff --git a/applications/welcome/static/js/web2py.js b/applications/welcome/static/js/web2py.js index 7cbb2d19..c7570263 100644 --- a/applications/welcome/static/js/web2py.js +++ b/applications/welcome/static/js/web2py.js @@ -20,7 +20,7 @@ if(window.focus) newwindow.focus(); return false; }, - collapse: function () { + collapse: function (id) { $('#' + id).slideToggle(); }, fade: function (id, value) { @@ -144,6 +144,7 @@ web2py.manage_errors(target); web2py.ajax_fields(target); web2py.show_if_handler(target); + web2py.component_handler(target); }, //manage errors in forms manage_errors: function(target) { @@ -275,7 +276,7 @@ var jelement = $("#" + target); var element = jelement.get(0); var statement = "jQuery('#" + target + "').get(0).reload();"; - jelement.reload = function () { + element.reload = function () { // Continue if times is Infinity or // the times limit is not reached if(element.reload_check()) { @@ -484,6 +485,18 @@ show_if.call($('#' + name, target)); }; }, + component_handler : function (target) { + $('div[data-w2p_remote]', target).each(function () { + var remote, times, timeout, target; + var el = $(this); + remote = el.data('w2p_remote'); + times = el.data('w2p_times'); + timeout = el.data('w2p_timeout'); + target = el.attr('id'); + web2py.component(remote, target, timeout, times, $(this)); + } + ) + }, a_handler: function (el, e) { e.preventDefault(); var method = el.data('w2p_method'); diff --git a/gluon/compileapp.py b/gluon/compileapp.py index 875f732b..fd197044 100644 --- a/gluon/compileapp.py +++ b/gluon/compileapp.py @@ -167,15 +167,15 @@ def LOAD(c=None, f='index', args=None, vars=None, elif timeout <= 0: raise ValueError( "Timeout argument must be greater than zero or None") - statement = "web2py_component('%s','%s', %s, %s);" \ + statement = "$.web2py.component('%s','%s', %s, %s);" \ % (url, target, timeout, times) + attr['_data-w2p_timeout'] = timeout + attr['_data-w2p_times'] = times else: - statement = "web2py_component('%s','%s');" % (url, target) - script = SCRIPT(statement, _type="text/javascript") - if not content is None: - return TAG[''](script, DIV(content, **attr)) - else: - return TAG[''](script) + statement = "$.web2py.component('%s','%s');" % (url, target) + attr['_data-w2p_remote'] = url + if not target is None: + return DIV(content, **attr) else: if not isinstance(args, (list, tuple)): @@ -226,7 +226,7 @@ def LOAD(c=None, f='index', args=None, vars=None, link = URL(request.application, c, f, r=request, args=args, vars=vars, extension=extension, user_signature=user_signature) - js = "web2py_trap_form('%s','%s');" % (link, target) + js = "$.web2py.trap_form('%s','%s');" % (link, target) script = js and SCRIPT(js, _type="text/javascript") or '' return TAG[''](DIV(XML(page), **attr), script) @@ -254,7 +254,7 @@ class LoadFactory(object): url = url or html.URL(request.application, c, f, r=request, args=args, vars=vars, extension=extension, user_signature=user_signature) - script = html.SCRIPT('web2py_component("%s","%s")' % (url, target), + script = html.SCRIPT('$.web2py.component("%s","%s")' % (url, target), _type="text/javascript") return html.TAG[''](script, html.DIV(content, **attr)) else: @@ -305,7 +305,7 @@ class LoadFactory(object): link = html.URL(request.application, c, f, r=request, args=args, vars=vars, extension=extension, user_signature=user_signature) - js = "web2py_trap_form('%s','%s');" % (link, target) + js = "$.web2py.trap_form('%s','%s');" % (link, target) script = js and html.SCRIPT(js, _type="text/javascript") or '' return html.TAG[''](html.DIV(html.XML(page), **attr), script) From 4b94fe637da720313e7515f67fa2531d08f31f33 Mon Sep 17 00:00:00 2001 From: Michele Comitini Date: Fri, 28 Jun 2013 02:07:22 +0200 Subject: [PATCH 13/17] restored the regex --- gluon/dal.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/gluon/dal.py b/gluon/dal.py index 295cad44..d13e3c13 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -6866,13 +6866,12 @@ class Row(object): return _extra.get(key) if v: return v - idot = key.find('.') - if idot != -1: - m = (key[:idot],key[idot+1:]) + m = REGEX_TABLE_DOT_FIELD.match(key) + if m: try: - return ogetattr(self, m[0])[m[1]] + return ogetattr(self, m.group(1))[m.group(2)] except (KeyError,AttributeError,TypeError): - key = m[1] + key = m.group(2) return ogetattr(self, key) __setitem__ = lambda self, key, value: setattr(self, str(key), value) @@ -6883,7 +6882,7 @@ class Row(object): __call__ = __getitem__ - get = lambda self, key, default: self.__dict__.get(key,default) + get = lambda self, key, default=None: self.__dict__.get(key,default) has_key = __contains__ = lambda self, key: key in self.__dict__ From 8666d3520eac255a73925da9b1ac3967d7d3bb8a Mon Sep 17 00:00:00 2001 From: niphlod Date: Sat, 29 Jun 2013 22:49:05 +0200 Subject: [PATCH 14/17] fixed issue 1567 with scheduler and Mysql --- gluon/scheduler.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gluon/scheduler.py b/gluon/scheduler.py index 2dea4efe..95820ffa 100644 --- a/gluon/scheduler.py +++ b/gluon/scheduler.py @@ -480,7 +480,8 @@ class Scheduler(MetaScheduler): Field('function_name', requires=IS_IN_SET(sorted(self.tasks.keys())) if self.tasks else DEFAULT), - Field('uuid', requires=IS_NOT_IN_DB(db, 'scheduler_task.uuid'), + Field('uuid', length=255, + requires=IS_NOT_IN_DB(db, 'scheduler_task.uuid'), unique=True, default=web2py_uuid), Field('args', 'text', default='[]', requires=TYPE(list)), Field('vars', 'text', default='{}', requires=TYPE(dict)), @@ -521,7 +522,7 @@ class Scheduler(MetaScheduler): db.define_table( 'scheduler_worker', - Field('worker_name', unique=True), + Field('worker_name', length=255, unique=True), Field('first_heartbeat', 'datetime'), Field('last_heartbeat', 'datetime'), Field('status', requires=IS_IN_SET(WORKER_STATUS)), From d218b052a19ac0d8c2c3f5e1aecc53d92516679e Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 29 Jun 2013 17:07:17 -0500 Subject: [PATCH 15/17] fixed issues 1568, no spaces in usermames, thanks Iceberg --- VERSION | 2 +- gluon/tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index aa81269c..596fbfe9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.06.27.07.20.48 +Version 2.6.0-development+timestamp.2013.06.29.17.06.13 diff --git a/gluon/tools.py b/gluon/tools.py index 6ec9d4d3..8e131114 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -1545,7 +1545,7 @@ class Auth(object): settings.table_user_name, []) + signature_list if username or settings.cas_provider: is_unique_username = \ - [IS_MATCH('[\w\.\-]+'), + [IS_MATCH('[\w\.\-]+', strict=True), IS_NOT_IN_DB(db, '%s.username' % settings.table_user_name)] if not settings.username_case_sensitive: is_unique_username.insert(1, IS_LOWER()) From 3407cf65849cddd521bf40b0357b4f3d006ae157 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 30 Jun 2013 09:19:26 -0500 Subject: [PATCH 16/17] made the WSGI compatibility layer lazy, nobody uses it anyway and it may be broken, probably should be removed --- VERSION | 2 +- gluon/globals.py | 1 - gluon/main.py | 100 +++++++++++++++++++++++------------------------ gluon/rewrite.py | 2 + 4 files changed, 52 insertions(+), 53 deletions(-) diff --git a/VERSION b/VERSION index 596fbfe9..f2f83bbb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.06.29.17.06.13 +Version 2.6.0-development+timestamp.2013.06.30.09.18.36 diff --git a/gluon/globals.py b/gluon/globals.py index 940d7f3e..3aafea1f 100644 --- a/gluon/globals.py +++ b/gluon/globals.py @@ -83,7 +83,6 @@ class Request(Storage): def __init__(self): Storage.__init__(self) - self.wsgi = Storage() # hooks to environ and start_response self.env = Storage() self.cookies = Cookie.SimpleCookie() self.get_vars = Storage() diff --git a/gluon/main.py b/gluon/main.py index c0851c02..0ebeade1 100644 --- a/gluon/main.py +++ b/gluon/main.py @@ -257,51 +257,55 @@ def serve_controller(request, response, session): raise HTTP(response.status, page, **response.headers) -def start_response_aux(status, headers, exc_info, response=None): - """ - in controller you can use:: - - - request.wsgi.environ - - request.wsgi.start_response - - to call third party WSGI applications - """ - response.status = str(status).split(' ', 1)[0] - response.headers = dict(headers) - return lambda *args, **kargs: response.write(escape=False, *args, **kargs) - - -def middleware_aux(request, response, *middleware_apps): - """ - In you controller use:: - +class LazyWSGI(object): + def __init__(self, environ, request, response): + self.wsgi_environ = environ + self.request = request + self.response = response + @property + def environ(self): + if not hasattr(self,'_environ'): + new_environ = self.wsgi_environ + new_environ['wsgi.input'] = self.request.body + new_environ['wsgi.version'] = 1 + self._environ = new_environ + return self._environ + def start_response(self,status='200', headers=[], exec_info=None): + """ + in controller you can use:: + + - request.wsgi.environ + - request.wsgi.start_response + + to call third party WSGI applications + """ + self.response.status = str(status).split(' ', 1)[0] + self.response.headers = dict(headers) + return lambda *args, **kargs: \ + self.response.write(escape=False, *args, **kargs) + def middleware(self,*a): + """ + In you controller use:: + @request.wsgi.middleware(middleware1, middleware2, ...) - - to decorate actions with WSGI middleware. actions must return strings. - uses a simulated environment so it may have weird behavior in some cases - """ - def middleware(f): - def app(environ, start_response): - data = f() - start_response(response.status, response.headers.items()) - if isinstance(data, list): - return data - return [data] - for item in middleware_apps: - app = item(app) - - def caller(app): - wsgi = request.wsgi - return app(wsgi.environ, wsgi.start_response) - return lambda caller=caller, app=app: caller(app) - return middleware - - -def environ_aux(environ, request): - new_environ = copy.copy(environ) - new_environ['wsgi.input'] = request.body - new_environ['wsgi.version'] = 1 - return new_environ + + to decorate actions with WSGI middleware. actions must return strings. + uses a simulated environment so it may have weird behavior in some cases + """ + def middleware(f): + def app(environ, start_response): + data = f() + start_response(self.response.status, + self.response.headers.items()) + if isinstance(data, list): + return data + return [data] + for item in middleware_apps: + app = item(app) + def caller(app): + return app(self.environ, self.start_response) + return lambda caller=caller, app=app: caller(app) + return middleware ISLE25 = sys.version_info[1] <= 5 @@ -537,13 +541,7 @@ def wsgibase(environ, responder): # expose wsgi hooks for convenience # ################################################## - request.wsgi.environ = environ_aux(environ, request) - request.wsgi.start_response = \ - lambda status='200', headers=[], \ - exec_info=None, response=response: \ - start_response_aux(status, headers, exec_info, response) - request.wsgi.middleware = \ - lambda *a: middleware_aux(request, response, *a) + request.wsgi = LazyWSGI(environ, request, response) # ################################################## # load cookies diff --git a/gluon/rewrite.py b/gluon/rewrite.py index baebe8c7..a96c4302 100644 --- a/gluon/rewrite.py +++ b/gluon/rewrite.py @@ -638,6 +638,8 @@ def regex_url_in(request, environ): if match.group('c') == 'static': application = match.group('a') version, filename = None, match.group('z') + if not filename: + raise HTTP(404) items = filename.split('/', 1) if regex_version.match(items[0]): version, filename = items From 480ea80ed8a10da8657b5a5834525df53f2b0f31 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 30 Jun 2013 10:47:10 -0500 Subject: [PATCH 17/17] fixed typo in basic_auth_realm = basic_auth_realm(), thanks Jonathan --- VERSION | 2 +- gluon/tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index f2f83bbb..ee2c404b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.06.30.09.18.36 +Version 2.6.0-development+timestamp.2013.06.30.10.46.28 diff --git a/gluon/tools.py b/gluon/tools.py index 8e131114..e2e2bd09 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -1824,7 +1824,7 @@ class Auth(object): basic = current.request.env.http_authorization if basic_auth_realm: if callable(basic_auth_realm): - basic_auth_realm = basic_auth_auth() + basic_auth_realm = basic_auth_realm() elif isinstance(basic_auth_realm, (unicode, str)): basic_realm = unicode(basic_auth_realm) elif basic_auth_realm is True: