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 requestsdefaultSortKey
: Default order for sorting the tabletableWrapperClass
: 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 useactions.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 tovaluePath
. Most commonly used whenvaluePath
uses a relationship property, assearchKey
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 intoFwTableSortable
'stableActions
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 inFwTableSortable
'sonSort
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 asdefaultSort
.
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 byentriesPerPage
. 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 sortingascending
: 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.