name: fastadmin-table-optimization
Provides comprehensive guidance for optimizing FastAdmin Bootstrap Table layouts with responsive design, dynamic column freezing, and proper dimension calculations. Addresses common issues like column misalignment, fixed column errors, and poor mobile/tablet responsiveness.
Use this skill when:
Always use Bootstrap's standard responsive breakpoints for consistency:
const BREAKPOINT_SM = 576; // Mobile portrait: < 576px
const BREAKPOINT_MD = 768; // Mobile landscape/small tablet: 576px ~ 767px
const BREAKPOINT_LG = 992; // Tablet: 768px ~ 991px
const BREAKPOINT_XL = 1200; // Desktop: 992px ~ 1199px
const BREAKPOINT_XXL = 1400; // Large desktop: ≥ 1200px
Why not 769px?
Fixed columns must be responsive to avoid content挤压 on smaller screens:
function getFixedColumnsConfig(bodyW) {
if (bodyW < BREAKPOINT_SM) {
return {
fixedColumns: false,
fixedNumber: 0,
fixedRightNumber: 0,
showColumns: false,
showExport: false,
fixedHeight: false
};
} else if (bodyW < BREAKPOINT_MD) {
return {
fixedColumns: true,
fixedNumber: 2, // checkbox + name
fixedRightNumber: 0,
showColumns: false,
showExport: false,
fixedHeight: false
};
} else if (bodyW < BREAKPOINT_LG) {
return {
fixedColumns: true,
fixedNumber: 3, // checkbox + id + name + phone
fixedRightNumber: 0,
showColumns: true,
showExport: true,
fixedHeight: true
};
} else if (bodyW < BREAKPOINT_XL) {
return {
fixedColumns: true,
fixedNumber: 4, // checkbox + id + name + phone
fixedRightNumber: 1, // action column
showColumns: true,
showExport: true,
fixedHeight: true
};
} else {
return {
fixedColumns: true,
fixedNumber: 5, // checkbox + id + name + phone + department
fixedRightNumber: 1, // action column
showColumns: true,
showExport: true,
fixedHeight: true
};
}
}
Use outerHeight(true) to capture full element dimensions including margins.
Critical: Do NOT include search form height in the calculation. The search form is expandable and should not affect table height - when expanded, the table should naturally scroll.
function getTableHeight() {
var bodyH = $(window).height();
var h_ribbon = $('#ribbon').outerHeight(true) || 0;
var h_panelHeading = $('.panel-heading').outerHeight(true) || 0;
var h_toolbar = $('.fixed-table-toolbar').outerHeight(true) || 0;
// Search form height NOT included - it's expandable and should not affect table height
var h_searchForm = 0;
var h_pagination = $('.fixed-table-pagination').outerHeight(true) || 40;
// Bottom margin and padding reserve
var margin = 0;
var calculatedHeight = bodyH - h_ribbon - h_panelHeading - h_toolbar - h_searchForm - h_pagination - margin;
// Set minimum height to ensure at least 10 rows of data (approximately 400px)
var minHeight = 400;
return Math.max(calculatedHeight, minHeight);
}
Why exclude search form height?
Expected behavior:
Account for panel padding and container widths:
function getTableWidth() {
var bodyW = $(window).width();
var panelPadding = 30; // Panel left/right padding
return bodyW - panelPadding;
}
Combine resize, load-success, and search toggle events into a single reset function with 200ms debounce:
var resizeTimer;
function resetTableView() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
var bodyW = $(window).width();
var tableWidth = getTableWidth();
var tableHeight = getTableHeight();
var fixedConfig = getFixedColumnsConfig(bodyW);
// Update table width
table.bootstrapTable('resetView', {
width: tableWidth
});
// Responsive height control
if (fixedConfig.fixedHeight) {
table.bootstrapTable('resetView', {
height: tableHeight
});
} else {
table.bootstrapTable('resetView', {
height: undefined
});
}
// Reinitialize if fixed columns changed
var currentOptions = table.bootstrapTable('getOptions');
if (currentOptions.fixedNumber !== fixedConfig.fixedNumber ||
currentOptions.fixedRightNumber !== fixedConfig.fixedRightNumber) {
var currentData = table.bootstrapTable('getData');
optionsInit.fixedColumns = fixedConfig.fixedColumns;
optionsInit.fixedNumber = fixedConfig.fixedNumber;
optionsInit.fixedRightNumber = fixedConfig.fixedRightNumber;
optionsInit.height = fixedConfig.fixedHeight ? tableHeight : undefined;
optionsInit.showColumns = fixedConfig.showColumns;
optionsInit.showExport = fixedConfig.showExport;
table.bootstrapTable('destroy');
table.bootstrapTable(optionsInit);
table.bootstrapTable('load', currentData);
}
}, 200); // 200ms debounce
}
Bind all relevant events to the unified reset function:
// Initialize table
table.bootstrapTable(optionsInit);
// Window resize (debounced)
$(window).on('resize', resetTableView);
// Data load complete
table.on('load-success.bs.table', function (e, data) {
resetTableView();
});
// Search form toggle
$(document).on('click', '.btn-search-toggle', function() {
setTimeout(resetTableView, 300);
});
index: function () {
Table.api.init({
extend: {
index_url: 'controller/index' + location.search,
add_url: 'controller/add',
edit_url: 'controller/edit',
del_url: 'controller/del',
table: 'table_name',
}
});
var table = $("#table");
// Bootstrap standard breakpoints
const BREAKPOINT_SM = 576;
const BREAKPOINT_MD = 768;
const BREAKPOINT_LG = 992;
const BREAKPOINT_XL = 1200;
// Dynamic fixed column configuration
function getFixedColumnsConfig(bodyW) {
if (bodyW < BREAKPOINT_SM) {
return {
fixedColumns: false,
fixedNumber: 0,
fixedRightNumber: 0,
showColumns: false,
showExport: false,
fixedHeight: false
};
} else if (bodyW < BREAKPOINT_MD) {
return {
fixedColumns: true,
fixedNumber: 2,
fixedRightNumber: 0,
showColumns: false,
showExport: false,
fixedHeight: false
};
} else if (bodyW < BREAKPOINT_LG) {
return {
fixedColumns: true,
fixedNumber: 3,
fixedRightNumber: 0,
showColumns: true,
showExport: true,
fixedHeight: true
};
} else if (bodyW < BREAKPOINT_XL) {
return {
fixedColumns: true,
fixedNumber: 4,
fixedRightNumber: 1,
showColumns: true,
showExport: true,
fixedHeight: true
};
} else {
return {
fixedColumns: true,
fixedNumber: 5,
fixedRightNumber: 1,
showColumns: true,
showExport: true,
fixedHeight: true
};
}
}
// Precise height calculation
function getTableHeight() {
var bodyH = $(window).height();
var h_ribbon = $('#ribbon').outerHeight(true) || 0;
var h_panelHeading = $('.panel-heading').outerHeight(true) || 0;
var h_toolbar = $('.fixed-table-toolbar').outerHeight(true) || 0;
// Search form height NOT included - it's expandable and should not affect table height
var h_searchForm = 0;
var h_pagination = $('.fixed-table-pagination').outerHeight(true) || 40;
var margin = 0;
var calculatedHeight = bodyH - h_ribbon - h_panelHeading - h_toolbar - h_searchForm - h_pagination - margin;
// Set minimum height to ensure at least 10 rows of data (approximately 400px)
var minHeight = 400;
return Math.max(calculatedHeight, minHeight);
}
// Accurate width calculation
function getTableWidth() {
var bodyW = $(window).width();
var panelPadding = 30;
return bodyW - panelPadding;
}
var bodyW = $(window).width();
var fixedConfig = getFixedColumnsConfig(bodyW);
var optionsInit = {
url: $.fn.bootstrapTable.defaults.extend.index_url,
pk: 'id',
sortName: 'id',
showColumns: fixedConfig.showColumns,
showExport: fixedConfig.showExport,
fixedColumns: fixedConfig.fixedColumns,
fixedNumber: fixedConfig.fixedNumber,
fixedRightNumber: fixedConfig.fixedRightNumber,
searchFormTemplate: 'consultformtpl',
height: fixedConfig.fixedHeight ? getTableHeight() : undefined,
width: getTableWidth(),
columns: [/* ... */],
queryParams: function (params) {
return params;
}
};
table.bootstrapTable(optionsInit);
// Unified reset with debounce
var resizeTimer;
function resetTableView() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
var bodyW = $(window).width();
var tableWidth = getTableWidth();
var tableHeight = getTableHeight();
var fixedConfig = getFixedColumnsConfig(bodyW);
table.bootstrapTable('resetView', {
width: tableWidth
});
if (fixedConfig.fixedHeight) {
table.bootstrapTable('resetView', {
height: tableHeight
});
} else {
table.bootstrapTable('resetView', {
height: undefined
});
}
var currentOptions = table.bootstrapTable('getOptions');
if (currentOptions.fixedNumber !== fixedConfig.fixedNumber ||
currentOptions.fixedRightNumber !== fixedConfig.fixedRightNumber) {
var currentData = table.bootstrapTable('getData');
optionsInit.fixedColumns = fixedConfig.fixedColumns;
optionsInit.fixedNumber = fixedConfig.fixedNumber;
optionsInit.fixedRightNumber = fixedConfig.fixedRightNumber;
optionsInit.height = fixedConfig.fixedHeight ? tableHeight : undefined;
optionsInit.showColumns = fixedConfig.showColumns;
optionsInit.showExport = fixedConfig.showExport;
table.bootstrapTable('destroy');
table.bootstrapTable(optionsInit);
table.bootstrapTable('load', currentData);
}
}, 200);
}
$(window).on('resize', resetTableView);
table.on('load-success.bs.table', function (e, data) {
resetTableView();
});
$(document).on('click', '.btn-search-toggle', function() {
setTimeout(resetTableView, 300);
});
Table.api.bindevent(table);
}
Cause: Fixed columns don't recalculate when DOM changes (search form expansion)
Solution: Bind load-success.bs.table event to call resetTableView()
Cause: Fixed columns count too high for small screens
Solution: Use getFixedColumnsConfig() to dynamically reduce fixed columns on smaller screens
Cause: Hardcoded height values or missing margin calculations
Solution: Use getTableHeight() with outerHeight(true) to account for all visible elements
Cause: Search form height is included in calculation, reducing available space
Solution: Set h_searchForm = 0 to exclude search form from height calculation, use minimum height (400px) to ensure data visibility
Cause: Incorrect width calculation or missing panel padding
Solution: Use getTableWidth() accounting for panel padding (typically 30px)
Cause: No debounce on resize event
Solution: Use 200ms debounce in resetTableView() to batch resize events
Cause: No minimum height constraint, search form expansion reduces available space
Solution: Set minHeight = 400 in getTableHeight() to guarantee at least 10 rows of data are visible
After implementing table optimizations:
application/extra/site.php