-
Notifications
You must be signed in to change notification settings - Fork 26.7k
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
PaginatedDataTable should support not having a header #38604
Comments
Sorry about not responding at the #26352. I've tried to merge various times but never succeed :( |
Feel free to either open that again once it's merged up, or to open a fresh PR against master with your changes! |
Any updates on this? |
Sorry, I have a really busy period at work so I put aside all the side projects. |
Any updates on this? |
I'd like to support his request please. |
Any updates on this feature request, it's really important to have the header optional. For some apps it means to have a huge blank header space and it's not aesthetically pleasing. |
@Sun3 @fringefilmsoz @jlubeck I guess if you're as impatient as me, you could simply copy and paste the raw paginated data table code and create a customised version of it to use throughout out your application. In my case i just wanted the raw data table structure without any card view and with out any headers so i created a custom version based on the original code by the flutter team. What's different from the original code
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'dart:math' as math;
import 'package:flutter/material.dart';
/// For Dropdown styling
class DropdownStyle {
/// Colors
final Color textColor;
final Color triggerIconColor;
final Color dropdownCanvasColor;
/// FontSizes / Icon Sizes
final double textSize;
final double triggerIconSize;
DropdownStyle({
this.textColor,
this.triggerIconColor,
this.dropdownCanvasColor,
this.textSize,
this.triggerIconSize,
});
}
/// For Previous / Next styling
class PreviousNextStyle {
final Color iconColor;
final double iconSize;
PreviousNextStyle({
this.iconColor,
this.iconSize,
});
}
/// Paginated Table
class CardlessHeadlessPaginatedDataTable extends StatefulWidget {
final List<Widget> actions;
final List<DataColumn> columns;
final int sortColumnIndex;
final bool sortAscending;
final ValueSetter<bool> onSelectAll;
final double dataRowHeight;
final double headingRowHeight;
final double horizontalMargin;
final double columnSpacing;
final bool showCheckboxColumn;
final int initialFirstRowIndex;
final ValueChanged<int> onPageChanged;
final int rowsPerPage;
static const int defaultRowsPerPage = 10;
final List<int> availableRowsPerPage;
final ValueChanged<int> onRowsPerPageChanged;
final DataTableSource source;
final TextStyle footerStyle;
final DropdownStyle dropdownStyle;
final PreviousNextStyle previousNextStyle;
CardlessHeadlessPaginatedDataTable({
Key key,
this.actions,
@required this.columns,
this.sortColumnIndex,
this.sortAscending = true,
this.onSelectAll,
this.dataRowHeight = kMinInteractiveDimension,
this.headingRowHeight = 56.0,
this.horizontalMargin = 24.0,
this.columnSpacing = 56.0,
this.showCheckboxColumn = true,
this.initialFirstRowIndex = 0,
this.onPageChanged,
this.rowsPerPage = defaultRowsPerPage,
this.availableRowsPerPage = const <int>[defaultRowsPerPage, defaultRowsPerPage * 2, defaultRowsPerPage * 5, defaultRowsPerPage * 10],
this.onRowsPerPageChanged,
this.footerStyle,
this.dropdownStyle,
this.previousNextStyle,
@required this.source,
}) : assert(columns != null),
assert(columns.isNotEmpty),
assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)),
assert(sortAscending != null),
assert(dataRowHeight != null),
assert(headingRowHeight != null),
assert(horizontalMargin != null),
assert(columnSpacing != null),
assert(showCheckboxColumn != null),
assert(rowsPerPage != null),
assert(rowsPerPage > 0),
assert(() {
if (onRowsPerPageChanged != null) assert(availableRowsPerPage != null && availableRowsPerPage.contains(rowsPerPage));
return true;
}()),
assert(source != null),
super(key: key);
@override
CardlessHeadlessPaginatedDataTableState createState() => CardlessHeadlessPaginatedDataTableState();
}
class CardlessHeadlessPaginatedDataTableState extends State<CardlessHeadlessPaginatedDataTable> {
int _firstRowIndex;
int _rowCount;
bool _rowCountApproximate;
final Map<int, DataRow> _rows = <int, DataRow>{};
@override
void initState() {
super.initState();
_firstRowIndex = PageStorage.of(context)?.readState(context) as int ?? widget.initialFirstRowIndex ?? 0;
widget.source.addListener(_handleDataSourceChanged);
_handleDataSourceChanged();
}
@override
void didUpdateWidget(CardlessHeadlessPaginatedDataTable oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.source != widget.source) {
oldWidget.source.removeListener(_handleDataSourceChanged);
widget.source.addListener(_handleDataSourceChanged);
_handleDataSourceChanged();
}
}
@override
void dispose() {
widget.source.removeListener(_handleDataSourceChanged);
super.dispose();
}
void _handleDataSourceChanged() {
setState(() {
_rowCount = widget.source.rowCount;
_rowCountApproximate = widget.source.isRowCountApproximate;
_rows.clear();
});
}
/// Ensures that the given row is visible.
void pageTo(int rowIndex) {
final int oldFirstRowIndex = _firstRowIndex;
setState(() {
final int rowsPerPage = widget.rowsPerPage;
_firstRowIndex = (rowIndex ~/ rowsPerPage) * rowsPerPage;
});
if ((widget.onPageChanged != null) && (oldFirstRowIndex != _firstRowIndex)) widget.onPageChanged(_firstRowIndex);
}
DataRow _getBlankRowFor(int index) {
return DataRow.byIndex(
index: index,
cells: widget.columns.map<DataCell>((DataColumn column) => DataCell.empty).toList(),
);
}
DataRow _getProgressIndicatorRowFor(int index) {
bool haveProgressIndicator = false;
final List<DataCell> cells = widget.columns.map<DataCell>((DataColumn column) {
if (!column.numeric) {
haveProgressIndicator = true;
return const DataCell(CircularProgressIndicator());
}
return DataCell.empty;
}).toList();
if (!haveProgressIndicator) {
haveProgressIndicator = true;
cells[0] = const DataCell(CircularProgressIndicator());
}
return DataRow.byIndex(
index: index,
cells: cells,
);
}
List<DataRow> _getRows(int firstRowIndex, int rowsPerPage) {
final List<DataRow> result = <DataRow>[];
final int nextPageFirstRowIndex = firstRowIndex + rowsPerPage;
bool haveProgressIndicator = false;
for (int index = firstRowIndex; index < nextPageFirstRowIndex; index += 1) {
DataRow row;
if (index < _rowCount || _rowCountApproximate) {
row = _rows.putIfAbsent(index, () => widget.source.getRow(index));
if (row == null && !haveProgressIndicator) {
row ??= _getProgressIndicatorRowFor(index);
haveProgressIndicator = true;
}
}
row ??= _getBlankRowFor(index);
result.add(row);
}
return result;
}
void _handlePrevious() {
pageTo(math.max(_firstRowIndex - widget.rowsPerPage, 0));
}
void _handleNext() {
pageTo(_firstRowIndex + widget.rowsPerPage);
}
final GlobalKey _tableKey = GlobalKey();
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
// FOOTER
final TextStyle footerTextStyle = widget.footerStyle ?? TextStyle(color: Colors.grey, fontSize: 12);
final DropdownStyle footerDropdownStyle = widget.dropdownStyle ??
DropdownStyle(
textColor: Colors.grey[900],
triggerIconColor: Colors.grey,
dropdownCanvasColor: Colors.white,
textSize: 14.0,
triggerIconSize: 24.0,
);
final PreviousNextStyle previousNextStyle = widget.previousNextStyle ??
PreviousNextStyle(
iconColor: Colors.grey,
iconSize: 24.0,
);
final List<Widget> footerWidgets = <Widget>[];
if (widget.onRowsPerPageChanged != null) {
final List<Widget> availableRowsPerPage = widget.availableRowsPerPage.where((int value) => value <= _rowCount || value == widget.rowsPerPage).map<DropdownMenuItem<int>>((int value) {
return DropdownMenuItem<int>(
value: value,
child: Text('$value'),
);
}).toList();
footerWidgets.addAll(<Widget>[
DefaultTextStyle(
style: footerTextStyle,
child: Text("Rows :"),
),
SizedBox(
width: 10,
),
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 64.0),
child: Align(
alignment: AlignmentDirectional.centerStart,
child: DropdownButtonHideUnderline(
child: DropdownButton<int>(
dropdownColor: footerDropdownStyle.dropdownCanvasColor,
items: availableRowsPerPage.cast<DropdownMenuItem<int>>(),
value: widget.rowsPerPage,
onChanged: widget.onRowsPerPageChanged,
style: TextStyle(
color: footerDropdownStyle.textColor,
fontSize: footerDropdownStyle.textSize,
),
iconSize: footerDropdownStyle.triggerIconSize,
icon: Icon(
Icons.keyboard_arrow_down,
color: footerDropdownStyle.triggerIconColor,
),
),
),
),
),
]);
}
footerWidgets.addAll(<Widget>[
SizedBox(
width: 10.0,
),
DefaultTextStyle(
style: footerTextStyle,
child: Text(
localizations.pageRowsInfoTitle(
_firstRowIndex + 1,
_firstRowIndex + widget.rowsPerPage,
_rowCount,
_rowCountApproximate,
),
),
),
SizedBox(width: 10.0),
IconButton(
icon: Icon(
Icons.chevron_left,
color: (_firstRowIndex <= 0) ? previousNextStyle.iconColor.withOpacity(0) : previousNextStyle.iconColor,
size: previousNextStyle.iconSize,
),
padding: EdgeInsets.zero,
tooltip: localizations.previousPageTooltip,
onPressed: _firstRowIndex <= 0 ? null : _handlePrevious,
),
SizedBox(width: 10.0),
IconButton(
icon: Icon(
Icons.chevron_right,
color: (!_rowCountApproximate && (_firstRowIndex + widget.rowsPerPage >= _rowCount)) ? previousNextStyle.iconColor.withOpacity(0) : previousNextStyle.iconColor,
size: previousNextStyle.iconSize,
),
padding: EdgeInsets.zero,
tooltip: localizations.nextPageTooltip,
onPressed: (!_rowCountApproximate && (_firstRowIndex + widget.rowsPerPage >= _rowCount)) ? null : _handleNext,
),
Container(width: 10.0),
]);
// TABLE
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: constraints.minWidth),
child: DataTable(
key: _tableKey,
columns: widget.columns,
sortColumnIndex: widget.sortColumnIndex,
sortAscending: widget.sortAscending,
onSelectAll: widget.onSelectAll,
dataRowHeight: widget.dataRowHeight,
headingRowHeight: widget.headingRowHeight,
horizontalMargin: widget.horizontalMargin,
columnSpacing: widget.columnSpacing,
showCheckboxColumn: widget.showCheckboxColumn,
rows: _getRows(_firstRowIndex, widget.rowsPerPage),
),
),
),
Container(
height: 56.0,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
reverse: true,
child: Row(
children: footerWidgets,
),
),
),
],
);
},
);
}
} |
Yup, that is what I ended up doing, but it's scary moving forward with a file that is not in sync with the main source code |
@jlubeck Exactly, though this is the way we need to do it for the time being till more customisation becomes available from the flutter team |
@yasinallana Thank you for sharing and I am also concerned using it when not in sync with the main source code. But if needed that is a great alternative. |
I also want to hide the header in PaginatedDataTable. Flutter team, Do you have any plan for this? Or you never do it? |
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of |
Or, it should support some kind of display mode in which the header can take up less space or be collapsed.
See #26352 for an attempt at implementing this. That PR appears to have been abandoned by its original author and so was closed.
/cc @willlarche @HansMuller @wolfcro1984 @arthurdenner
The text was updated successfully, but these errors were encountered: