rundis / elm-bootstrap

Responsive and reliable web apps with Elm and Twitter Bootstrap
http://elm-bootstrap.info/
BSD 3-Clause "New" or "Revised" License
398 stars 72 forks source link

Add Pagination #99

Closed peteraba closed 5 years ago

peteraba commented 6 years ago

I'm missing the Pagination package and have created the code to fulfill it, but to honor @rundis, I decided to create an issue instead of a PR. Here's my proposed API:

module Bootstrap.Pagination
    exposing
        ( defaultLabels
        , defaultUrlTemplate
        , justifyContentAround
        , justifyContentBetween
        , justifyContentCenter
        , justifyContentEnd
        , justifyContentStart
        , large
        , onClick
        , pagination
        , simplePagination
        , small
        )

{-| Pagination labels provide a way to customize the text of the pagination links and the ariaLabel of the pagination

    Pagination.PaginationLabels
        { ariaLabel = "Page navigation example" -- aria-label for the nav HTML element
        , firstLabel = "Fir" -- text of the "First" navigation link
        , previousLabel = "Pre" -- text of the "Previous" navigation link
        , nextLabel = "Nex" -- text of the "Next" navigation link
        , lastLabel = "Las" -- text of the "Last" navigation link
        }

-}
type alias PaginationLabels =
    { ariaLabel : String
    , firstLabel : Maybe String
    , previousLabel : Maybe String
    , nextLabel : Maybe String
    , lastLabel : Maybe String
    }

{-| Create an empty Pagination Labels instance
-}
defaultLabels : PaginationLabels

{-| Provide a valid Pagination url template
-}
defaultUrlTemplate : String
defaultUrlTemplate =
    "#%d"

{-| Opaque type representing possible options for pagination
-}
type Option msg
    = Icons
    | Small
    | Large
    | JustifyContentStart
    | JustifyContentEnd
    | JustifyContentCenter
    | JustifyContentBetween
    | JustifyContentAround
    | Attrs (List (Html.Attribute msg))

{-| Allows you to create a simple default pagination
-}
simplePagination : ( Int, Int, Int, String, List (Html.Attribute msg) ) -> Html.Html msg

{-| Create a customizable pagination

    Pagination.pagination
        { options = [ Pagination.small ] -- list of pagination options
        , pageActive = 5 -- current page to display
        , pageMax = 8 -- maximum number of pages to display
        , maxNumLinksPerSide = 2 -- maximum number of "extra" pages to display on each side of the currently active one
        , urlTemplate = "#my-route/%d?my-filter=foo" -- url template %d will be replaced with the number of the page
        , labels = defaultLabels
        , linkAttrs = Pagination.onClick msg -- attributes to be used for each navigation link
        }

-}
pagination :
    { options : List (Option msg)
    , pageActive : Int
    , pageMax : Int
    , maxNumLinksPerSide : Int
    , urlTemplate : String
    , labels : PaginationLabels
    , linkAttrs : List (Html.Attribute msg)
    }
    -> Html.Html msg
peteraba commented 6 years ago

@rundis ?

rundis commented 6 years ago

@peteraba Sorry for the slow response, but I've been totally swamped with work.

See this for first stab attempt from my side: https://github.com/rundis/elm-bootstrap/commit/8c07251518bfd3181c877050fa97166a32d0cca2

|'ve tried to balance flexibility vs easy of use vs typical use cases (inspired by your gist and original post above). I did leave some stuff out:

Simple example

import Bootstrap.Pagination as Pagination
import Bootstrap.HAlign as HAlign

simplePaginationList: Model -> Html Msg
simplePaginationList model =
    Pagination.defaultConfig
        |> Pagination.ariaLabel "Pagination"
        |> Pagination HAlign.centerXs
        |> Pagination.large
        |> Pagination.itemsList
            { prevItem = Just <| Pagination.ListItem [] [ text "Previous" ]
            , nextItem = Just <| Pagination.ListItem [] [ text "Next" ]
            , activeIdx = model.activePageIdx
            , data = [ 1, 2, 3, 4, 5 ] -- You'd typically generate this from your model somehow !
            , itemFn = \idx _ -> Pagination.ListItem [] [ text <| toString (idx + 1) ]
            , urlFn = \idx _ -> "#/pages/" ++ toString (idx + 1)
            }
        |> Pagination.view

Custom

import Bootstrap.Pagination as Pagination
import Bootstrap.Pagination as Item
import Bootstrap.HAlign as HAlign

customPagination : Model -> Html Msg
customPagination model =
    let
        myData =
            [ { icon = "car", name = "Car" }
            , { icon = "bus", name = "Bus" }
            , { icon = "train", name = "Train" }
            ]
    in
        div []
            [ h1 [] [ text "Pagination" ]
            , Pagination.defaultConfig
                |> Pagination.ariaLabel "Pagination"
                |> Pagination.align HAlign.centerXs
                |> Pagination.large
                |> Pagination.items
                    ([ Item.item
                        |> Item.span [ class "custom-page-item" ]
                            [ span 
                                [ class "fa fa-fast-backward"
                                , attribute "aria-hidden" "true" ] 
                                []
                            , span [ class "sr-only" ] 
                                [ text "First page" ]
                            ]
                     , Item.item
                        |> Item.span [ class "custom-page-item" ]
                            [ span 
                                [ class "fa fa-arrow-left"
                                , attribute "aria-hidden" "true" 
                                ] 
                                []
                            , span [ class "sr-only" ] [ text "Previous" ]
                            ]
                     ]
                        ++ (List.indexedMap
                                (\idx item ->
                                    Item.item
                                        |> Item.active ( idx == model.activePageIdx )
                                        |> Item.span [ class "custom-page-item" ]
                                            [ span 
                                                [ class <| "fa fa-" ++ item.icon
                                                , attribute "aria-hidden" "true" 
                                                ] 
                                                []
                                            , span [ class "sr-only" ] [ text item.name ]
                                            ]
                                )
                                myData
                           )
                        ++ [ Item.item
                                |> Item.span [ class "custom-page-item" ]
                                    [ span 
                                        [ class "fa fa-arrow-right"
                                        , attribute "aria-hidden" "true" 
                                        ] 
                                        []
                                    , span [ class "sr-only" ] [ text "Next" ]
                                    ]
                           , Item.item
                                |> Item.span [ class "custom-page-item" ]
                                    [ span 
                                        [ class "fa fa-fast-forward"
                                        , attribute "aria-hidden" "true" ] 
                                        []
                                    , span [ class "sr-only" ] [ text "Last page" ]
                                    ]
                           ]
                    )
                |> Pagination.view
            ]
peteraba commented 6 years ago

Hi @rundis , I have to admit I kind of moved on this problem in the meanwhile, but would still be interested in contributing.

The proposed usage examples generally look good except that your proposal is a bit thiner, leaving the user more work to figure out some details. It's completely legit and arguable better, I went for a bit "smarter" version where the numeric items can be built automatically. I would still create another layer which is able to calculate "data" but your is nice for it's flexibility.

When it comes to your questions: both "max pages" and "max number of links per side" were only needed so that "data" could be calculated. As said, that calculation could happen outside the bootstrap library of course.

rundis commented 5 years ago

A module for this now exists. Missing docs on the docs site still though.