Paginated Table - FwPaginationWrapper

When it comes to creating a paginated table, there is a lot more that goes into it than first meets the eye. Because this component is one of the only components we have which calls a network request from within the component, and it is the only one I can think of that makes a network request to a browse function that you yourself has to write (unlike Access Control, which defines both client and server). Because of this, there are elements of both client and server that you will need to do in order to make sure that pagination is functioning correctly.

Client Side

The bulk of the clientside aspect of the pagination is the component FwPaginationWrapper, which can be used either to wrap around a table you define or use a default FwTableSortable that it uses. The API docs for FwPaginationWrapper can be found here. We will only draw attention to most important aspects of this component to be installed at a base level, and you can checkout the API docs for greater detail.

Prerequisites

The page selector component comes from an addon, ember-cli-pagination, which must be installed the first time before you use the component. Install that using:

ember install ember-cli-pagination

In addition, to include the styling for it, add the following to your app.scss file after all related ember-fw imports:

@import "ember-fw/pagination/pagination-fw";

Base Parameters

When calling the FwPaginationWrapper, there are some parameters which are required to be used. There are two types of properties which you can pass to the parameter. There are just regular properties which require a regular data type. The full list of all the parameters of this kind are listed in the API docs here. A few of the more important ones that should be listed are these ones which are required to be passed directly:

  • modelName: Name of the model contained in this table, used for requests
  • defaultSortKey: Default order for sorting the table
  • tableWrapperClass: HTML class to apply to the div element that wraps the table. Typically used for Bootstrap Grid classes. If unset, no class is used.
  • entriesPerPage: Number of entries to show on each page. Defaults to 100.

The second type of parameter that you can pass in are action parameters, which are required to be passed an action (so @param={{action 'myAction'}}). Some of these action parameters are more important than others, but a full list of action parameter options can be found at our API docs here. The purpose of these properties are callbacks to allow fetching an update to date value without the need to refresh the whole wrapper. These are a few of the more important ones you may want to look at:

  • onSearch: Callback on searching to update history data, no return type. If unset, performs no action.
  • makeQuery: Callback that returns a query object to send to the server. Make sure to check API docs for parameters. This should usually be set, so you don't use an empty object for history query.
  • getTitle: Called on search to get the title for the table at the current time. Defaults to "Table" if this is not defined.
  • getExportColumns: Called on export to get the list of columns for exporting. Typically just returns the same list as used for table display. Required if you use actions.export.

Table Column Usage

Setting up columns for the paginated table is very similar to how you would setup columns for a regular FwTableSortable as described above. But there are a few extra properties that you can set for each column object that are used internally within the FwPaginationWrapper, which you should make sure to have defined for seemless searching and sorting:

  • searchKey: Key to send to the server side when sorting by this column. If unset, defaults to valuePath. Most commonly used when valuePath uses a relationship property, as searchKey can then just use the relationship name.
  • component: To show the loading spinner on the header during searching, component must be set to a header component that shows a spinner when column.loading is true. See Column Components for your options on this.

Block invoking

This template is typically invoked in block format, wrapping around the history search panel. When called in block form, a single hash parameter called actions is provided. This contains the following properties:

  • actions.search: Action to call to use search parameter and fetch entries.
  • actions.export: Action to call to fetch all entries and export them into a CSV file.

These actions should be used within the search panel to create search and export buttons.

Table

You have two options when it comes to rendering the table of results itself. You are able to use block format, and define the table directly as you wish. This should be used for more complicated tables. Or, if you need just a simple FwTableSortable table, then you can use parameter format and use the built in table given to you.

Parameter format

Calling the table in parameter format will use a default FwTableSortable. In this format, the header will display a single "Export Page" button, and you are unable to override the header any more than that. If you use this method, the following additional parameters are required:

  • getTableColumns: Action callback to get the table columns. Typically will just return your columns array.
  • emptyText: Text to display when the table is empty. If unset, hides the table when empty.
  • tableActions: Parameter to send into FwTableSortable's tableActions property

These were already included in the API docs links given above if you want more information on them.

Block format

Block format allows passing in a custom table component instead of using the default FwTableSortable. In this format, the table is included in the block with the search panel. To distinguish the two, a second parameter, table, is included. It will be an object for the table, and null for the search panel. The code below shows an example of using the block format with both parameters:

<FwPaginationWrapper
    ...
    as |actions table|
>
  {{#unless table}}
     {{!-- Search panel contents --}}
  {{else}}
    {{!-- Table component invocation --}}
  {{/unless}}
</FwPaginationWrapper>

In block format when table is defined, actions contains the following actions:

  • actions.sort: Action to use in FwTableSortable's onSort property, called when a column is clicked.

table contains the following parameters:

  • table.title: Full title for the table, pass to tables as title.
  • table.suffix: Suffix for the table title. Used in some custom table components.
  • table.entries: List of entries to display in the table. Passed to a table as the first unnamed parameter, rows.
  • table.sortKey: Currently active sort key for the table. Passed to a table as defaultSort.

Server Side

The client side performs all history behavior using the browse method of the given model, based on the modelName parameter. The browse route must support several query parameters to handle all cases.

Count

The count parameter is a boolean that when set to true returns a count of results instead of the results. This is needed to calculate the number of pages available and to show the total in the table title.

Count can be performed simply using $this->adapter->count in place of findAll. It takes two parameters, $modelName and $query, same as the first two parameters to findAll. For usage in the table, the result must be placed in a key, "count", such as with the following code:

$count = $this->adapter->count($modelName, $query);
return $this->view->helper('json')->add($count, 'count');

Limit and Offset

The main feature of pagination is the ability to fetch only one page of results at a time, reducing the amount of data fetched in requests. So then this requires two additional parameters:

  • limit: This parameter determines the number of entries per page, as defined by entriesPerPage. This can be accomplished using a filter with $qb->limit.
  • offset: This determines the first entry to be fetched for the limit. This can be accomplished using a filter with $qb->offset.

The following code implements both limit and offset:

if (isset($options->limit)) {
  $limit = $options->limit;
  $offset = $options->offset ?? 0;
  $this->adapter->addFilter($modelName, function($qb)
      use ($limit, $offset) {
    $qb->limit($limit);
    $qb->offset($offset);
  });
}

Sorting

Pagination requires refetching all pages every time the sort order changes, as the first result may not be on the first page. FwPaginationWrapper handles all the logic needed to do that client side, but it needs to be supported on the server side to work. This requires two parameters:

  • sortKey: Table key to use in sorting
  • ascending: If true, sorts results ascending. If false, sorts them descending.

The following code implements sort key and ascending:

if (isset($options->sortKey)) {
  $sortKey = alias($options->sortKey);
  $ascending = ($options->ascending ?? 'false') == 'true';
  $ascending = $ascending ? 'ASC' : 'DESC';
  $this->adapter->addFilter($modelName, function($qb)
      use ($sortKey, $ascending) {
    $qb->orderBy($sortKey, $ascending);
  });
}

In this code, alias is a function that maps the sortKey from the client side name used in the parameter to the server side name supported by SQL. Point of Sales handles this using a function in the model, called using $this->adapter->aliasKey.

Note: you may need more complex code to sort by complex table fields, such as a relationship (such as Status’s location name) or a computed value (such as Point of Sales’s transaction log total). An example of this can be found in Point of Sales.

Design Considerations

It is important to note that if you want to use SQL limiting and offsets, you should do all filtering and sorting using SQL. This means that any filters in PHP need to be migrated to SQL or otherwise some pages will return too few results.