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

PaginatedDataTable should support not having a header #38604

Closed
dnfield opened this issue Aug 15, 2019 · 13 comments · Fixed by #69610
Closed

PaginatedDataTable should support not having a header #38604

dnfield opened this issue Aug 15, 2019 · 13 comments · Fixed by #69610
Assignees
Labels
c: new feature Nothing broken; request for a new capability f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. P2 Important issues not at the top of the work list

Comments

@dnfield
Copy link
Contributor

dnfield commented Aug 15, 2019

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

@dnfield dnfield added c: new feature Nothing broken; request for a new capability framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. labels Aug 15, 2019
@dnfield dnfield added this to the Goals milestone Aug 15, 2019
@wolfcro1984
Copy link

Sorry about not responding at the #26352. I've tried to merge various times but never succeed :(
Maybe I should give it a try again in the next week when I find the time. I have to update everything and retest everything.

@dnfield
Copy link
Contributor Author

dnfield commented Aug 15, 2019

Feel free to either open that again once it's merged up, or to open a fresh PR against master with your changes!

@kyczawon
Copy link

Any updates on this?

@wolfcro1984
Copy link

Sorry, I have a really busy period at work so I put aside all the side projects.
Look at Issue #21862 where I explained what I did to get the functionality. The code in the framework changed a little bit in the meantime but you should be able to get the idea of what you have to do. Of course you'll have to make your own implementation (copy) of PaginatedDataTable, but that is a great feature of Flutter (open source).

@jlubeck
Copy link
Contributor

jlubeck commented Mar 6, 2020

Any updates on this?

@fringefilmsoz
Copy link

I'd like to support his request please.

@kf6gpe kf6gpe added the P2 Important issues not at the top of the work list label May 29, 2020
@Sun3
Copy link

Sun3 commented Jul 19, 2020

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.

@Hixie Hixie removed this from the None. milestone Aug 17, 2020
@yasinallana
Copy link

yasinallana commented Aug 19, 2020

@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

  • includes a way to pass in a footer dropdown styling
  • includes a way to pass in a footer text styling (does not include dropdown item text)
  • includes a way to pass in a footer previous / next icon colour styling
  • the previous and next are hidden (via colour opacity on the icon) when reaching the end or the beginning of the data list
  • no header
  • no card view
// 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,
                ),
              ),
            ),
          ],
        );
      },
    );
  }
}

@jlubeck
Copy link
Contributor

jlubeck commented Aug 19, 2020

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

@yasinallana
Copy link

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

@Sun3
Copy link

Sun3 commented Aug 19, 2020

@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.

@svsno1
Copy link

svsno1 commented Sep 25, 2020

I also want to hide the header in PaginatedDataTable. Flutter team, Do you have any plan for this? Or you never do it?

@github-actions
Copy link

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 flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 10, 2021
@guidezpl guidezpl moved this from Done to Done in Previous Sprints in Material Flutter - Sprint 36 Feb 2, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
c: new feature Nothing broken; request for a new capability f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. P2 Important issues not at the top of the work list
Projects
No open projects
Material Flutter - Sprint 36
  
Done in Previous Sprints
Development

Successfully merging a pull request may close this issue.