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

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 );