Rows from server-side.

manki kimmanki kim
7 min read

This is a function to call rows from the server side added in PlutoGrid 5.3 version. There are two ways to load and process data on the server side.

The pagination method used in the DB structure where the total number of pages is known and the infinite scroll method used in the DB structure where the total number of pages is unknown.

PlutoLazyPagination
https://weblaze.dev/pluto_grid/build/web/#feature/row-lazy-pagination
PlutoInfinityScrollRows
https://weblaze.dev/pluto_grid/build/web/#feature/row-infinity-scroll

PlutoLazyPagination

This is a pagination widget.
Return a PlutoLazyPagination widget to the createFooter callback of PlutoGrid as shown below.
Pagination widget is created at the bottom of PlutoGrid.

final columns = [
  PlutoColumn(
    title: 'Column', 
    field: 'column', 
    type: PlutoColumnType.text()
  ),
];

// Pass an empty list to PlutoGrid.
// After PlutoGrid is loaded, page 1 is automatically called.
// initialPage defaults to 1, initialFetch defaults to true, if you change this
// You can change the page to be loaded first or set not to load the page first.
final rows = [];

PlutoGrid(
  columns: columns,
  rows: rows,
  createFooter: (stateManager) {
    return PlutoLazyPagination(
      // Determine the first page.
      // Default is 1.
      initialPage: 1,

      // First call the fetch function to determine whether to load the page.
      // Default is true.
      initialFetch: true,

      // Decide whether sorting will be handled by the server.
      // If false, handle sorting on the client side.
      // Default is true.
      fetchWithSorting: true,

      // Decide whether filtering is handled by the server.
      // If false, handle filtering on the client side.
      // Default is true.
      fetchWithFiltering: true,

      // Determines the page size to move to the previous and next page buttons.
      // Default value is null. In this case,
      // it moves as many as the number of page buttons visible on the screen.
      pageSizeToMove: null,

      // Implement a callback function to get data from the server. explained below.
      fetch: fetch, 
      stateManager: stateManager,
    );
  },
)

Implementation description of fetch function of PlutoLazyPagination

// Referring to the request data of the PlutoLazyPaginationRequest type,
// Implement a function that returns a PlutoLazyPaginationResponse response value.
Future<PlutoLazyPaginationResponse> fetch(
  PlutoLazyPaginationRequest request,
) async {
  String queryString = '?page=${request.page}';

  // If filtering is applied, there is at least one element in request.filterRows .
  if (request.filterRows.isNotEmpty) {
    // request.filterRows is a List<PlutoRow> type and contains filtering information.
    final filterMap = FilterHelper.convertRowsToMap(request.filterRows);
    // Assume that the column has two filters, 123 and a, as a Contains type filter.
    // {column: [{Contains: 123}, {Contains: a}]}
    for (final filter in filterMap.entries) {
      for (final type in filter.value) {
        queryString += '&filter[${filter.key}]';
        final filterType = type.entries.first;
        queryString += '[${filterType.key}][]=${filterType.value}';
      }
    }
  }

  // If a column in a sorted state exists.
  if (request.sortColumn != null && !request.sortColumn!.sort.isNone) {
    queryString += '&sort=${request.sortColumn!.field},${request.sortColumn!.sort.name}';
  }

  // Make API requests with queryString.
  print(queryString);
  // ?page=10&filter[column][Contains][]=123&filter[column][Contains][]=a&sort=column,ascending
  final dataFromServer = await Future.value("""
    {
      "totalPage": 10,
      "data": [
        {
          "column": "value 1"
        },
        {
          "column": "value 2"
        }
      ]
    }
    """);

  // Converts the json format received from the server to the Map format.
  final parsedData = jsonDecode(dataFromServer);

  // Create a PlutoRow.
  final rows = parsedData.data.map<PlutoRow>((rowData) {
    return PlutoRow.fromJson(rowData);
  });

  return PlutoLazyPaginationResponse(
    totalPage: parsedData['totalPage'],
    rows: rows.toList(),
  );
}

PlutoInfinityScrollRows

It is an infinite scrolling widget.
Return the PlutoInfinityScrollRows widget to the createFooter callback of PlutoGrid as shown below.
No widgets are created at the bottom of the PlutoGrid. (Created by SizedBox.shrink.)
No widgets need to be added, but if you need it for a special case, please ask.
I will update the function to add widgets.

final columns = [
  PlutoColumn(
    title: 'Id', 
    field: 'id', 
    type: PlutoColumnType.text()
  ),
  PlutoColumn(
    title: 'Name', 
    field: 'name', 
    type: PlutoColumnType.text()
  ),
];

// Pass an empty list to PlutoGrid.
// Call the fetch function automatically after PlutoGrid is loaded.
// initialFetch default value is true, if you change this
// You can set it not to load the page at first.
final rows = [];

PlutoGrid(
  columns: columns,
  rows: rows,
  createFooter: (stateManager) => PlutoInfinityScrollRows(
    // First call the fetch function to determine whether to load the page.
    // Default is true.
    initialFetch: true,

    // Decide whether sorting will be handled by the server.
    // If false, handle sorting on the client side.
    // Default is true.
    fetchWithSorting: true,

    // Decide whether filtering is handled by the server.
    // If false, handle filtering on the client side.
    // Default is true.
    fetchWithFiltering: true,

    // Implement a callback function to get data from the server. explained below.
    fetch: fetch,
    stateManager: stateManager,
  ),   
)

Implementation description of fetch function of PlutoInfinityScrollRows

Future<PlutoInfinityScrollRowsResponse> fetch(
 PlutoInfinityScrollRowsRequest request,
) async {
  String queryString = '?';

  // If lastRow is null, the first page can be loaded from the server.
  // If lastRow is present, add the user's unique value to the querystring.
  if (request.lastRow == null) {
    queryString += 'lastId=';
  } else {
    queryString += 'lastId=${request.lastRow!.cells['id']}';
  }

  // At least one element is present in request.filterRows if filtering is applied.
  if (request.filterRows.isNotEmpty) {
    // request.filterRows is a List<PlutoRow> type and contains filtering information.
    final filterMap = FilterHelper.convertRowsToMap(request.filterRows);
    // Assume that two filters, mike and jessi, are set as a Contains type filter in name.
    // {name: [{Contains: mike}, {Contains: jessi}]}
    for (final filter in filterMap.entries) {
      for (final type in filter.value) {
        queryString += '&filter[${filter.key}]';
        final filterType = type.entries.first;
        queryString += '[${filterType.key}][]=${filterType.value}';
      }
    }
  }

  // If a column in a sorted state exists.
  if (request.sortColumn != null && !request.sortColumn!.sort.isNone) {
    queryString += '&sort=${request.sortColumn!.field},${request.sortColumn!.sort.name}';
  }

  // Make API requests with queryString.
  print(queryString);
  // ?lastId=&filter[name][Contains][]=mike&filter[name][Contains][]=jessi&sort=name,ascending
  final dataFromServer = await Future.value("""
  {
    "isLast": false,
    "data": [
      {
        "id": 1,
        "name": "mike"
      },
      {
        "id": 2,
        "name": "jessi"
      }
    ]
  }
  """);

  // Convert the json format received from the server into Map format.
  final parsedData = jsonDecode(dataFromServer);

  // Include whether this is the last page when the server responds with data.
  final bool isLast = parsedData['isLast'];

  // Create a PlutoRow.
  final rows = parsedData.data.map<PlutoRow>((rowData) {
    return PlutoRow.fromJson(rowData);
  });

  // If this is the last page, you can notify the user with a message.
  if (isLast && mounted) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('Last Page!')),
    );
  }

  return PlutoInfinityScrollRowsResponse(
    isLast: isLast,
    rows: rows.toList(),
  );
}

Additional comments in common

  • When sorting and filtering events occur, the fetch callback requests the first page.
    PlutoLazyPagination is page = 1
    PlutoInfinityScrollRows is lastRow = null
    This is because a sorting or filtering event must show the user new data with sorting and filtering applied.
    In some cases, sorting or filtering can be handled client-side rather than being handled by the server.
    fetchWithSorting = false, fetchWithFiltering = false
    In this case, it is sorted or filtered based on the currently existing rows.
  • The existing stateManager.setPage operation is not valid.
    The way it is handled by the server does not have a separate page state internally in PlutoGrid.
    You do the paging inside the PlutoLazyPagination or PlutoInfinityScrollRows widget.
    Therefore, page changes or additions are handled by deleting and inserting all existing rows of PlutoGrid.
    A separate implementation is required if client-side caching handling is required. This is not yet to be considered, please comment on the GitHub issue and I will consider handling caching.
  • When data is fetched from the server, it is common that filtering and sorting are not applied to all columns.
    There are properties that can prevent filtering and sorting of certain columns.
    enableSorting, enableFilterMenuItem in PlutoColumn.
    You may also need a single filtering input rather than a filtering input for each column. In this case, a separate UI is not provided. If necessary, please suggest it to a GitHub issue and I will consider adding it.
  • The loading screen uses an internal loading widget.
    A custom loading UI is not yet available. If necessary, please comment on a github issue and I will consider adding it.
  • When creating a return value by fetching data from the server and creating a PlutoRow, you need to pay attention to the PlutoColumn.field value.
    The field passed to columns in PlutoGrid must match.
    PlutoRow.cells is of type Map<String, PlutoCell>, and the part corresponding to String should be PlutoColumn.field.

If you have more questions, please contact us through the GitHub issue. https://github.com/bosskmk/pluto_grid

1
Subscribe to my newsletter

Read articles from manki kim directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

manki kim
manki kim

I've mainly been doing backend web development. I also have a technology for web front-end, and recently I mainly develop Flutter packages.