rjbma / elm-listview / ListView

A package for viewing data from a List. Features currently include sorting and pagination.

A key characteristic of this package is that it tries to seperate transforming the data (i.e., sort, paginate, group, filter, etc.) from actually viewing it (e.g., rendering a HTML table, CSS grid, etc.).

This module, ListView, is only responsible for the first part, however the ListView.Viewers module provides some standard viewers that should be sufficient for many use cases.

Basic usage

Here's a small example of how to render a list of data as a HTML table, using the viewer ListView.Viewers.viewAsHtmlTable. Note that we also use the TableMsg type and updateTable function from that same module, which are needed by the viewHtmlTable function. As is ususal with other Elm packages, the updateTable function needs to be called from your main update function somehow.

-- main entity type we want to view
type alias Character =
    { fullName : String
    , power : Int
    , imageUrl : String
    }

-- some data, this is the list we want to view
rows : List Character
rows = [...]

-- this is just a helper function to render an image
viewPicture : character -> Html msg
viewPicture row =
    Html.img [ class_ "avatar", src row.imageUrl ] []


-- config for my list to be viewed as an HTML table with 3 columns
tableConfig : Config Character Msg
tableConfig =
    ListView.makeConfig
        |> ListView.withColumn (ListView.makeColumn.html "" viewPicture)
        |> ListView.withColumn (ListView.makeColumn.string "Name" .fullName)
        |> ListView.withColumn (ListView.makeColumn.int "Power" .power)

-- we need to store the `DataList` state in our model
type alias Model = { tableState : ListView.State }


-- our app needs to be able to process messages from the `DataListViewer` (e.g., sort by this column)
type Msg
    = OnTableMsg ListView.Viewers.ListViewMsg

-- our main `update` function needs to handle messages from the table
update : Msg -> Model -> Model
update msg model =
    case msg of
        OnTableMsg tableMsg ->
            { model | tableState = ListView.Viewers.update rows tableMsg model.tableState }


view : Model -> Html Msg
view model =
    ListView.Viewers.viewAsHtmlTable OnTableMsg tableConfig model.tableState rows

main : Program () Model Msg
main =
    Browser.sandbox
        { init = { tableState: ListView.makeState }
        , view = view
        , update = update
        }


type Config a msg

Opaque type holding all the static configuration of the list (basically, a list with all the viewable columns and their behavior)

makeConfig : Config a msg

Make an empty (i.e., without columns) table configuration. This is the starting point for adding new columns

makeConfig
    |> withColumn (makeColumn.html "" viewPicture)
    |> withColumn (makeColumn.string "Name" .fullName)
    |> withColumn (makeColumn.string "Tagline" .tagLine)
    |> withColumn (makeColumn.int "Power" .power)

makeColumn : { string : String -> (a -> String) -> ColumnConfig a msg, int : String -> (a -> Basics.Int) -> ColumnConfig a msg, float : String -> (a -> Basics.Float) -> ColumnConfig a msg, html : String -> (a -> RowIndex -> Html msg) -> ColumnConfig a msg }

Namespace for aggregating all the functions that create new columns for the data list. Some examples:

makeColumn.string "Car" .carName

.carName must return a String value. Column is sortable alphabetically by default.


makeColumn.int "Age" .age
makeColumn.float "Salary" .salary

Values are converted to String for rendering, but are sorted numerically by default.


makeColumn.html "Company url" (\row rowIndex -> Html.a [href row.url] [Html.text row.name])

The accessor function must return Html msg. Can be used to render just about anything. These columns are not sortable by default. An important difference from the other column types is that the accessor function has access to the index of the row in the original list. This is sometimes usefule if the generated Html wants to produce messages that need to identify the row by its index (e.g., a message to update the row somehow).


Note: This namespace only exists because there are already too many exposed functions in this module, and I wanted to group related functions into a common namespace, without creating a separate module.

Not really sure this is a good idea, may change in the future.

withColumn : ColumnConfig a msg -> Config a msg -> Config a msg

Add a new column to a table configuration. Columns can be created with makeColumn.XXX functions

withColumns : List (ColumnConfig a msg) -> Config a msg -> Config a msg

Add a bunch of new columns to a table configuration. Columns can be created with makeColumn.XXX functions


type State

Holds all the state of the ListView, like how it's currently being sorted or what the current page is.

You definately want to store this in your Model. In the future this should be exportable/importable in order to be stored in local storage, for example.

makeState : State

Creates a State with default setting: page size of 10 elements and no sorting.

Advanced usage

Sometimes the output of the standard viewers defined in ListView.Viewers may not be suitable for your needs; maybe you want to layout your page using elm-ui instead; or maybe you just need a simpler paginator with only Previous and Next buttons.

In those cases, you may want to skip the ListView.Viewers altogether, and use the ListView.getViewInfo instead, which returns all the information needed (hopefully) to render the list, already sorted, filtered, etc. This is exactly what the viewers from the ListView.Viewers module do, so be sure to check the source code.

One caveat is that, in order for external packages to be able to render data from the ListView module, a lot of (otherwise internal) details had to be exposed. This creates a very large public API, which will mostly likely change when this package evolves, creating many breaking changes along the way. More so in codebases that define their own viewers. Keep this in mind when going down this route!

A final note: many customizations can be made to the standard viewers using CSS alone. You may not need build your own viewer!

getViewInfo : Config a msg -> State -> List a -> ListViewInfo a msg

Takes in all the data, configuration and state and produces all the information needed to view the ListView, poperly sorted, paginated and formatted.

Advanced usages can call this function to completely customize what gets rendered, and what messages are produced.

Typical usages that just need an HTML table or a CSS grid/flexbox should just use the corresponding viewer from ListView.Viewers (which all call this function under the hoods).

updateState : { withPage : Basics.Int -> PageChange -> State -> State, withSorting : ListViewSortState -> State -> State, withRowsPerPage : Basics.Int -> State -> State }

Namespace for aggregating all the functions that update the given ListView.State.

updateColumn : { withName : String -> ColumnConfig a msg -> ColumnConfig a msg, withCode : String -> ColumnConfig a msg -> ColumnConfig a msg, withSorter : (a -> comparable) -> ColumnConfig a msg -> ColumnConfig a msg, withViewer : (a -> RowIndex -> ColumnOutput msg) -> ColumnConfig a msg -> ColumnConfig a msg }

Namespace for aggregating all the functions that change the configuration an existing Column (i.e., how it's displayed or sorted, or even its name). For example, to create a sortable HTML column:

showUsername person =
    Html.span []
        [ Html.img [ src person.avatar ] []
        , Html.text person.name
        ]
makeColumn.html "Name" showUsername .name


type SortDirection
    = ASC
    | DESC

Direction of sorting. Note that currently, if a column is sortable, it can always be sorted in both directions.


type ListViewSortState
    = Unsorted
    | Sorted ColumnIndex SortDirection

Information on how the table is currently being sorted. Part of State


type PageChange
    = GotoFirstPage
    | GotoLastPage
    | GotoNextPage
    | GotoPreviousPage
    | GotoPage Basics.Int

Represents a change in the current page


type alias ListViewInfo a msg =
{ columnsForRow : a -> RowIndex -> List (Html msg)
, rowsToDisplay : List ( RowIndex
, a )
, columnsViewInfo : List ColumnViewInfo
, pagingViewInfo : PagingViewInfo 
}

Type that holds all the necessary information for rendering a ListView


type alias PagingViewInfo =
{ numberOfRows : Basics.Int
, numberOfPages : Basics.Int
, rowsPerPage : Basics.Int
, currentPage : Basics.Int
, currentPageStartIndex : Basics.Int
, currentPageEndIndex : Basics.Int 
}

Information about the ListView paging, should contain all that's needed to render a paginator


type alias ColumnViewInfo =
{ name : String
, code : String
, index : ColumnIndex
, sortInfo : ColumnSortInfo 
}

Type that holds all the necessary information for rendering a column header


type ColumnSortInfo
    = UnsortableColumn
    | UnsortedColumn
    | SortedColumn SortDirection

Represents sort state for a single column