Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for Bootstrap 5 #2432

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
110 changes: 110 additions & 0 deletions flask_admin/static/admin/css/bootstrap5/admin.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* List View - fix trash icon inside table column */
.model-list form.icon {
display: inline;
}

.model-list form.icon button {
border: none;
background: transparent;
text-decoration: none;
padding: 0;
line-height: normal;
}

.model-list a.icon:first-child {
margin-left: 10px;
}

/* List View - prevent link icons from differing from trash icon */
.model-list a.icon {
text-decoration: none;
color: inherit;
}

/* List View - fix checkbox column width */
.list-checkbox-column {
width: 14px;
}

/* List View - fix overlapping border between actions and table */
.model-list {
position: static;
margin-top: -1px;
z-index: 999;
}

/* List View Search Form - fix gap between form and table */
.actions-nav form.navbar-form {
margin: 1px 0 0 0;
}

/* List View - prevent word wrap on buttons column, to keep it on one line */
.list-buttons-column {
white-space: nowrap;
}

/* Filters */
table.filters {
border-collapse: collapse;
border-spacing: 4px;
}

/* prevents gap between table and actions while there are no filters set */
table.filters:not(:empty) {
margin: 12px 0px 20px 0px;
}

/* spacing between filter X button, operation, and value field */
/* uses tables instead of form classes for bootstrap2-3 compatibility */
table.filters tr td {
padding-right: 5px;
padding-bottom: 3px;
}

/* Filters - Select2 Boxes */
.filters .filter-op {
width: 130px;
}

.filters .filter-val {
width: 220px;
}

/* Image thumbnails */
.image-thumbnail img {
max-width: 100px;
max-height: 100px;
}

/* Forms */
/* adds spacing between navbar and edit/create form (non-modal only) */
/* required because form-horizontal removes top padding */
div.container > .admin-form {
margin-top: 35px;
}

/* Form Field Description - Appears when field has 'description' attribute */
/* Test with: form_args = {'name':{'description': 'test'}} */
/* prevents awkward gap after help-block - This is default for bootstrap2 */
.admin-form .help-block {
margin-bottom: 0px;
}

/* Modals */
/* hack to prevent cut-off left side of select2 inside of modal */
/* may be able to remove this after Bootstrap v3.3.5 */
body.modal-open {
overflow-y: scroll;
padding-right: 0 !important;
}

/* Details View - add space between navbar and results */
.fa_filter_container {
margin-top: 20px;
margin-bottom: 10px;
}

.table-responsive
{
overflow-x: auto;
}
48 changes: 48 additions & 0 deletions flask_admin/static/admin/css/bootstrap5/rediscli.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
.console {
position: relative;
width: 100%;
min-height: 400px;
}

.console-container {
border-radius: 4px;
position: absolute;
border: 1px solid #d4d4d4;
padding: 2px;
overflow: scroll;
top: 2px;
left: 2px;
right: 2px;
bottom: 5em;
}

.console-line {
position: absolute;
left: 2px;
right: 2px;
bottom: 2px;
}

.console-line input {
width: 100%;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
height: 2em;
}

.console .cmd {
background-color: #f5f5f5;
padding: 2px;
margin: 1px;
}

.console .response {
background-color: #f0f0f0;
padding: 2px;
margin: 1px;
}

.console .error {
color: red;
}
18 changes: 18 additions & 0 deletions flask_admin/static/admin/css/bootstrap5/submenu.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.nav li.dropdown ul.dropdown-menu li:hover ul {
display:block;
position:absolute;
left:100%;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.nav li.dropdown ul.dropdown-menu ul {
display: none;
float:right;
position: relative;
top: auto;
margin-top: -30px;
}
.nav li.dropdown a.dropdown-toggle .glyphicon {
margin: 0 4px;
}
198 changes: 198 additions & 0 deletions flask_admin/static/admin/js/bs5_filters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
var AdminFilters = function(element, filtersElement, filterGroups, activeFilters) {
var $root = $(element);
var $container = $('.filters', $root);
var lastCount = 0;

function getCount(name) {
var idx = name.indexOf('_');

if (idx === -1) {
return 0;
}

return parseInt(name.substr(3, idx - 3), 10);
}

function makeName(name) {
var result = 'flt' + lastCount + '_' + name;
lastCount += 1;
return result;
}

function removeFilter() {
$(this).closest('tr').remove();
if($('.filters tr').length == 0) {
$('button', $root).hide();
$('a[class=btn]', $root).hide();
$('.filters tbody').remove();
} else {
$('button', $root).show();
}

return false;
}

// triggered when the filter operation (equals, not equals, etc) is changed
function changeOperation(subfilters, $el, filter, $select) {
// get the filter_group subfilter based on the index of the selected option
var selectedFilter = subfilters[$select.select2('data').element[0].index];
var $inputContainer = $el.find('td').last();

// recreate and style the input field (turn into date range or select2 if necessary)
var $field = createFilterInput($inputContainer, null, selectedFilter);
styleFilterInput(selectedFilter, $field);

$('button', $root).show();
}

// generate HTML for filter input - allows changing filter input type to one with options or tags
function createFilterInput(inputContainer, filterValue, filter) {
if (filter.type == "select2-tags") {
var $field = $('<input type="hidden" class="filter-val form-control" />').attr('name', makeName(filter.arg));
$field.val(filterValue);
} else if (filter.options) {
var $field = $('<select class="filter-val" />').attr('name', makeName(filter.arg));

$(filter.options).each(function() {
// for active filter inputs with options, add "selected" if there is a matching active filter
if (filterValue && (filterValue == this[0])) {
$field.append($('<option/>')
.val(this[0]).text(this[1]).attr('selected', true));
} else {
$field.append($('<option/>')
.val(this[0]).text(this[1]));
}
});
} else {
var $field = $('<input type="text" class="filter-val form-control" />').attr('name', makeName(filter.arg));
$field.val(filterValue);
}
inputContainer.replaceWith($('<td/>').append($field));

return $field;
}

// add styling to input field, accommodates filters that change the input field's HTML
function styleFilterInput(filter, field) {
if (filter.type) {
if ((filter.type == "datepicker") || (filter.type == "daterangepicker")) {
field.attr('data-date-format', "YYYY-MM-DD");
} else if ((filter.type == "datetimepicker") || (filter.type == "datetimerangepicker")) {
field.attr('data-date-format', "YYYY-MM-DD HH:mm:ss");
} else if ((filter.type == "timepicker") || (filter.type == "timerangepicker")) {
field.attr('data-date-format', "HH:mm:ss");
} else if (filter.type == "select2-tags") {
var options = [];
if (filter.options) {
filter.options.forEach(function(option) {
options.push({id:option[0], text:option[1]});
});
// save tag options as json on data attribute
field.attr('data-tags', JSON.stringify(options));
}
}
faForm.applyStyle(field, filter.type);
} else if (filter.options) {
filter.type = "select2";
faForm.applyStyle(field, filter.type);
}

return field;
}

function addFilter(name, subfilters, selectedIndex, filterValue) {
var $el = $('<tr class="form-horizontal" />').appendTo($container);

// Filter list
$el.append(
$('<td/>').append(
$('<a href="#" class="btn btn-secondary remove-filter" />')
.append($('<span class="close-icon">&times;</span>'))
.append('&nbsp;')
.append(name)
.click(removeFilter)
)
);

// Filter operation <select> (equal, not equal, etc)
var $select = $('<select class="filter-op" />');

// if one of the subfilters are selected, use that subfilter to create the input field
var filterSelection = 0;
$.each(subfilters, function( subfilterIndex, subfilter ) {
if (this.index == selectedIndex) {
$select.append($('<option/>').attr('value', subfilter.arg).attr('selected', true).text(subfilter.operation));
filterSelection = subfilterIndex;
} else {
$select.append($('<option/>').attr('value', subfilter.arg).text(subfilter.operation));
}
});

$el.append(
$('<td/>').append($select)
);

// select2 for filter-op (equal, not equal, etc)
$select.select2({width: 'resolve'}).on("change", function(e) {
changeOperation(subfilters, $el, filter, $select);
});

// get filter option from filter_group, only for new filter creation
var filter = subfilters[filterSelection];
var $inputContainer = $('<td/>').appendTo($el);

var $newFilterField = createFilterInput($inputContainer, filterValue, filter).focus();
var $styledFilterField = styleFilterInput(filter, $newFilterField);

return $styledFilterField;
}

// Add Filter Button, new filter
$('a.filter', filtersElement).click(function() {
var name = ($(this).text().trim !== undefined ? $(this).text().trim() : $(this).text().replace(/^\s+|\s+$/g,''));

addFilter(name, filterGroups[name], false, null);

$('button', $root).show();
});

// on page load - add active filters
$.each(activeFilters, function( activeIndex, activeFilter ) {
var idx = activeFilter[0],
name = activeFilter[1],
filterValue = activeFilter[2];
var $activeField = addFilter(name, filterGroups[name], idx, filterValue);
});

// show "Apply Filter" button when filter input is changed
$('.filter-val', $root).on('input change', function() {
$('button', $root).show();
});

$('.remove-filter', $root).click(removeFilter);

$('.filter-val', $root).not('.select2-container').each(function() {
var count = getCount($(this).attr('name'));
if (count > lastCount)
lastCount = count;
});

lastCount += 1;
};

(function($) {
/*$('[data-role=tooltip]').tooltip({
html: true,
placement: 'bottom'
});*/
$(document).on('adminFormReady', function(evt){
if ($('#filter-groups-data').length == 1) {
var filter = new AdminFilters(
'#filter_form', '.field-filters',
JSON.parse($('#filter-groups-data').text()),
JSON.parse($('#active-filters-data').text())
);
}
});
$(document).trigger('adminFormReady'); // trigger event to allow dynamic filter form to function properly
})(jQuery);
9 changes: 9 additions & 0 deletions flask_admin/static/admin/js/bs5_modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// fixes "content does not load remote modal on clicked modal button"
$('body').on('click.modal.data-api', '[data-toggle="modal"]', function () {
$($(this).data("target") + ' .modal-content').load($(this).attr('href'));
});

$(function() {
// Apply flask-admin form styles after the modal is loaded
window.faForm.applyGlobalStyles(document);
});
6 changes: 6 additions & 0 deletions flask_admin/static/bootstrap/bootstrap5/bootstrap.min.css

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions flask_admin/static/bootstrap/bootstrap5/bootstrap.min.js

Large diffs are not rendered by default.

Loading