The Python web server now uses jQuery Tablesorter

This commit is contained in:
John Wiegley 2009-11-21 02:06:01 -05:00
parent eab95ad55b
commit 5d4a0a39be
17 changed files with 567 additions and 27 deletions

BIN
python/res/asc.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 B

BIN
python/res/bg.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 B

BIN
python/res/desc.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 B

BIN
python/res/icons/first.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

BIN
python/res/icons/last.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

BIN
python/res/icons/next.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

BIN
python/res/icons/prev.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

32
python/res/jquery-latest.js vendored Normal file

File diff suppressed because one or more lines are too long

12
python/res/jquery.dimensions.min.js vendored Normal file
View file

@ -0,0 +1,12 @@
/* Copyright (c) 2007 Paul Bakaus (paul.bakaus@googlemail.com) and Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net)
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
*
* $LastChangedDate: 2007-12-20 08:43:48 -0600 (Thu, 20 Dec 2007) $
* $Rev: 4257 $
*
* Version: 1.2
*
* Requires: jQuery 1.2+
*/
(function($){$.dimensions={version:'1.2'};$.each(['Height','Width'],function(i,name){$.fn['inner'+name]=function(){if(!this[0])return;var torl=name=='Height'?'Top':'Left',borr=name=='Height'?'Bottom':'Right';return this.is(':visible')?this[0]['client'+name]:num(this,name.toLowerCase())+num(this,'padding'+torl)+num(this,'padding'+borr);};$.fn['outer'+name]=function(options){if(!this[0])return;var torl=name=='Height'?'Top':'Left',borr=name=='Height'?'Bottom':'Right';options=$.extend({margin:false},options||{});var val=this.is(':visible')?this[0]['offset'+name]:num(this,name.toLowerCase())+num(this,'border'+torl+'Width')+num(this,'border'+borr+'Width')+num(this,'padding'+torl)+num(this,'padding'+borr);return val+(options.margin?(num(this,'margin'+torl)+num(this,'margin'+borr)):0);};});$.each(['Left','Top'],function(i,name){$.fn['scroll'+name]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(name=='Left'?val:$(window)['scrollLeft'](),name=='Top'?val:$(window)['scrollTop']()):this['scroll'+name]=val;}):this[0]==window||this[0]==document?self[(name=='Left'?'pageXOffset':'pageYOffset')]||$.boxModel&&document.documentElement['scroll'+name]||document.body['scroll'+name]:this[0]['scroll'+name];};});$.fn.extend({position:function(){var left=0,top=0,elem=this[0],offset,parentOffset,offsetParent,results;if(elem){offsetParent=this.offsetParent();offset=this.offset();parentOffset=offsetParent.offset();offset.top-=num(elem,'marginTop');offset.left-=num(elem,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&$.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return $(offsetParent);}});function num(el,prop){return parseInt($.curCSS(el.jquery?el[0]:el,prop,true))||0;};})(jQuery);

View file

@ -0,0 +1,122 @@
/*
* Metadata - jQuery plugin for parsing metadata from elements
*
* Copyright (c) 2006 John Resig, Yehuda Katz, J<EFBFBD>örn Zaefferer, Paul McLanahan
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Revision: $Id$
*
*/
/**
* Sets the type of metadata to use. Metadata is encoded in JSON, and each property
* in the JSON will become a property of the element itself.
*
* There are three supported types of metadata storage:
*
* attr: Inside an attribute. The name parameter indicates *which* attribute.
*
* class: Inside the class attribute, wrapped in curly braces: { }
*
* elem: Inside a child element (e.g. a script tag). The
* name parameter indicates *which* element.
*
* The metadata for an element is loaded the first time the element is accessed via jQuery.
*
* As a result, you can define the metadata type, use $(expr) to load the metadata into the elements
* matched by expr, then redefine the metadata type and run another $(expr) for other elements.
*
* @name $.metadata.setType
*
* @example <p id="one" class="some_class {item_id: 1, item_label: 'Label'}">This is a p</p>
* @before $.metadata.setType("class")
* @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
* @desc Reads metadata from the class attribute
*
* @example <p id="one" class="some_class" data="{item_id: 1, item_label: 'Label'}">This is a p</p>
* @before $.metadata.setType("attr", "data")
* @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
* @desc Reads metadata from a "data" attribute
*
* @example <p id="one" class="some_class"><script>{item_id: 1, item_label: 'Label'}</script>This is a p</p>
* @before $.metadata.setType("elem", "script")
* @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
* @desc Reads metadata from a nested script element
*
* @param String type The encoding type
* @param String name The name of the attribute to be used to get metadata (optional)
* @cat Plugins/Metadata
* @descr Sets the type of encoding to be used when loading metadata for the first time
* @type undefined
* @see metadata()
*/
(function($) {
$.extend({
metadata : {
defaults : {
type: 'class',
name: 'metadata',
cre: /({.*})/,
single: 'metadata'
},
setType: function( type, name ){
this.defaults.type = type;
this.defaults.name = name;
},
get: function( elem, opts ){
var settings = $.extend({},this.defaults,opts);
// check for empty string in single property
if ( !settings.single.length ) settings.single = 'metadata';
var data = $.data(elem, settings.single);
// returned cached data if it already exists
if ( data ) return data;
data = "{}";
if ( settings.type == "class" ) {
var m = settings.cre.exec( elem.className );
if ( m )
data = m[1];
} else if ( settings.type == "elem" ) {
if( !elem.getElementsByTagName )
return undefined;
var e = elem.getElementsByTagName(settings.name);
if ( e.length )
data = $.trim(e[0].innerHTML);
} else if ( elem.getAttribute != undefined ) {
var attr = elem.getAttribute( settings.name );
if ( attr )
data = attr;
}
if ( data.indexOf( '{' ) <0 )
data = "{" + data + "}";
data = eval("(" + data + ")");
$.data( elem, settings.single, data );
return data;
}
}
});
/**
* Returns the metadata object for the first member of the jQuery object.
*
* @name metadata
* @descr Returns element's metadata object
* @param Object opts An object contianing settings to override the defaults
* @type jQuery
* @cat Plugins/Metadata
*/
$.fn.metadata = function( opts ){
return $.metadata.get( this[0], opts );
};
})(jQuery);

2
python/res/jquery.tablesorter.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,25 @@
div.tablesorterPager {
padding: 10px 0 10px 0;
background-color: #D6D2C2;
text-align: center;
}
div.tablesorterPager span {
padding: 0 5px 0 5px;
}
div.tablesorterPager input.prev {
width: auto;
margin-right: 10px;
}
div.tablesorterPager input.next {
width: auto;
margin-left: 10px;
}
div.tablesorterPager input {
font-size: 8px;
width: 50px;
border: 1px solid #330000;
text-align: center;
}

View file

@ -0,0 +1,184 @@
(function($) {
$.extend({
tablesorterPager: new function() {
function updatePageDisplay(c) {
var s = $(c.cssPageDisplay,c.container).val((c.page+1) + c.seperator + c.totalPages);
}
function setPageSize(table,size) {
var c = table.config;
c.size = size;
c.totalPages = Math.ceil(c.totalRows / c.size);
c.pagerPositionSet = false;
moveToPage(table);
fixPosition(table);
}
function fixPosition(table) {
var c = table.config;
if(!c.pagerPositionSet && c.positionFixed) {
var c = table.config, o = $(table);
if(o.offset) {
c.container.css({
top: o.offset().top + o.height() + 'px',
position: 'absolute'
});
}
c.pagerPositionSet = true;
}
}
function moveToFirstPage(table) {
var c = table.config;
c.page = 0;
moveToPage(table);
}
function moveToLastPage(table) {
var c = table.config;
c.page = (c.totalPages-1);
moveToPage(table);
}
function moveToNextPage(table) {
var c = table.config;
c.page++;
if(c.page >= (c.totalPages-1)) {
c.page = (c.totalPages-1);
}
moveToPage(table);
}
function moveToPrevPage(table) {
var c = table.config;
c.page--;
if(c.page <= 0) {
c.page = 0;
}
moveToPage(table);
}
function moveToPage(table) {
var c = table.config;
if(c.page < 0 || c.page > (c.totalPages-1)) {
c.page = 0;
}
renderTable(table,c.rowsCopy);
}
function renderTable(table,rows) {
var c = table.config;
var l = rows.length;
var s = (c.page * c.size);
var e = (s + c.size);
if(e > rows.length ) {
e = rows.length;
}
var tableBody = $(table.tBodies[0]);
// clear the table body
$.tablesorter.clearTableBody(table);
for(var i = s; i < e; i++) {
//tableBody.append(rows[i]);
var o = rows[i];
var l = o.length;
for(var j=0; j < l; j++) {
tableBody[0].appendChild(o[j]);
}
}
fixPosition(table,tableBody);
$(table).trigger("applyWidgets");
if( c.page >= c.totalPages ) {
moveToLastPage(table);
}
updatePageDisplay(c);
}
this.appender = function(table,rows) {
var c = table.config;
c.rowsCopy = rows;
c.totalRows = rows.length;
c.totalPages = Math.ceil(c.totalRows / c.size);
renderTable(table,rows);
};
this.defaults = {
size: 10,
offset: 0,
page: 0,
totalRows: 0,
totalPages: 0,
container: null,
cssNext: '.next',
cssPrev: '.prev',
cssFirst: '.first',
cssLast: '.last',
cssPageDisplay: '.pagedisplay',
cssPageSize: '.pagesize',
seperator: "/",
positionFixed: true,
appender: this.appender
};
this.construct = function(settings) {
return this.each(function() {
config = $.extend(this.config, $.tablesorterPager.defaults, settings);
var table = this, pager = config.container;
$(this).trigger("appendCache");
config.size = parseInt($(".pagesize",pager).val());
$(config.cssFirst,pager).click(function() {
moveToFirstPage(table);
return false;
});
$(config.cssNext,pager).click(function() {
moveToNextPage(table);
return false;
});
$(config.cssPrev,pager).click(function() {
moveToPrevPage(table);
return false;
});
$(config.cssLast,pager).click(function() {
moveToLastPage(table);
return false;
});
$(config.cssPageSize,pager).change(function() {
setPageSize(table,parseInt($(this).val()));
return false;
});
});
};
}
});
// extend plugin scope
$.fn.extend({
tablesorterPager: $.tablesorterPager.construct
});
})(jQuery);

39
python/res/style.css Normal file
View file

@ -0,0 +1,39 @@
/* tables */
table.tablesorter {
font-family:arial;
background-color: #CDCDCD;
margin:10px 0pt 15px;
font-size: 8pt;
width: 100%;
text-align: left;
}
table.tablesorter thead tr th, table.tablesorter tfoot tr th {
background-color: #e6EEEE;
border: 1px solid #FFF;
font-size: 8pt;
padding: 4px;
}
table.tablesorter thead tr .header {
background-image: url(bg.gif);
background-repeat: no-repeat;
background-position: center right;
cursor: pointer;
}
table.tablesorter tbody td {
color: #3D3D3D;
padding: 4px;
background-color: #FFF;
vertical-align: top;
}
table.tablesorter tbody tr.odd td {
background-color:#F0F0F6;
}
table.tablesorter thead tr .headerSortUp {
background-image: url(asc.gif);
}
table.tablesorter thead tr .headerSortDown {
background-image: url(desc.gif);
}
table.tablesorter thead tr .headerSortDown, table.tablesorter thead tr .headerSortUp {
background-color: #8dbdd8;
}

View file

@ -4,35 +4,104 @@ import ledger
import cgi
import sys
import types
import posixpath
import urllib
import shutil
import os
import re
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from os.path import exists, join, isfile
from Cheetah.Template import Template
from Cheetah.Filters import WebSafe
from Cheetah.Filters import Filter, WebSafe
class UnicodeFilter(WebSafe):
webroot = join(os.getcwd(), 'python', 'res')
class UnicodeFilter(Filter):
def filter(self, s, **kargs):
return WebSafe.filter(self, s, str=unicode, **kargs)
return Filter.filter(self, s, str=unicode, **kargs)
def strip(value):
#return re.sub('\n', '<br />', value.strip_annotations().to_string())
return value.strip_annotations().to_string()
templateDef = '''#encoding utf-8
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>$title</title>
<link rel="stylesheet" href="/style.css" type="text/css" media="print, projection, screen" />
<script type="text/javascript" src="/jquery-latest.js"></script>
<script type="text/javascript" src="/jquery.tablesorter.min.js"></script>
<script type="text/javascript" src="/jquery.tablesorter.pager.js"></script>
<script type="text/javascript" src="/jquery.dimensions.min.js"></script>
<script type="text/javascript">
\$(function() {
\$("table")
.tablesorter({textExtraction: 'complex',
widthFixed: true,
widgets: ['zebra']})
.tablesorterPager({size: 100,
container: \$("\#pager")});
});
</script>
</head>
<body>
<table>
#for $xact in $journal
#for $post in $xact
<tr>
<td>$post.date</td>
<td>$post.xact.payee</td>
<td>$post.account</td>
<td>$post.amount</td>
</tr>
#end for
#end for
<div id="main">
<h1>Register report</h1>
<table id="register" cellspacing="1" class="tablesorter">
<thead>
<tr>
<th>Date</th>
<th>Payee</th>
<th>Account</th>
<th>Amount</th>
<th>Total</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Date</th>
<th>Payee</th>
<th>Account</th>
<th>Amount</th>
<th>Total</th>
</tr>
</tfoot>
<tbody>
#for $xact in $journal
#for $post in $xact
#set $total = $total + $post.amount
<tr>
<!--<td>${$xact.date if $xact is not $last_xact else $empty}</td>
<td>${$xact.payee if $xact is not $last_xact else $empty}</td>-->
<td>$xact.date</td>
<td>$xact.payee</td>
<td>$post.account</td>
<td>${strip($post.amount)}</td>
<td>${strip($total)}</td>
</tr>
#set $last_xact = $xact
#end for
#end for
</tbody>
</table>
<div id="pager" class="pager">
<form>
<img src="/icons/first.png" class="first"/>
<img src="/icons/prev.png" class="prev"/>
<input type="text" class="pagedisplay"/>
<img src="/icons/next.png" class="next"/>
<img src="/icons/last.png" class="last"/>
<select class="pagesize">
<option selected="selected" value="100">100</option>
<option value="200">200</option>
<option value="500">500</option>
<option value="1000">1000</option>
</select>
</form>
</div>
</body>
</html>
'''
@ -43,14 +112,23 @@ class LedgerHandler(BaseHTTPRequestHandler):
BaseHTTPRequestHandler.__init__(self, *args)
def do_GET(self):
tmpl = Template(templateDef, filter=UnicodeFilter)
path = self.translate_path(self.path)
tmpl.title = 'Ledger Journal'
tmpl.journal = self.journal
if path and exists(path) and isfile(path):
self.copyfile(open(path), self.wfile)
else:
tmpl = Template(templateDef, filter=UnicodeFilter)
html = unicode(tmpl)
html = html.encode('utf-8')
self.wfile.write(html)
tmpl.title = 'Ledger Journal'
tmpl.journal = self.journal
tmpl.total = ledger.Value(0)
tmpl.strip = strip
tmpl.last_xact = None
tmpl.empty = ""
html = unicode(tmpl)
html = html.encode('utf-8')
self.wfile.write(html)
def do_POST(self):
print "Saw a POST request!"
@ -63,6 +141,45 @@ class LedgerHandler(BaseHTTPRequestHandler):
except Exception:
print "Saw exception in POST handler"
# This code is straight from SimpleHTTPServer.py
def copyfile(self, source, outputfile):
"""Copy all data between two file objects.
The SOURCE argument is a file object open for reading
(or anything with a read() method) and the DESTINATION
argument is a file object open for writing (or
anything with a write() method).
The only reason for overriding this would be to change
the block size or perhaps to replace newlines by CRLF
-- note however that this the default server uses this
to copy binary data as well.
"""
shutil.copyfileobj(source, outputfile)
def translate_path(self, path):
"""Translate a /-separated PATH to the local filename syntax.
Components that mean special things to the local file system
(e.g. drive or directory names) are ignored. (XXX They should
probably be diagnosed.)
"""
# abandon query parameters
path = path.split('?',1)[0]
path = path.split('#',1)[0]
path = posixpath.normpath(urllib.unquote(path))
words = path.split('/')
words = filter(None, words)
path = webroot
for word in words:
drive, word = os.path.splitdrive(word)
head, word = os.path.split(word)
if word in (os.curdir, os.pardir): continue
path = os.path.join(path, word)
return path
def main(*args):
try:
port = 9000

View file

@ -541,8 +541,8 @@ public:
void print(std::ostream& out,
const int first_width = -1,
const int latter_width = -1,
const bool right_justify = true,
const bool colorize = true) const;
const bool right_justify = false,
const bool colorize = false) const;
/**
* Debugging methods. There are two methods defined to help with

View file

@ -1114,18 +1114,18 @@ void value_t::in_place_cast(type_t cast_type)
break;
}
case BALANCE:
case BALANCE: {
const balance_t& bal(as_balance());
switch (cast_type) {
case AMOUNT: {
const balance_t& temp(as_balance());
if (temp.amounts.size() == 1) {
if (bal.amounts.size() == 1) {
// Because we are changing the current balance value to an amount
// value, and because set_amount takes a reference (and that memory is
// about to be repurposed), we must pass in a copy.
set_amount(amount_t((*temp.amounts.begin()).second));
set_amount(amount_t((*bal.amounts.begin()).second));
return;
}
else if (temp.amounts.size() == 0) {
else if (bal.amounts.size() == 0) {
set_amount(0L);
return;
}
@ -1135,10 +1135,17 @@ void value_t::in_place_cast(type_t cast_type)
}
break;
}
case STRING:
if (bal.is_empty())
set_string("");
else
set_string(as_balance().to_string());
return;
default:
break;
}
break;
}
case STRING:
switch (cast_type) {