Add support for "new-style grids" and "model master views".

Finally, an API that makes some sense...  We don't yet have feature parity
with the old-style grids and CRUD views, but this is already a significant
improvement to the design.  Still needs a lot of docs though...
This commit is contained in:
Lance Edgar 2015-07-29 11:09:38 -05:00
parent 62b7194c21
commit 585eb09bec
26 changed files with 2296 additions and 94 deletions

View file

@ -64,6 +64,16 @@ body > #body-wrapper {
}
/******************************
* context menu
******************************/
#context-menu {
float: right;
list-style-type: none;
}
/******************************
* Panels
******************************/

View file

@ -0,0 +1,190 @@
/********************************************************************************
* newgrids.css
*
* Style tweaks for the new grids.
********************************************************************************/
/******************************
* filters
******************************/
.newgrid-wrapper .newfilters {
margin-right: 15em;
}
.newgrid-wrapper .newfilters fieldset {
margin: -8px 0 5px 0;
padding: 1px 5px 5px 5px;
}
.newgrid-wrapper .newfilters .filter {
margin-bottom: 2px;
}
.newgrid-wrapper .newfilters .filter:last-child {
margin-bottom: 0;
}
.newgrid-wrapper .newfilters .filter .toggle {
margin: 0;
text-align: left;
width: 15em;
}
.newgrid-wrapper .newfilters .ui-button-text {
line-height: 1.4em;
}
.newgrid-wrapper .newfilters .ui-button-text-icon-primary .ui-button-text {
padding: 0.2em 1em 0.2em 2.1em;
}
.newgrid-wrapper .newfilters .ui-selectmenu-button .ui-selectmenu-text {
padding: 0.2em 2.1em 0.2em 1em;
}
.newgrid-wrapper .newfilters .filter label {
font-weight: bold;
padding: 0.4em 0.2em;
position: relative;
top: -14px;
}
.newgrid-wrapper .newfilters .filter .value {
display: inline-block;
vertical-align: top;
}
.newgrid-wrapper .newfilters .filter .value input {
height: 19px;
}
.newgrid-wrapper .newfilters .filter .inputs {
display: inline-block;
/* TODO: Would be nice not to hard-code a height here... */
height: 26px;
vertical-align: top;
}
.newgrid-wrapper .newfilters .buttons {
margin: 0 0 5px 0;
}
.newgrid-wrapper .newfilters #add-filter-button {
margin-right: 5px;
vertical-align: middle;
}
/******************************
* table
******************************/
.newgrid table {
background-color: white;
border: 1px solid black;
border-collapse: collapse;
font-size: 10pt;
line-height: normal;
white-space: nowrap;
}
.newgrid.full table {
width: 100%;
}
/******************************
* thead
******************************/
.newgrid table thead th {
border-right: 1px solid black;
border-bottom: 1px solid black;
padding: 2px 3px;
}
.newgrid table thead th:last-child {
border-right: none;
}
.newgrid table thead th.sortable a {
display: block;
padding-right: 18px;
}
.newgrid table thead th.sorted {
background-position: right center;
background-repeat: no-repeat;
}
.newgrid table thead th.sorted.asc {
background-image: url(../img/sort_arrow_up.png);
}
.newgrid table thead th.sorted.desc {
background-image: url(../img/sort_arrow_down.png);
}
/******************************
* tbody
******************************/
.newgrid table tbody td {
padding: 5px 6px;
}
.newgrid table tbody tr:nth-child(odd) {
background-color: #e0e0e0;
}
.newgrid table tbody tr.hovering {
background-color: #bbbbbb;
}
/******************************
* main actions
******************************/
.newgrid .actions {
width: 1px;
}
.newgrid .actions a {
margin: 0 5px 0 0;
position: relative;
top: -2px;
}
.newgrid .actions a:last-child {
margin: 0;
}
.newgrid .actions .ui-icon {
display: inline-block;
position: relative;
top: 3px;
}
/******************************
* more actions
******************************/
.newgrid .actions div.more {
background-color: white;
border: 1px solid black;
display: none;
padding: 3px 10px 3px 5px;
position: absolute;
z-index: 1;
}
.newgrid .actions .more a {
display: block;
padding: 2px 0;
}

243
tailbone/static/js/jquery.ui.tailbone.js vendored Normal file
View file

@ -0,0 +1,243 @@
// -*- coding: utf-8 -*-
/**********************************************************************
* jQuery UI plugins for Tailbone
**********************************************************************/
/**********************************************************************
* gridwrapper plugin
**********************************************************************/
(function($) {
$.widget('tailbone.gridwrapper', {
_create: function() {
var that = this;
// Snag some element references.
this.filters = this.element.find('.newfilters');
this.add_filter = this.filters.find('#add-filter');
this.apply_filters = this.filters.find('#apply-filters');
this.grid = this.element.find('.newgrid');
// Enhance filters etc.
this.filters.find('.filter').gridfilter();
this.apply_filters.button('option', 'icons', {primary: 'ui-icon-search'});
if (! this.filters.find('.active:checked').length) {
this.apply_filters.button('disable');
}
this.add_filter.selectmenu({
width: '15em',
// Initially disabled if contains no enabled filter options.
disabled: this.add_filter.find('option:enabled').length == 1,
// When add-filter choice is made, show/focus new filter value input,
// and maybe hide the add-filter selection or show the apply button.
change: function (event, ui) {
var filter = that.filters.find('#filter-' + ui.item.value);
var select = $(this);
var option = ui.item.element;
filter.gridfilter('active', true);
filter.gridfilter('focus');
select.val('');
option.attr('disabled', 'disabled');
select.selectmenu('refresh');
if (select.find('option:enabled').length == 1) { // prompt is always enabled
select.selectmenu('disable');
} else {
that.apply_filters.button('enable');
}
}
});
// Intercept filters form submittal, and submit via AJAX instead.
this.filters.find('form').on('submit', function() {
var form = $(this);
var settings = {filter: true, partial: true};
form.find('.filter').each(function() {
// currently active filters will be included in form data
if ($(this).gridfilter('active')) {
settings[$(this).data('key')] = $(this).gridfilter('value');
settings[$(this).data('key') + '.verb'] = $(this).gridfilter('verb');
// others will be hidden from view
} else {
$(this).gridfilter('hide');
}
});
// if no filters are visible, disable submit button
if (! form.find('.filter:visible').length) {
that.apply_filters.button('disable');
}
// okay, submit filters to server and refresh grid
that.refresh(settings);
return false;
});
// Refresh data when user clicks a sortable column header.
this.element.on('click', 'thead th.sortable a', function() {
var th = $(this).parent();
var data = {
sortkey: th.data('sortkey'),
sortdir: (th.hasClass('sorted') && th.hasClass('asc')) ? 'desc' : 'asc',
page: 1,
partial: true
};
that.refresh(data);
return false;
});
// Refresh data when user chooses a new page size setting.
this.element.on('change', '.pager #pagesize', function() {
var settings = {
partial: true,
pagesize: $(this).val()
};
that.refresh(settings);
});
// Refresh data when user clicks a pager link.
this.element.on('click', '.pager a', function() {
that.refresh(this.search.substring(1)); // remove leading '?'
return false;
});
// Add hover highlight effect to grid rows during mouse-over.
this.element.on('mouseenter', 'tbody tr', function() {
$(this).addClass('hovering');
});
this.element.on('mouseleave', 'tbody tr', function() {
$(this).removeClass('hovering');
});
// Show 'more' actions when user hovers over 'more' link.
this.element.on('mouseenter', '.actions a.more', function() {
that.grid.find('.actions div.more').hide();
$(this).siblings('div.more')
.show()
.position({my: 'left-5 top-4', at: 'left top', of: $(this)});
});
this.element.on('mouseleave', '.actions div.more', function() {
$(this).hide();
});
},
// Refreshes the visible data within the grid, according to the given settings.
refresh: function(settings) {
var that = this;
this.element.mask("Refreshing data...");
$.get(this.grid.data('url'), settings, function(data) {
that.grid.replaceWith(data);
that.grid = that.element.find('.newgrid');
that.element.unmask();
});
}
});
})( jQuery );
/**********************************************************************
* gridfilter plugin
**********************************************************************/
(function($) {
$.widget('tailbone.gridfilter', {
_create: function() {
// Track down some important elements.
this.checkbox = this.element.find('input[name$="-active"]');
this.label = this.element.find('label');
this.inputs = this.element.find('.inputs');
this.add_filter = this.element.parents('.newgrid-wrapper').find('#add-filter');
// Hide the checkbox and label, and add button for toggling active status.
this.checkbox.addClass('ui-helper-hidden-accessible');
this.label.hide();
this.activebutton = $('<button type="button" class="toggle" />')
.insertAfter(this.label)
.text(this.label.text())
.button({
icons: {primary: 'ui-icon-blank'}
});
// Enhance some more stuff.
this.inputs.find('.verb').selectmenu({width: '15em'});
// Listen for button click, to keep checkbox in sync.
this._on(this.activebutton, {
click: function(e) {
var checked = !this.checkbox.is(':checked');
this.checkbox.prop('checked', checked);
this.refresh();
if (checked) {
this.focus();
}
}
});
// Update the initial state of the button according to checkbox.
this.refresh();
},
refresh: function() {
if (this.checkbox.is(':checked')) {
this.activebutton.button('option', 'icons', {primary: 'ui-icon-check'});
this.inputs.show();
} else {
this.activebutton.button('option', 'icons', {primary: 'ui-icon-blank'});
this.inputs.hide();
}
},
active: function(value) {
if (value === undefined) {
return this.checkbox.is(':checked');
}
if (value) {
if (!this.checkbox.is(':checked')) {
this.checkbox.prop('checked', true);
this.refresh();
this.element.show();
}
} else if (this.checkbox.is(':checked')) {
this.checkbox.prop('checked', false);
this.refresh();
}
},
hide: function() {
this.active(false);
this.element.hide();
var option = this.add_filter.find('option[value="' + this.element.data('key') + '"]');
option.attr('disabled', false);
if (this.add_filter.selectmenu('option', 'disabled')) {
this.add_filter.selectmenu('enable');
}
this.add_filter.selectmenu('refresh');
},
focus: function() {
this.inputs.find('.value input').focus();
},
value: function() {
return this.inputs.find('.value input').val();
},
verb: function() {
return this.inputs.find('.verb').val();
}
});
})( jQuery );

View file

@ -112,6 +112,11 @@ $(function() {
$('input[type=submit]').button();
$('input[type=reset]').button();
/*
* Enhance new-style grids.
*/
$('.newgrid-wrapper').gridwrapper();
/*
* When filter labels are clicked, (un)check the associated checkbox.
*/