more template/css overhaul

This commit is contained in:
Lance Edgar 2012-08-14 13:28:39 -07:00
parent 005fca223f
commit 26ffcc865a
20 changed files with 337 additions and 580 deletions

View file

@ -44,13 +44,19 @@ __all__ = ['Grid']
class Grid(edbob.Object):
full = False
hoverable = True
clickable = False
checkboxes = False
deletable = False
partial_only = False
row_route_name = None
row_route_kwargs = None
click_route_name = None
click_route_kwargs = None
delete_route_name = None
delete_route_kwargs = None
def __init__(self, request, **kwargs):
kwargs.setdefault('fields', OrderedDict())
@ -65,23 +71,38 @@ class Grid(edbob.Object):
def column_header(self, field):
return literal('<th field="%s">%s</th>' % (field.name, field.label))
def div_class(self):
if self.clickable:
return 'grid clickable'
if self.hoverable:
return 'grid hoverable'
return 'grid'
def _div_attrs(self):
attrs = {'class_':'grid', 'url':self.request.current_route_url()}
if self.clickable:
attrs['class_'] = 'grid clickable'
elif self.hoverable:
attrs['class_'] = 'grid hoverable'
return attrs
def div_attrs(self):
return format_attrs(**self._div_attrs())
classes = ['grid']
if self.full:
classes.append('full')
if self.clickable:
classes.append('clickable')
if self.hoverable:
classes.append('hoverable')
return format_attrs(
class_=' '.join(classes),
url=self.request.current_route_url())
def get_delete_url(self, row):
kwargs = {}
if self.delete_route_kwargs:
if callable(self.delete_route_kwargs):
kwargs = self.delete_route_kwargs(row)
else:
kwargs = self.delete_route_kwargs
return self.request.route_url(self.delete_route_name, **kwargs)
def get_row_attrs(self, row, i):
attrs = self.row_attrs(row, i)
if self.clickable:
kwargs = {}
if self.click_route_kwargs:
if callable(self.click_route_kwargs):
kwargs = self.click_route_kwargs(row)
else:
kwargs = self.click_route_kwargs
attrs['url'] = self.request.route_url(self.click_route_name, **kwargs)
return format_attrs(**attrs)
def iter_fields(self):
return self.fields.itervalues()
@ -99,15 +120,3 @@ class Grid(edbob.Object):
def row_attrs(self, row, i):
attrs = {'class_': 'odd' if i % 2 else 'even'}
return attrs
def get_row_attrs(self, row, i):
attrs = self.row_attrs(row, i)
if self.row_route_name:
kwargs = {}
if self.row_route_kwargs:
if callable(self.row_route_kwargs):
kwargs = self.row_route_kwargs(row)
else:
kwargs = self.row_route_kwargs
attrs['url'] = self.request.route_url(self.row_route_name, **kwargs)
return format_attrs(**attrs)

View file

@ -159,7 +159,7 @@ def filter_ilike(field):
return {'lk': ilike, 'nl': not_ilike}
def get_filter_config(name, request, filter_map, **kwargs):
def get_filter_config(prefix, request, filter_map, **kwargs):
"""
Returns a configuration dictionary for a search form.
"""
@ -191,7 +191,7 @@ def get_filter_config(name, request, filter_map, **kwargs):
config.update(kwargs)
# Update config with data cached in session.
update_config(request.session, prefix=name+'.')
update_config(request.session, prefix=prefix+'.')
# Update config with data from GET/POST request.
if request.params.get('filters') == 'true':
@ -201,7 +201,7 @@ def get_filter_config(name, request, filter_map, **kwargs):
for key in config:
if (not key.startswith('filter_factory_')
and not key.startswith('filter_label_')):
request.session[name+'.'+key] = config[key]
request.session[prefix+'.'+key] = config[key]
return config

View file

@ -1,98 +0,0 @@
/************************************************************
* crud.css
*
* Styles specific to "object CRUD" pages.
************************************************************/
/******************************
* Wrapper
******************************/
div.crud {
font-size: 10pt;
margin: auto;
}
/******************************
* Context Menu
******************************/
div.crud #context-menu {
float: right;
list-style-type: none;
}
/******************************
* Fieldsets
******************************/
/* div.crud div.field-couple { */
/* clear: both; */
/* overflow: auto; */
/* min-height: 30px; */
/* } */
/* /\* div.crud div.field-couple div.label, *\/ */
/* div.crud div.field-couple label { */
/* display: block; */
/* float: left; */
/* width: 140px; */
/* font-weight: bold; */
/* margin-top: 2px; */
/* white-space: nowrap; */
/* } */
/* div.crud div.field-couple div.field { */
/* display: block; */
/* float: left; */
/* margin-bottom: 5px; */
/* line-height: 25px; */
/* } */
/* div.crud div.field-couple div.field input[type=text], */
/* div.crud div.field-couple div.field input[type=password], */
/* div.crud div.field-couple div.field select { */
/* width: 320px; */
/* } */
/* unbound checkbox field, e.g. 'add another' */
div.crud div.checkbox {
margin: 10px 0px;
padding-left: 3px;
}
/* div.crud div.buttons { */
/* clear: both; */
/* margin-top: 10px; */
/* } */
/* div.crud div.buttons * { */
/* margin-right: 8px; */
/* } */
/* div.crud table.fieldset tbody tr { */
/* vertical-align: top; */
/* } */
/* div.crud table.fieldset tbody td { */
/* height: 30px; */
/* padding: 2px; */
/* } */
/* div.crud table.fieldset td.label { */
/* font-weight: bold; */
/* width: 120px; */
/* } */
/* div.crud table.fieldset tbody td ul { */
/* padding-left: 15px; */
/* } */
/* div.crud table.fieldset tbody td ul li { */
/* line-height: 1em; */
/* margin-bottom: 4px; */
/* } */

View file

@ -9,7 +9,6 @@
html, body {
font-family: sans-serif;
font-size: .9em;
}
a {
@ -28,11 +27,6 @@ li {
overflow: auto;
}
table.wrapper {
/* border: 1px solid black; */
width: 100%;
}
.left {
float: left;
text-align: left;
@ -169,30 +163,6 @@ div.controls label {
}
/******************************
* Header Table
******************************/
table.header {
padding-bottom: 5px;
width: 100%;
}
table.header td.context-menu {
vertical-align: top;
}
table.header td.context-menu ul {
list-style-type: none;
text-align: right;
}
table.header td.tools {
text-align: right;
vertical-align: bottom;
}
/******************************
* Dialogs
******************************/
@ -201,200 +171,7 @@ div.dialog {
display: none;
}
#feedback-dialog textarea {
height: 180px;
width: 500px;
}
/******************************
* Filters
******************************/
div.filters div.filter div.value {
display: inline;
}
/******************************
* Grids
******************************/
div.grid {
clear: both;
}
div.grid table {
border-top: 1px solid black;
border-left: 1px solid black;
border-collapse: collapse;
font-size: 9pt;
line-height: normal;
white-space: nowrap;
}
div.grid.full table {
width: 100%;
}
div.grid table th,
div.grid table td {
border-right: 1px solid black;
border-bottom: 1px solid black;
padding: 2px 3px;
}
div.grid table th.sortable a {
display: block;
padding-right: 18px;
}
div.grid table th.sorted {
background-position: right center;
background-repeat: no-repeat;
}
div.grid table th.sorted.asc {
background-image: url(../img/sort_arrow_up.png);
}
div.grid table th.sorted.desc {
background-image: url(../img/sort_arrow_down.png);
}
div.grid table tbody td {
text-align: left;
}
div.grid table tbody td.right {
text-align: right;
}
div.grid table tr.odd {
background-color: #e0e0e0;
}
/* div.grid table thead th.checkbox, */
/* div.grid table tbody td.checkbox { */
/* text-align: center; */
/* vertical-align: middle; */
/* width: 15px; */
/* #feedback-dialog textarea { */
/* height: 180px; */
/* width: 500px; */
/* } */
/* div.grid table td.action { */
/* cursor: default; */
/* } */
div.grid table td.delete {
text-align: center;
width: 18px;
background-image: url(../img/delete.png);
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
}
div.grid table tbody tr.hovering {
background-color: #bbbbbb;
}
div.grid table.hoverable tbody tr {
cursor: default;
}
div.grid.clickable table tbody tr,
div.grid table.selectable tbody tr,
div.grid table.checkable tbody tr {
cursor: pointer;
}
/* div.grid table.selectable tbody tr.selected, */
/* div.grid table.checkable tbody tr.selected { */
/* background-color: #666666; */
/* color: white; */
/* } */
div.pager {
margin-top: 5px;
}
div.pager p {
font-size: 10pt;
margin: 0px;
}
div.pager p.showing {
float: left;
}
div.pager #grid-page-count {
font-size: 8pt;
}
div.pager p.page-links {
float: right;
}
/* /\****************************** */
/* * Sub-Grids */
/* ******************************\/ */
/* div.subgrid { */
/* margin-top: 20px; */
/* } */
/* div.subgrid label { */
/* font-weight: bold; */
/* display: block; */
/* float: left; */
/* margin-bottom: 5px; */
/* } */
/******************************
* Fieldsets
******************************/
div.field-wrapper {
clear: both;
overflow: auto;
min-height: 30px;
}
div.field-wrapper label {
display: block;
float: left;
width: 140px;
font-weight: bold;
margin-top: 2px;
white-space: nowrap;
}
/* div.field-couple div.field-error { */
/* clear: both; */
/* color: #dd6666; */
/* font-weight: bold; */
/* } */
div.field-wrapper div.field {
display: block;
float: left;
margin-bottom: 5px;
line-height: 25px;
}
div.field-wrapper div.field input[type=text],
div.field-wrapper div.field input[type=password],
div.field-wrapper div.field select {
width: 320px;
}
div.buttons {
clear: both;
margin-top: 10px;
}
div.buttons * {
margin-right: 8px;
}

View file

@ -0,0 +1,24 @@
/******************************
* Filters
******************************/
div.filters div.filter {
margin-bottom: 10px;
}
div.filters div.filter label {
margin-right: 8px;
}
div.filters div.filter select.filter-type {
margin-right: 8px;
}
div.filters div.filter div.value {
display: inline;
}
div.filters div.buttons * {
margin-right: 8px;
}

View file

@ -0,0 +1,70 @@
/******************************
* Context Menu
******************************/
div.form-wrapper ul.context-menu {
float: right;
list-style-type: none;
margin: 0px;
text-align: right;
}
/******************************
* Forms
******************************/
div.fieldset-form {
float: left;
margin-left: 50px;
margin-top: 10px;
}
/******************************
* Fieldsets
******************************/
div.field-wrapper {
clear: both;
overflow: auto;
min-height: 30px;
}
div.field-wrapper label {
display: block;
float: left;
width: 140px;
font-weight: bold;
margin-top: 2px;
white-space: nowrap;
}
/* div.field-couple div.field-error { */
/* clear: both; */
/* color: #dd6666; */
/* font-weight: bold; */
/* } */
div.field-wrapper div.field {
display: block;
float: left;
margin-bottom: 5px;
line-height: 25px;
}
div.field-wrapper div.field input[type=text],
div.field-wrapper div.field input[type=password],
div.field-wrapper div.field select {
width: 320px;
}
div.buttons {
clear: both;
margin-top: 10px;
}
div.buttons * {
margin-right: 8px;
}

View file

@ -0,0 +1,150 @@
/******************************
* Grid Header
******************************/
table.grid-header {
padding-bottom: 5px;
width: 100%;
}
/******************************
* Context Menu
******************************/
table.grid-header td.context-menu {
vertical-align: top;
}
table.grid-header td.context-menu ul {
list-style-type: none;
margin: 0px;
text-align: right;
}
/******************************
* Tools
******************************/
table.grid-header td.tools {
text-align: right;
vertical-align: bottom;
}
/******************************
* Grid
******************************/
div.grid {
clear: both;
}
div.grid table {
border-top: 1px solid black;
border-left: 1px solid black;
border-collapse: collapse;
font-size: 9pt;
line-height: normal;
white-space: nowrap;
}
div.grid.full table {
width: 100%;
}
div.grid table th,
div.grid table td {
border-right: 1px solid black;
border-bottom: 1px solid black;
padding: 2px 3px;
}
div.grid table th.sortable a {
display: block;
padding-right: 18px;
}
div.grid table th.sorted {
background-position: right center;
background-repeat: no-repeat;
}
div.grid table th.sorted.asc {
background-image: url(../img/sort_arrow_up.png);
}
div.grid table th.sorted.desc {
background-image: url(../img/sort_arrow_down.png);
}
div.grid table tbody td {
text-align: left;
}
div.grid table tbody td.right {
text-align: right;
}
div.grid table tr.odd {
background-color: #e0e0e0;
}
/* div.grid table thead th.checkbox, */
/* div.grid table tbody td.checkbox { */
/* text-align: center; */
/* vertical-align: middle; */
/* width: 15px; */
/* } */
div.grid table td.delete {
text-align: center;
width: 18px;
background-image: url(../img/delete.png);
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
}
div.grid table tbody tr.hovering {
background-color: #bbbbbb;
}
div.grid table.hoverable tbody tr {
cursor: default;
}
div.grid.clickable table tbody tr,
div.grid table.selectable tbody tr,
div.grid table.checkable tbody tr {
cursor: pointer;
}
/* div.grid table.selectable tbody tr.selected, */
/* div.grid table.checkable tbody tr.selected { */
/* background-color: #666666; */
/* color: white; */
/* } */
div.pager {
margin-top: 5px;
}
div.pager p {
font-size: 10pt;
margin: 0px;
}
div.pager p.showing {
float: left;
}
div.pager #grid-page-count {
font-size: 8pt;
}
div.pager p.page-links {
float: right;
}

View file

@ -1,165 +0,0 @@
/************************************************************
* index.css
*
* Styles specific to "object index" pages.
************************************************************/
/******************************
* Wrapper
******************************/
div.object-index {
font-size: 10pt;
}
div.object-index table.header {
padding-bottom: 5px;
width: 100%;
}
/******************************
* Context Menu
******************************/
div.object-index table.header td.context-menu {
vertical-align: top;
}
div.object-index table.header td.context-menu ul {
list-style-type: none;
text-align: right;
}
/******************************
* Filters
******************************/
div.object-index div.filters div.filter {
margin-bottom: 10px;
}
div.object-index div.filters div.filter label,
div.object-index div.filters div.filter select.filter-type {
margin-right: 8px;
}
div.object-index div.filters div.buttons * {
margin-right: 8px;
}
/******************************
* Tools
******************************/
div.object-index table.header td.tools {
text-align: right;
vertical-align: bottom;
}
/******************************
* Grids
******************************/
/* div.object-index div.grid { */
/* clear: both; */
/* } */
div.object-index div.grid table {
/* border-top: 1px solid black; */
/* border-left: 1px solid black; */
/* border-collapse: collapse; */
/* font-size: 9pt; */
/* line-height: normal; */
/* white-space: nowrap; */
width: 100%;
}
/* div.object-index div.grid table th, */
/* div.object-index div.grid table td { */
/* border-right: 1px solid black; */
/* border-bottom: 1px solid black; */
/* padding: 2px 3px; */
/* } */
/* div.object-index div.grid table th.sortable a { */
/* display: block; */
/* padding-right: 18px; */
/* } */
/* div.object-index div.grid table th.sorted { */
/* background-position: right center; */
/* background-repeat: no-repeat; */
/* } */
/* div.object-index div.grid table th.sorted.asc { */
/* background-image: url(../img/sort_arrow_up.png); */
/* } */
/* div.object-index div.grid table th.sorted.desc { */
/* background-image: url(../img/sort_arrow_down.png); */
/* } */
/* div.object-index div.grid table tbody td { */
/* text-align: left; */
/* } */
/* div.object-index div.grid table tr.even { */
/* background-color: #e0e0e0; */
/* } */
/* div.object-index div.grid table td.delete { */
/* text-align: center; */
/* width: 18px; */
/* background-image: url(../img/delete.png); */
/* background-repeat: no-repeat; */
/* background-position: center; */
/* cursor: pointer; */
/* } */
/* div.object-index div.grid table tbody tr.hovering { */
/* background-color: #bbbbbb; */
/* } */
/* div.object-index div.grid table.hoverable tbody tr { */
/* cursor: default; */
/* } */
/* div.object-index div.grid.clickable table tbody tr, */
/* div.object-index div.grid table.selectable tbody tr, */
/* div.object-index div.grid table.checkable tbody tr { */
/* cursor: pointer; */
/* } */
/* div.grid table.selectable tbody tr.selected, */
/* div.grid table.checkable tbody tr.selected { */
/* background-color: #666666; */
/* color: white; */
/* } */
/* div.object-index div.pager { */
/* margin-top: 5px; */
/* } */
/* div.object-index div.pager p { */
/* font-size: 10pt; */
/* margin: 0px; */
/* } */
/* div.object-index div.pager p.showing { */
/* float: left; */
/* } */
/* div.object-index div.pager #grid-page-count { */
/* font-size: 8pt; */
/* height: 21px; */
/* } */
/* div.object-index div.pager p.page-links { */
/* float: right; */
/* } */

View file

@ -287,18 +287,13 @@ $(function() {
$(this).toggleClass('selected');
});
$('div.grid td.delete').live('click', function() {
var grid = $(this).parents('div.grid:first');
var url = grid.attr('delurl');
$('div.grid table tbody td.delete').live('click', function() {
var url = $(this).attr('url');
if (url) {
if (confirm("Do you really wish to delete this object?")) {
location.href = url.replace(/%7Buuid%7D/, get_uuid(this));
location.href = url;
}
} else {
alert("Hm, I don't know how to delete that..\n\n"
+ "(Add a 'delurl' parameter to the AlchemyGrid instance.)");
}
return false;
});
$('#grid-page-count').live('change', function() {

View file

@ -1,2 +1,3 @@
<%inherit file="/edbob/crud.mako" />
${parent.body()}

View file

@ -18,9 +18,12 @@
${h.javascript_link(request.static_url('edbob.pyramid:static/js/jquery.autocomplete.js'))}
${h.javascript_link(request.static_url('edbob.pyramid:static/js/edbob.js'))}
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/smoothness/jquery-ui-1.8.2.custom.css'))}
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/edbob.css'))}
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/grids.css'))}
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/filters.css'))}
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/forms.css'))}
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/autocomplete.css'))}
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/smoothness/jquery-ui-1.8.2.custom.css'))}
${self.head_tags()}
</head>

View file

@ -2,26 +2,11 @@
<%def name="title()">${(fieldset.crud_title+' : '+fieldset.get_display_text() if fieldset.edit else 'New '+fieldset.crud_title) if crud else ''|n}</%def>
<%def name="head_tags()">
${parent.head_tags()}
<style type="text/css">
#context-menu {
float: right;
}
div.fieldset {
float: left;
}
</style>
</%def>
<%def name="context_menu_items()"></%def>
<div class="wrapper">
<div class="form-wrapper">
<ul id="context-menu">
<ul class="context-menu">
${self.context_menu_items()}
</ul>

View file

@ -1,16 +1,11 @@
<%inherit file="/base.mako" />
<%def name="head_tags()">
${parent.head_tags()}
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/index.css'))}
</%def>
<%def name="context_menu_items()"></%def>
<%def name="tools()"></%def>
<div class="object-index">
<div class="grid-wrapper">
<table class="header">
<table class="grid-header">
<tr>
% if search:
<td rowspan="2" class="filters">
@ -30,8 +25,8 @@
${self.tools()}
</td>
</tr>
</table>
</table><!-- grid-header -->
${grid}
</div>
</div><!-- grid-wrapper -->

View file

@ -0,0 +1,3 @@
<%inherit file="/edbob/grid.mako" />
${parent.body()}

View file

@ -29,7 +29,7 @@
<td class="noclick ${col.name}">${col.callback(row)}</td>
% endfor
% if grid.deletable:
<td class="delete">&nbsp;</td>
<td class="noclick delete" url="${grid.get_delete_url(row)}">&nbsp;</td>
% endif
</tr>
% endfor

View file

@ -1,2 +0,0 @@
<%inherit file="/edbob/index.mako" />
${parent.body()}

View file

@ -134,6 +134,8 @@ class Crud(object):
model = Session.query(self.mapped_class).get(uuid) if uuid else None
assert model
Session.delete(model)
self.request.session.flash("The %s has been deleted." %
self.mapped_class.__name__)
return HTTPFound(location=self.home_url)
@classmethod

View file

@ -31,6 +31,7 @@ from webhelpers import paginate
from edbob.pyramid import grids
from edbob.pyramid import Session
from edbob.pyramid.views.grids.core import GridView
from edbob.util import requires_impl
__all__ = ['AlchemyGridView', 'SortableAlchemyGridView',
@ -64,6 +65,11 @@ class SortableAlchemyGridView(AlchemyGridView):
sort = None
@property
@requires_impl(is_property=True)
def config_prefix(self):
pass
def join_map(self):
return {}
@ -79,7 +85,7 @@ class SortableAlchemyGridView(AlchemyGridView):
def make_sort_config(self, **kwargs):
return grids.util.get_sort_config(
self.route_name, self.request, **kwargs)
self.config_prefix, self.request, **kwargs)
def sort_config(self):
return self.make_sort_config(sort=self.sort)
@ -111,6 +117,8 @@ class SortableAlchemyGridView(AlchemyGridView):
class PagedAlchemyGridView(SortableAlchemyGridView):
full = True
def make_pager(self):
config = self._sort_config
query = self.query()
@ -144,7 +152,7 @@ class SearchableAlchemyGridView(PagedAlchemyGridView):
def make_filter_config(self, **kwargs):
return grids.search.get_filter_config(
self.route_name, self.request, self.filter_map(), **kwargs)
self.config_prefix, self.request, self.filter_map(), **kwargs)
def filter_config(self):
return self.make_filter_config()

View file

@ -39,13 +39,19 @@ class GridView(View):
route_url = None
renderer = None
permission = None
full = False
checkboxes = False
clickable = False
deletable = False
partial_only = False
def update_grid_kwargs(self, kwargs):
kwargs.setdefault('full', self.full)
kwargs.setdefault('checkboxes', self.checkboxes)
kwargs.setdefault('clickable', self.clickable)
kwargs.setdefault('deletable', self.deletable)
kwargs.setdefault('partial_only', self.partial_only)
def make_grid(self, **kwargs):

View file

@ -34,17 +34,16 @@ from formalchemy.fields import SelectFieldRenderer
import edbob
from edbob.db.auth import set_user_password
from edbob.pyramid import Session
from edbob.pyramid.filters import filter_ilike
from edbob.pyramid.grids import sorter
from edbob.pyramid.views import GridView
from edbob.pyramid.views import SearchableAlchemyGridView
from edbob.pyramid.views.crud import Crud
class UserGrid(GridView):
class UsersGrid(SearchableAlchemyGridView):
mapped_class = edbob.User
route_name = 'users.list'
route_prefix = 'user'
route_name = 'users'
route_url = '/users'
sort = 'username'
def join_map(self):
return {
@ -55,27 +54,22 @@ class UserGrid(GridView):
def filter_map(self):
return self.make_filter_map(
ilike=['username'],
person=filter_ilike(edbob.Person.display_name))
person=self.filter_ilike(edbob.Person.display_name))
def search_config(self, fmap):
return self.make_search_config(
fmap,
def filter_config(self):
return self.make_filter_config(
include_filter_username=True,
filter_type_username='lk',
include_filter_person=True,
filter_type_person='lk')
def grid_config(self, search, fmap):
return self.make_grid_config(search, fmap,
sort='username')
def sort_map(self):
return self.make_sort_map(
'username',
person=sorter(edbob.Person.display_name))
person=self.sorter(edbob.Person.display_name))
def grid(self, data, config):
g = self.make_grid(data, config)
def grid(self):
g = self.make_grid()
g.configure(
include=[
g.username,
@ -219,5 +213,5 @@ class UserCrud(Crud):
def includeme(config):
UserGrid.add_route(config, 'users.list', '/users')
UsersGrid.add_route(config)
UserCrud.add_routes(config)