Adding the posibility to include custom buttons on modal footers, so we can for example add "test" buttons wherever they are needed

This commit is contained in:
Adolfo Gómez 2013-12-05 05:34:35 +00:00
parent f5e5d88973
commit c29e2d4dcb
8 changed files with 176 additions and 77 deletions

View File

@ -38,7 +38,7 @@ from uds.core import auths
from users_groups import Users, Groups
from uds.REST import Handler, NotFound
from uds.REST import NotFound
from uds.REST.model import ModelHandler
import logging
@ -50,12 +50,14 @@ logger = logging.getLogger(__name__)
class Authenticators(ModelHandler):
model = Authenticator
detail = { 'users': Users, 'groups':Groups }
save_fields = ['name', 'comments']
save_fields = ['name', 'comments', 'priority', 'small_name']
table_title = _('Current authenticators')
table_fields = [
{ 'name': {'title': _('Name'), 'visible': True, 'type': 'iconType' } },
{ 'comments': {'title': _('Comments')}},
{ 'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '5em'}},
{ 'small_name': {'title': _('Small name')}},
{ 'users_count': {'title': _('Users'), 'type': 'numeric', 'width': '5em'}}
]
@ -64,7 +66,7 @@ class Authenticators(ModelHandler):
def getGui(self, type_):
try:
return self.addDefaultFields(auths.factory().lookup(type_).guiDescription(), ['name', 'comments'])
return self.addDefaultFields(auths.factory().lookup(type_).guiDescription(), ['name', 'comments', 'priority', 'small_name'])
except:
raise NotFound('type not found')
@ -72,8 +74,10 @@ class Authenticators(ModelHandler):
type_ = auth.getType()
return { 'id': auth.id,
'name': auth.name,
'comments': auth.comments,
'priority': auth.priority,
'small_name': auth.small_name,
'users_count': auth.users.count(),
'type': type_.type(),
'comments': auth.comments,
}

View File

@ -79,7 +79,6 @@ class BaseModelHandler(Handler):
'tooltip': _('Name of this element'),
'order': -100,
})
# And maybe comments (only if model has this field)
if 'comments' in flds:
self.addField(gui, {
'name': 'comments',
@ -88,6 +87,25 @@ class BaseModelHandler(Handler):
'length': 256,
'order': -99,
})
if 'priority' in flds:
self.addField(gui, {
'name': 'priority',
'type': 'numeric',
'label': _('Priority'),
'tooltip': _('Selects the priority of this element (lower number means higher priority)'),
'length': 4,
'order': -98,
})
if 'small_name' in flds:
self.addField(gui, {
'name': 'small_name',
'type': 'text',
'label': _('Small name'),
'tooltip': _('Small name of this element'),
'length': 128,
'order': -97,
})
return gui
def type_as_dict(self, type_):

View File

@ -48,7 +48,7 @@ import logging
logger = logging.getLogger(__name__)
@denyBrowsers(browsers=['ie<9'])
@denyBrowsers(browsers=['ie<10'])
@webLoginRequired
def index(request):
if request.user.isStaff() is False:

View File

@ -29,10 +29,6 @@ gui.dashboard.link = function(event) {
gui.providers = new GuiElement(api.providers, 'provi');
gui.providers.link = function(event) {
"use strict";
// Cleans up memory used by other datatables
$.each($.fn.dataTable.fnTables(), function(undefined, tbl){
$(tbl).dataTable().fnDestroy();
});
api.templates.get('providers', function(tmpl) {
gui.clearWorkspace();
@ -117,6 +113,20 @@ gui.authenticators.link = function(event) {
}));
gui.setLinksEvents();
// Button definition to trigger "Test" action
var testButton = {
buttons: [
{
text: gettext('Test authenticator'),
css: 'btn-info',
action: function(event, form_selector, closeFnc) {
var fields = gui.forms.read(form_selector);
}
},
]
};
gui.authenticators.table({
container : 'auths-placeholder',
rowSelect : 'single',
@ -148,8 +158,8 @@ gui.authenticators.link = function(event) {
onRefresh : function() {
$('#users-placeholder').empty(); // Remove detail on parent refresh
},
onNew : gui.methods.typedNew(gui.authenticators, gettext('New authenticator'), gettext('Error creating authenticator')),
onEdit: gui.methods.typedEdit(gui.authenticators, gettext('Edit authenticator'), gettext('Error processing authenticator')),
onNew : gui.methods.typedNew(gui.authenticators, gettext('New authenticator'), gettext('Error creating authenticator'),testButton),
onEdit: gui.methods.typedEdit(gui.authenticators, gettext('Edit authenticator'), gettext('Error processing authenticator'), testButton),
onDelete: gui.methods.del(gui.authenticators, gettext('Delete authenticator'), gettext('Error deleting authenticator')),
});
@ -282,7 +292,7 @@ gui.clear_cache.link = function() {
"use strict";
api.getJson('cache/flush', {
success: function() {
gui.launchModal(gettext('Cache'), gettext('Cache has been flushed'), ' ' );
gui.launchModal(gettext('Cache'), gettext('Cache has been flushed'), { actionButton: ' ' } );
},
});

View File

@ -44,6 +44,7 @@ GuiElement.prototype = {
}
});
},
// Options: dictionary
// container: container ID of parent for the table. If undefined, table will be appended to workspace
// buttons: array of visible buttons (strings), valid are [ 'new', 'edit', 'refresh', 'delete', 'xls' ],
@ -212,8 +213,8 @@ GuiElement.prototype = {
var tbl = $('#' + tableId).dataTable();
// Clears selection first
TableTools.fnGetInstance(tableId).fnSelectNone();
if( data.length > 1000 )
api.tools.blockUI();
//if( data.length > 1000 )
api.tools.blockUI();
self.rest.overview(function(data) { // Restore overview
setTimeout( function() {
@ -245,7 +246,7 @@ GuiElement.prototype = {
};
};
var onCheck = options.onCheck || function() { return true }; // Default oncheck always returns true
var onCheck = options.onCheck || function(){ return true; }; // Default oncheck always returns true
// methods for buttons on row select
var editSelected = function(btn, obj, node) {
@ -445,7 +446,6 @@ GuiElement.prototype = {
}); // End Overview data
}); // End Tableinfo data
$('.DTTT_dropdown').remove(); // Tabletools keep adding garbage to end of body on each new table creation, so we simply remove it on each new creation
return '#' + tableId;
},
};

View File

@ -105,14 +105,6 @@
var init = function(formSelector) {
gui.doLog(formSelector, fillers);
/*var pos = 0;
var triggerChangeSequentially = function() {
if( pos >= fillers.length )
return;
$(formSelector + ' [name="' + fillers[pos].name + '"]').trigger('change');
pos = pos + 1;
};*/
var onChange = function(filler) {
return function() {
gui.doLog('Onchange invoked for ', filler);
@ -132,14 +124,13 @@
$select.append('<option value="' + value.id + '">' + value.text + '</option>');
});
$select.val(originalValues[sel.name]);
// Refresh selectpicker updated
// Refresh selectpicker if item is such
if($select.hasClass('selectpicker'))
$select.selectpicker('refresh');
// Trigger change for the changed item
$select.trigger('change');
});
//triggerChangeSequentially();
});
};
};
@ -148,7 +139,8 @@
$.each(fillers, function(undefined, f) {
$(formSelector + ' [name="' + f.name + '"]').on('change', onChange(f));
});
// Trigger first filler if it exists, this will cascade rest of "changes" if they exists
if( fillers.length )
$(formSelector + ' [name="' + fillers[0].name + '"]').trigger('change');
};
@ -173,15 +165,53 @@
return res;
};
gui.forms.launchModal = function(title, fields, item, onSuccess) {
// Options has this keys:
// title
// fields
// item
// success
// buttons: Array of buttons to be added to footer, with:
// text --> text of button
// css --> button style (btn-default, btn-warning, ...). If not defined, 'btn-default' will be used
// action --> function to be executed. Will be passed 3 parameters: event, formSelector and closeFnc
// (use gui.forms.read(form selector) to get fields, closeFnc() to close form if desired)
// Failed operations will show a modal with server error
gui.forms.launchModal = function(options, onSuccess) {
options = options || {};
var id = 'modal-' + Math.random().toString().split('.')[1]; // Get a random ID for this modal
var ff = gui.forms.fromFields(fields, item);
gui.appendToWorkspace(gui.modal(id, title, ff.html));
var ff = gui.forms.fromFields(options.fields, options.item);
var footer = '';
var clickEventHandlers = [];
if( options.buttons ) {
$.each(options.buttons, function(index, value){
var _id = id + '-footer-' + index;
var css = value.css || 'btn-default';
clickEventHandlers.push({id: '#' + _id, action: value.action });
footer += '<button id="' + _id + '" type="button" class="pull-left btn ' + css + '">' + value.text + '</button>';
});
}
gui.appendToWorkspace(gui.modal(id, options.title, ff.html, { footer: footer }));
id = '#' + id; // for jQuery
var formSelector = id + ' form';
var closeFnc = function(){$(id).modal('hide');};
if( ff.init )
ff.init(id);
// Append click events for custom buttons on footer
$.each(clickEventHandlers, function(undefined, value){
if( value.action ) {
$(value.id).on('click', function(event){
if( value.action(event, formSelector, closeFnc) == true ) {
$(id).modal('hide');
}
});
}
});
// Get form
var $form = $(id + ' form');
@ -214,11 +244,11 @@
$(id + ' .button-accept').click(function(){
if( !$form.valid() )
return;
if( onSuccess ) {
onSuccess(id + ' form', function(){$(id).modal('hide');}); // Delegate close to to onSuccess
if( options.success ) {
options.success(formSelector, closeFnc); // Delegate close to to onSuccess
return;
} else {
$(id).modal('hide');
closeFnc();
}
});

View File

@ -97,19 +97,23 @@
});
};
gui.modal = function(id, title, content, actionButton, closeButton) {
gui.modal = function(id, title, content, options) {
options = options || {};
return api.templates.evaluate('tmpl_modal', {
id: id,
title: title,
content: content,
button1: closeButton,
button2: actionButton
footer: options.footer,
button1: options.closeButton,
button2: options.actionButton,
});
};
gui.launchModal = function(title, content, actionButton, closeButton) {
gui.launchModal = function(title, content, options) {
options = options || {};
var id = Math.random().toString().split('.')[1]; // Get a random ID for this modal
gui.appendToWorkspace(gui.modal(id, title, content, actionButton, closeButton));
gui.appendToWorkspace(gui.modal(id, title, content, options));
id = '#' + id; // for jQuery
$(id).modal()
@ -130,7 +134,7 @@
gui.failRequestModalFnc = function(title) {
return function(jqXHR, textStatus, errorThrown) { // fail on put
gui.launchModal(title, jqXHR.responseText, ' ');
gui.launchModal(title, jqXHR.responseText, { actionButton: ' '});
};
};
@ -144,35 +148,56 @@
};
// Links methods
gui.deployed_services = function() {
gui.clearWorkspace();
gui.appendToWorkspace(gui.breadcrumbs(gettext('Deployed services')));
};
// Clean up several "internal" data
// I have discovered some "items" that are keep in memory, or that adds garbage to body (datatable && tabletools mainly)
// This place is where i add them to "clean" at least those things on page change
gui.cleanup = function() {
gui.doLog('Cleaning up things');
// Tabletools creates divs at end that do not get removed, here is a good place to ensure there is no garbage left behind
// And anyway, if this div does not exists, it creates a new one...
$('.DTTT_dropdown').remove(); // Tabletools keep adding garbage to end of body on each new table creation, so we simply remove it on each new creation
// Destroy any created datatable
$.each($.fn.dataTable.fnTables(), function(undefined, tbl){
$(tbl).dataTable().fnDestroy();
});
};
gui.setLinksEvents = function() {
var sidebarLinks = [
{
id : 'lnk-dashboard',
exec : gui.dashboard.link,
cleanup: true,
}, {
id : 'lnk-service_providers',
exec : gui.providers.link
exec : gui.providers.link,
cleanup: true,
}, {
id : 'lnk-authenticators',
exec : gui.authenticators.link
exec : gui.authenticators.link,
cleanup: true,
}, {
id : 'lnk-osmanagers',
exec : gui.osmanagers.link
exec : gui.osmanagers.link,
cleanup: true,
}, {
id : 'lnk-connectivity',
exec : gui.connectivity.link
exec : gui.connectivity.link,
cleanup: true,
}, {
id : 'lnk-deployed_services',
exec : gui.deployed_services
exec : gui.deployed_services,
cleanup: true,
}, {
id : 'lnk-clear_cache',
exec : gui.clear_cache.link,
cleanup: false,
},
];
$.each(sidebarLinks, function(index, value) {
@ -182,10 +207,10 @@
if ($('.navbar-toggle').css('display') != 'none') {
$(".navbar-toggle").trigger("click");
}
if( value.cleanup ) {
gui.cleanup();
}
$('html, body').scrollTop(0);
// Tabletools creates divs at end that do not get removed, here is a good place to ensure there is no garbage left behind
// And anyway, if this div does not exists, it creates a new one...
$('.DTTT_dropdown').remove();
value.exec(event);
});
});
@ -223,45 +248,56 @@
gui.methods = {};
// "Generic" edit method to set onEdit table
gui.methods.typedEdit = function(parent, modalTitle, modalErrorMsg, guiProcessor, fieldsProcessor) {
gui.methods.typedEdit = function(parent, modalTitle, modalErrorMsg, options) {
options = options || {}
var self = parent;
return function(value, event, table, refreshFnc) {
self.rest.gui(value.type, function(guiDefinition) {
var tabs = guiProcessor ? guiProcessor(guiDefinition) : guiDefinition; // Preprocess fields (probably generate tabs...)
var tabs = options.guiProcessor ? options.guiProcessor(guiDefinition) : guiDefinition; // Preprocess fields (probably generate tabs...)
self.rest.item(value.id, function(item) {
gui.forms.launchModal(modalTitle+' <b>'+value.name+'</b>', tabs, item, function(form_selector, closeFnc) {
var fields = gui.forms.read(form_selector);
fields.data_type = value.type;
fields = fieldsProcessor ? fieldsProcessor(fields) : fields;
self.rest.save(fields, function(data) { // Success on put
closeFnc();
refreshFnc();
gui.alert(gettext('Edition successfully done'), 'success');
}, gui.failRequestModalFnc(modalErrorMsg)); // Fail on put, show modal message
return false;
});
});
gui.forms.launchModal({
title: modalTitle+' <b>'+value.name+'</b>',
fields: tabs,
item: item,
buttons: options.buttons,
success: function(form_selector, closeFnc) {
var fields = gui.forms.read(form_selector);
fields.data_type = value.type;
fields = options.fieldsProcessor ? options.fieldsProcessor(fields) : fields;
self.rest.save(fields, function(data) { // Success on put
closeFnc();
refreshFnc();
gui.alert(gettext('Edition successfully done'), 'success');
}, gui.failRequestModalFnc(modalErrorMsg)); // Fail on put, show modal message
},
});
});
}, gui.failRequestModalFnc(modalErrorMsg));
};
};
// "Generic" new method to set onNew table
gui.methods.typedNew = function(parent, modalTitle, modalErrorMsg, guiProcessor, fieldsProcessor) {
gui.methods.typedNew = function(parent, modalTitle, modalErrorMsg, options) {
options = options || {};
var self = parent;
return function(type, table, refreshFnc) {
self.rest.gui(type, function(guiDefinition) {
var tabs = guiProcessor ? guiProcessor(guiDefinition) : guiDefinition; // Preprocess fields (probably generate tabs...)
gui.forms.launchModal(modalTitle, tabs, undefined, function(form_selector, closeFnc) {
var fields = gui.forms.read(form_selector);
fields.data_type = type;
fields = fieldsProcessor ? fieldsProcessor(fields) : fields; // P
self.rest.create(fields, function(data) { // Success on put
closeFnc();
refreshFnc();
gui.alert(gettext('Creation successfully done'), 'success');
}, gui.failRequestModalFnc(modalErrorMsg) // Fail on put, show modal message
);
var tabs = options.guiProcessor ? options.guiProcessor(guiDefinition) : guiDefinition; // Preprocess fields (probably generate tabs...)
gui.forms.launchModal({
title: modalTitle,
fields: tabs,
item: undefined,
buttons: options.buttons,
success: function(form_selector, closeFnc) {
var fields = gui.forms.read(form_selector);
fields.data_type = type;
fields = options.fieldsProcessor ? options.fieldsProcessor(fields) : fields; // Process fields before creating?
self.rest.create(fields, function(data) { // Success on put
closeFnc();
refreshFnc();
gui.alert(gettext('Creation successfully done'), 'success');
}, gui.failRequestModalFnc(modalErrorMsg)); // Fail on put, show modal message
},
});
});
};
@ -271,7 +307,7 @@
var self = parent;
return function(value, event, table, refreshFnc) {
var content = gettext('Are you sure do you want to delete ') + '<b>' + value.name + '</b>';
var modalId = gui.launchModal(modalTitle, content, '<button type="button" class="btn btn-danger button-accept">' + gettext('Delete') + '</button>');
var modalId = gui.launchModal(modalTitle, content, { actionButton: '<button type="button" class="btn btn-danger button-accept">' + gettext('Delete') + '</button>'});
$(modalId + ' .button-accept').click(function(){
$(modalId).modal('hide');
self.rest.del(value.id, function(){

View File

@ -11,6 +11,7 @@
{{{ content }}}
</div>
<div class="modal-footer">
{{{ footer }}}
{{# if button1 }}
{{{ button1 }}}
{{ else }}