Compare commits

...

17 Commits

Author SHA1 Message Date
setop b40d76e4bf add initialRevSort 2023-11-29 10:49:43 +01:00
Tereza Sokol 2d1117eac2 Upgrade to 0.19 2018-11-13 14:53:46 -05:00
Evan Czaplicki bf632ced14 Merge pull request #13 from MatthiasWinkelmann/master
Updated examples for elm 0.18
2016-12-12 00:09:31 -06:00
Matthias Winkelmann 1078c7f5fa
Updated examples for elm 0.18 2016-12-07 07:40:58 +01:00
Evan Czaplicki 6d9acec654 Merge pull request #11 from derekdreery/patch-1
Very small typo ;)
2016-11-23 11:43:46 -06:00
derekdreery 4dbfc3f806 Very small typo ;) 2016-11-22 22:14:56 +00:00
Evan Czaplicki 2903fe9620 Merge pull request #8 from tim-bezhashvyly/patch-1
Add Donald Trump to presidents list
2016-11-16 15:37:40 -08:00
Tim Bezhashvyly 535c39d7d3 Add Donald Trump to presidents list 2016-11-16 22:54:49 +01:00
Evan Czaplicki 2fdac906c3 Merge pull request #6 from dela3499/patch-1
Fixed typo: The data displayed by in the table...
2016-10-13 11:14:39 -07:00
Carlos De la Guardia 18dc1c23ae Fixed typo: The data displayed by in the table... 2016-10-13 10:49:56 -07:00
Evan Czaplicki 2191b37127 Bump to 1.0.1 2016-10-07 11:25:01 -07:00
Evan Czaplicki 8206138b89 Update constraints and code for 0.18 2016-10-07 11:24:39 -07:00
Evan Czaplicki 86377edfcc Merge pull request #4 from jhrcek/docFix
Fix examples links in the docs
2016-08-07 11:35:20 -07:00
Jan Hrček 9f8866525e Fix examples links in the docs 2016-08-07 09:14:25 +02:00
Evan Czaplicki d988bd91bc Fix repo name 2016-07-25 14:26:40 -07:00
Evan Czaplicki 701149ab61 Fix typo in example links 2016-07-25 14:09:09 -07:00
Evan Czaplicki 7f3ab5f2f7 Use https links to examples 2016-07-25 14:06:59 -07:00
9 changed files with 324 additions and 267 deletions

1
.tool-versions Normal file
View File

@ -0,0 +1 @@
elm 0.19.1

View File

@ -7,8 +7,8 @@ This library also lets you customize `<caption>`, `<tbody>`, `<tr>`, etc. for yo
## Examples ## Examples
1. [U.S. Presidents by Birth Place](http://evancz.github.io/elm-sortable-tables/presidents.html) / [Code](https://github.com/evancz/elm-sortable-table/blob/master/examples/1-presidents.elm) 1. [U.S. Presidents by Birth Place](https://evancz.github.io/elm-sortable-table/presidents.html) / [Code](https://github.com/evancz/elm-sortable-table/blob/master/examples/1-presidents.elm)
2. [Travel Planner for the Mission District in San Francisco](http://evancz.github.io/elm-sortable-tables/travel.html) / [Code](https://github.com/evancz/elm-sortable-table/blob/master/examples/2-travel.elm) 2. [Travel Planner for the Mission District in San Francisco](https://evancz.github.io/elm-sortable-table/travel.html) / [Code](https://github.com/evancz/elm-sortable-table/blob/master/examples/2-travel.elm)
## Usage Rules ## Usage Rules
@ -43,7 +43,7 @@ I took some minor liberties with `update` to make the API a bit simpler. It woul
### Single Source of Truth ### Single Source of Truth
The data displayed by in the table is given as an argument to `view`. To put that another way, the `Table.State` value only tracks the details specific to *displaying* a sorted table, not the actual data to appear in the table. **This is the most important decision in this whole library.** This choice means you can change your data without any risk of the table getting out of sync. You may be adding things, changing entries, or whatever else; the table will never &ldquo;get stuck&rdquo; and display out of date information. The data displayed in the table is given as an argument to `view`. To put that another way, the `Table.State` value only tracks the details specific to *displaying* a sorted table, not the actual data to appear in the table. **This is the most important decision in this whole library.** This choice means you can change your data without any risk of the table getting out of sync. You may be adding things, changing entries, or whatever else; the table will never &ldquo;get stuck&rdquo; and display out of date information.
To make this more clear, let&rsquo;s imagine the alternate choice: instead of giving `List data` to `view`, we have it live in `Table.State`. Now say we want to update the dataset. We grab a copy of the data, make the changes we want, and put it back. But what if we forget to put it back? What if we hold on to that second copy in our `Model`? Which one is the *real* data now? To make this more clear, let&rsquo;s imagine the alternate choice: instead of giving `List data` to `view`, we have it live in `Table.State`. Now say we want to update the dataset. We grab a copy of the data, make the changes we want, and put it back. But what if we forget to put it back? What if we hold on to that second copy in our `Model`? Which one is the *real* data now?

View File

@ -1,7 +1,7 @@
{ {
"version": "1.0.0", "version": "1.0.1",
"summary": "Sortable tables for data of any shape.", "summary": "Sortable tables for data of any shape.",
"repository": "https://github.com/evancz/elm-table.git", "repository": "https://github.com/evancz/elm-sortable-table.git",
"license": "BSD3", "license": "BSD3",
"source-directories": [ "source-directories": [
"src" "src"
@ -10,8 +10,8 @@
"Table" "Table"
], ],
"dependencies": { "dependencies": {
"elm-lang/core": "4.0.0 <= v < 5.0.0", "elm-lang/core": "5.0.0 <= v < 6.0.0",
"elm-lang/html": "1.1.0 <= v < 2.0.0" "elm-lang/html": "2.0.0 <= v < 3.0.0"
}, },
"elm-version": "0.17.0 <= v < 0.18.0" "elm-version": "0.18.0 <= v < 0.19.0"
} }

17
elm.json Normal file
View File

@ -0,0 +1,17 @@
{
"type": "package",
"name": "NoRedInk/elm-sortable-table",
"summary": "Sortable tables for whatever data you want to display.",
"license": "BSD-3-Clause",
"version": "1.0.1",
"exposed-modules": [
"Table"
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {
"elm/core": "1.0.0 <= v < 2.0.0",
"elm/html": "1.0.0 <= v < 2.0.0",
"elm/json": "1.0.0 <= v < 2.0.0"
},
"test-dependencies": {}
}

View File

@ -1,14 +1,12 @@
import Html exposing (Html, div, h1, input, text) import Html exposing (Html, div, h1, input, text)
import Html.App as App
import Html.Attributes exposing (placeholder) import Html.Attributes exposing (placeholder)
import Html.Events exposing (onInput) import Html.Events exposing (onInput)
import String
import Table import Table
main = main =
App.program Html.program
{ init = init presidents { init = init presidents
, update = update , update = update
, view = view , view = view
@ -153,4 +151,5 @@ presidents =
, Person "George W. Bush" 1946 "New Haven" "Connecticut" , Person "George W. Bush" 1946 "New Haven" "Connecticut"
, Person "Bill Clinton" 1946 "Hope" "Arkansas" , Person "Bill Clinton" 1946 "Hope" "Arkansas"
, Person "Barack Obama" 1961 "Honolulu" "Hawaii" , Person "Barack Obama" 1961 "Honolulu" "Hawaii"
, Person "Donald Trump" 1946 "New York City" "New York"
] ]

View File

@ -1,16 +1,14 @@
import Html exposing (Html, Attribute, div, h1, input, p, text) import Html exposing (Html, Attribute, div, h1, input, p, text)
import Html.App as App import Html.Attributes exposing (checked, style, type_)
import Html.Attributes exposing (checked, style, type')
import Html.Events exposing (onClick) import Html.Events exposing (onClick)
import Html.Lazy exposing (lazy) import Html.Lazy exposing (lazy)
import String
import Table exposing (defaultCustomizations) import Table exposing (defaultCustomizations)
import Time exposing (Time) import Time exposing (Time)
main = main =
App.program Html.program
{ init = init missionSights { init = init missionSights
, update = update , update = update
, view = view , view = view
@ -171,7 +169,7 @@ checkboxColumn =
viewCheckbox : Sight -> Table.HtmlDetails Msg viewCheckbox : Sight -> Table.HtmlDetails Msg
viewCheckbox {selected} = viewCheckbox {selected} =
Table.HtmlDetails [] Table.HtmlDetails []
[ input [ type' "checkbox", checked selected ] [] [ input [ type_ "checkbox", checked selected ] []
] ]

View File

@ -1,7 +1,7 @@
# Examples # Examples
1. [U.S. Presidents by Birth Place](http://evancz.github.io/elm-sortable-tables/presidents.html) 1. [U.S. Presidents by Birth Place](https://evancz.github.io/elm-sortable-table/presidents.html)
2. [Travel Planner for the Mission District in San Francisco](http://evancz.github.io/elm-sortable-tables/travel.html) 2. [Travel Planner for the Mission District in San Francisco](https://evancz.github.io/elm-sortable-table/travel.html)
## Build Instructions ## Build Instructions

View File

@ -1,5 +1,5 @@
{ {
"version": "1.0.0", "version": "1.0.1",
"summary": "helpful summary of your project, less than 80 characters", "summary": "helpful summary of your project, less than 80 characters",
"repository": "https://github.com/user/project.git", "repository": "https://github.com/user/project.git",
"license": "BSD3", "license": "BSD3",
@ -8,9 +8,9 @@
], ],
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"elm-lang/core": "4.0.3 <= v < 5.0.0", "elm-lang/core": "5.0.0 <= v < 6.0.0",
"elm-lang/html": "1.1.0 <= v < 2.0.0", "elm-lang/html": "2.0.0 <= v < 3.0.0",
"evancz/elm-sortable-table": "1.0.0 <= v < 2.0.0" "evancz/elm-sortable-table": "1.0.1 <= v < 2.0.0"
}, },
"elm-version": "0.17.0 <= v < 0.18.0" "elm-version": "0.18.0 <= v < 0.19.0"
} }

View File

@ -1,34 +1,35 @@
module Table exposing module Table exposing
( view ( view
, config, stringColumn, intColumn, floatColumn , config, stringColumn, intColumn, floatColumn
, State, initialSort , State, initialSort, initialRevSort
, Column, customColumn, veryCustomColumn , Column, customColumn, veryCustomColumn
, Sorter, unsortable, increasingBy, decreasingBy , Sorter, unsortable, increasingBy, decreasingBy
, increasingOrDecreasingBy, decreasingOrIncreasingBy , increasingOrDecreasingBy, decreasingOrIncreasingBy
, Config, customConfig , Config, customConfig, Customizations, HtmlDetails, Status(..)
, Customizations, HtmlDetails, Status(..), defaultCustomizations , defaultCustomizations
) )
{-| {-| This library helps you create sortable tables. The crucial feature is that it
This library helps you create sortable tables. The crucial feature is that it
lets you own your data separately and keep it in whatever format is best for lets you own your data separately and keep it in whatever format is best for
you. This way you are free to change your data without worrying about the table you. This way you are free to change your data without worrying about the table
&ldquo;getting out of sync&rdquo; with the data. Having a single source of &ldquo;getting out of sync&rdquo; with the data. Having a single source of
truth is pretty great! truth is pretty great!
I recommend checking out the [examples][] to get a feel for how it works. I recommend checking out the [examples] to get a feel for how it works.
[examples]: https://github.com/evancz/elm-sortable-table/tree/master/examples
[examples]: https://github.com/evancz/elm-tables/tree/master/examples
# View # View
@docs view @docs view
# Configuration # Configuration
@docs config, stringColumn, intColumn, floatColumn @docs config, stringColumn, intColumn, floatColumn
# State # State
@docs State, initialSort @docs State, initialSort
@ -41,19 +42,22 @@ point are a bunch of ways to customize your table further. If it does not
provide what you need, you may just want to write a custom table yourself. It provide what you need, you may just want to write a custom table yourself. It
is not that crazy. is not that crazy.
## Custom Columns ## Custom Columns
@docs Column, customColumn, veryCustomColumn, @docs Column, customColumn, veryCustomColumn
Sorter, unsortable, increasingBy, decreasingBy, @docs Sorter, unsortable, increasingBy, decreasingBy
increasingOrDecreasingBy, decreasingOrIncreasingBy @docs increasingOrDecreasingBy, decreasingOrIncreasingBy
## Custom Tables ## Custom Tables
@docs Config, customConfig, Customizations, HtmlDetails, Status, @docs Config, customConfig, Customizations, HtmlDetails, Status
defaultCustomizations @docs defaultCustomizations
-} -}
import Html exposing (Html, Attribute) import Html exposing (Attribute, Html)
import Html.Attributes as Attr import Html.Attributes as Attr
import Html.Events as E import Html.Events as E
import Html.Keyed as Keyed import Html.Keyed as Keyed
@ -67,8 +71,8 @@ import Json.Decode as Json
{-| Tracks which column to sort by. {-| Tracks which column to sort by.
-} -}
type State = type State
State String Bool = State String Bool
{-| Create a table state. By providing a column name, you determine which {-| Create a table state. By providing a column name, you determine which
@ -78,10 +82,15 @@ yachts to be sorted by length by default, you might say:
import Table import Table
Table.initialSort "Length" Table.initialSort "Length"
-} -}
initialSort : String -> State initialSort : String -> State
initialSort header = initialSort header =
State header False State header False
initialRevSort : String -> State
initialRevSort header =
State header True
@ -90,16 +99,17 @@ initialSort header =
{-| Configuration for your table, describing your columns. {-| Configuration for your table, describing your columns.
**Note:** Your `Config` should *never* be held in your model. **Note:** Your `Config` should _never_ be held in your model.
It should only appear in `view` code. It should only appear in `view` code.
-} -}
type Config data msg = type Config data msg
Config = Config
{ toId : data -> String { toId : data -> String
, toMsg : State -> msg , toMsg : State -> msg
, columns : List (ColumnData data msg) , columns : List (ColumnData data msg)
, customizations : Customizations data msg , customizations : Customizations data msg
} }
{-| Create the `Config` for your `view` function. Everything you need to {-| Create the `Config` for your `view` function. Everything you need to
@ -126,46 +136,47 @@ have a column for name and age. We would create a `Config` like this:
You provide the following information in your table configuration: You provide the following information in your table configuration:
- `toId` &mdash; turn a `Person` into a unique ID. This lets us use - `toId` &mdash; turn a `Person` into a unique ID. This lets us use
[`Html.Keyed`][keyed] under the hood to make resorts faster. [`Html.Keyed`][keyed] under the hood to make resorts faster.
- `columns` &mdash; specify some columns to show. - `columns` &mdash; specify some columns to show.
- `toMsg` &mdash; a way send new table states to your app as messages. - `toMsg` &mdash; a way to send new table states to your app as messages.
See the [examples][] to get a better feel for this! See the [examples] to get a better feel for this!
[keyed]: http://package.elm-lang.org/packages/elm-lang/html/latest/Html-Keyed [keyed]: http://package.elm-lang.org/packages/elm-lang/html/latest/Html-Keyed
[examples]: https://github.com/evancz/elm-tables/tree/master/examples [examples]: https://github.com/evancz/elm-sortable-table/tree/master/examples
-} -}
config config :
: { toId : data -> String { toId : data -> String
, toMsg : State -> msg , toMsg : State -> msg
, columns : List (Column data msg) , columns : List (Column data msg)
} }
-> Config data msg -> Config data msg
config { toId, toMsg, columns } = config { toId, toMsg, columns } =
Config Config
{ toId = toId { toId = toId
, toMsg = toMsg , toMsg = toMsg
, columns = List.map (\(Column cData) -> cData) columns , columns = List.map (\(Column cData) -> cData) columns
, customizations = defaultCustomizations , customizations = defaultCustomizations
} }
{-| Just like `config` but you can specify a bunch of table customizations. {-| Just like `config` but you can specify a bunch of table customizations.
-} -}
customConfig customConfig :
: { toId : data -> String { toId : data -> String
, toMsg : State -> msg , toMsg : State -> msg
, columns : List (Column data msg) , columns : List (Column data msg)
, customizations : Customizations data msg , customizations : Customizations data msg
} }
-> Config data msg -> Config data msg
customConfig { toId, toMsg, columns, customizations } = customConfig { toId, toMsg, columns, customizations } =
Config Config
{ toId = toId { toId = toId
, toMsg = toMsg , toMsg = toMsg
, columns = List.map (\(Column cData) -> cData) columns , columns = List.map (\(Column cData) -> cData) columns
, customizations = customizations , customizations = customizations
} }
{-| There are quite a lot of ways to customize the `<table>` tag. You can add {-| There are quite a lot of ways to customize the `<table>` tag. You can add
@ -175,18 +186,19 @@ summaries of various columns. And maybe you want to put attributes on `<tbody>`
or on particular rows in the body. All these customizations are available to you. or on particular rows in the body. All these customizations are available to you.
**Note:** The level of craziness possible in `<thead>` and `<tfoot>` are so **Note:** The level of craziness possible in `<thead>` and `<tfoot>` are so
high that I could not see how to provide the full functionality *and* make it high that I could not see how to provide the full functionality _and_ make it
impossible to do bad stuff. So just be aware of that, and share any stories impossible to do bad stuff. So just be aware of that, and share any stories
you have. Stories make it possible to design better! you have. Stories make it possible to design better!
-} -}
type alias Customizations data msg = type alias Customizations data msg =
{ tableAttrs : List (Attribute msg) { tableAttrs : List (Attribute msg)
, caption : Maybe (HtmlDetails msg) , caption : Maybe (HtmlDetails msg)
, thead : List (String, Status, Attribute msg) -> HtmlDetails msg , thead : List ( String, Status, Attribute msg ) -> HtmlDetails msg
, tfoot : Maybe (HtmlDetails msg) , tfoot : Maybe (HtmlDetails msg)
, tbodyAttrs : List (Attribute msg) , tbodyAttrs : List (Attribute msg)
, rowAttrs : data -> List (Attribute msg) , rowAttrs : data -> List (Attribute msg)
} }
{-| Sometimes you must use a `<td>` tag, but the attributes and children are up {-| Sometimes you must use a `<td>` tag, but the attributes and children are up
@ -194,68 +206,78 @@ to you. This type lets you specify all the details of an HTML node except the
tag name. tag name.
-} -}
type alias HtmlDetails msg = type alias HtmlDetails msg =
{ attributes : List (Attribute msg) { attributes : List (Attribute msg)
, children : List (Html msg) , children : List (Html msg)
} }
{-| The customizations used in `config` by default. {-| The customizations used in `config` by default.
-} -}
defaultCustomizations : Customizations data msg defaultCustomizations : Customizations data msg
defaultCustomizations = defaultCustomizations =
{ tableAttrs = [] { tableAttrs = []
, caption = Nothing , caption = Nothing
, thead = simpleThead , thead = simpleThead
, tfoot = Nothing , tfoot = Nothing
, tbodyAttrs = [] , tbodyAttrs = []
, rowAttrs = simpleRowAttrs , rowAttrs = simpleRowAttrs
} }
simpleThead : List (String, Status, Attribute msg) -> HtmlDetails msg simpleThead : List ( String, Status, Attribute msg ) -> HtmlDetails msg
simpleThead headers = simpleThead headers =
HtmlDetails [] (List.map simpleTheadHelp headers) HtmlDetails [] (List.map simpleTheadHelp headers)
simpleTheadHelp : ( String, Status, Attribute msg ) -> Html msg simpleTheadHelp : ( String, Status, Attribute msg ) -> Html msg
simpleTheadHelp (name, status, onClick) = simpleTheadHelp ( name, status, onClick_ ) =
let let
content = content =
case status of case status of
Unsortable -> Unsortable ->
[ Html.text name ] [ Html.text name ]
Sortable selected -> Sortable selected ->
[ Html.text name [ Html.text name
, if selected then darkGrey "" else lightGrey "" , if selected then
] darkGrey ""
Reversible Nothing -> else
[ Html.text name lightGrey ""
, lightGrey "" ]
]
Reversible (Just isReversed) -> Reversible Nothing ->
[ Html.text name [ Html.text name
, darkGrey (if isReversed then "" else "") , lightGrey ""
] ]
in
Html.th [ onClick ] content Reversible (Just isReversed) ->
[ Html.text name
, darkGrey
(if isReversed then
""
else
""
)
]
in
Html.th [ onClick_ ] content
darkGrey : String -> Html msg darkGrey : String -> Html msg
darkGrey symbol = darkGrey symbol =
Html.span [ Attr.style [("color", "#555")] ] [ Html.text (" " ++ symbol) ] Html.span [ Attr.style "color" "#555" ] [ Html.text (" " ++ symbol) ]
lightGrey : String -> Html msg lightGrey : String -> Html msg
lightGrey symbol = lightGrey symbol =
Html.span [ Attr.style [("color", "#ccc")] ] [ Html.text (" " ++ symbol) ] Html.span [ Attr.style "color" "#ccc" ] [ Html.text (" " ++ symbol) ]
simpleRowAttrs : data -> List (Attribute msg) simpleRowAttrs : data -> List (Attribute msg)
simpleRowAttrs _ = simpleRowAttrs _ =
[] []
{-| The status of a particular column, for use in the `thead` field of your {-| The status of a particular column, for use in the `thead` field of your
@ -272,11 +294,12 @@ simpleRowAttrs _ =
is sorted. is sorted.
This information lets you do custom header decorations for each scenario. This information lets you do custom header decorations for each scenario.
-} -}
type Status type Status
= Unsortable = Unsortable
| Sortable Bool | Sortable Bool
| Reversible (Maybe Bool) | Reversible (Maybe Bool)
@ -285,50 +308,50 @@ type Status
{-| Describes how to turn `data` into a column in your table. {-| Describes how to turn `data` into a column in your table.
-} -}
type Column data msg = type Column data msg
Column (ColumnData data msg) = Column (ColumnData data msg)
type alias ColumnData data msg = type alias ColumnData data msg =
{ name : String { name : String
, viewData : data -> HtmlDetails msg , viewData : data -> HtmlDetails msg
, sorter : Sorter data , sorter : Sorter data
} }
{-|-} {-| -}
stringColumn : String -> (data -> String) -> Column data msg stringColumn : String -> (data -> String) -> Column data msg
stringColumn name toStr = stringColumn name toStr =
Column Column
{ name = name { name = name
, viewData = textDetails << toStr , viewData = textDetails << toStr
, sorter = increasingOrDecreasingBy toStr , sorter = increasingOrDecreasingBy toStr
} }
{-|-} {-| -}
intColumn : String -> (data -> Int) -> Column data msg intColumn : String -> (data -> Int) -> Column data msg
intColumn name toInt = intColumn name toInt =
Column Column
{ name = name { name = name
, viewData = textDetails << toString << toInt , viewData = textDetails << String.fromInt << toInt
, sorter = increasingOrDecreasingBy toInt , sorter = increasingOrDecreasingBy toInt
} }
{-|-} {-| -}
floatColumn : String -> (data -> Float) -> Column data msg floatColumn : String -> (data -> Float) -> Column data msg
floatColumn name toFloat = floatColumn name toFloat =
Column Column
{ name = name { name = name
, viewData = textDetails << toString << toFloat , viewData = textDetails << String.fromFloat << toFloat
, sorter = increasingOrDecreasingBy toFloat , sorter = increasingOrDecreasingBy toFloat
} }
textDetails : String -> HtmlDetails msg textDetails : String -> HtmlDetails msg
textDetails str = textDetails str =
HtmlDetails [] [ Html.text str ] HtmlDetails [] [ Html.text str ]
{-| Perhaps the basic columns are not quite what you want. Maybe you want to {-| Perhaps the basic columns are not quite what you want. Maybe you want to
@ -339,66 +362,68 @@ quite cut it. You could define a custom column like this:
dollarColumn : String -> (data -> Float) -> Column data msg dollarColumn : String -> (data -> Float) -> Column data msg
dollarColumn name toDollars = dollarColumn name toDollars =
Table.customColumn Table.customColumn
{ name = name { name = name
, viewData = \data -> viewDollars (toDollars data) , viewData = \data -> viewDollars (toDollars data)
, sorter = Table.decreasingBy toDollars , sorter = Table.decreasingBy toDollars
} }
viewDollars : Float -> String viewDollars : Float -> String
viewDollars dollars = viewDollars dollars =
"$" ++ toString (round (dollars / 1000)) ++ "k" "$" ++ toString (round (dollars / 1000)) ++ "k"
The `viewData` field means we will displays the number `12345.67` as `$12k`. The `viewData` field means we will displays the number `12345.67` as `$12k`.
The `sorter` field specifies how the column can be sorted. In `dollarColumn` we The `sorter` field specifies how the column can be sorted. In `dollarColumn` we
are saying that it can *only* be shown from highest-to-lowest monetary value. are saying that it can _only_ be shown from highest-to-lowest monetary value.
More about sorters soon! More about sorters soon!
-} -}
customColumn customColumn :
: { name : String { name : String
, viewData : data -> String , viewData : data -> String
, sorter : Sorter data , sorter : Sorter data
} }
-> Column data msg -> Column data msg
customColumn { name, viewData, sorter } = customColumn { name, viewData, sorter } =
Column <| Column <|
ColumnData name (textDetails << viewData) sorter ColumnData name (textDetails << viewData) sorter
{-| It is *possible* that you want something crazier than `customColumn`. In {-| It is _possible_ that you want something crazier than `customColumn`. In
that unlikely scenario, this function lets you have full control over the that unlikely scenario, this function lets you have full control over the
attributes and children of each `<td>` cell in this column. attributes and children of each `<td>` cell in this column.
So maybe you want to a dollars column, and the dollar signs should be green. So maybe you want to a dollars column, and the dollar signs should be green.
import Html exposing (Html, Attribute, span, text) import Html exposing (Attribute, Html, span, text)
import Html.Attributes exposing (style) import Html.Attributes exposing (style)
import Table import Table
dollarColumn : String -> (data -> Float) -> Column data msg dollarColumn : String -> (data -> Float) -> Column data msg
dollarColumn name toDollars = dollarColumn name toDollars =
Table.veryCustomColumn Table.veryCustomColumn
{ name = name { name = name
, viewData = \data -> viewDollars (toDollars data) , viewData = \data -> viewDollars (toDollars data)
, sorter = Table.decreasingBy toDollars , sorter = Table.decreasingBy toDollars
} }
viewDollars : Float -> Table.HtmlDetails msg viewDollars : Float -> Table.HtmlDetails msg
viewDollars dollars = viewDollars dollars =
Table.HtmlDetails [] Table.HtmlDetails []
[ span [ style [("color","green")] ] [ text "$" ] [ span [ style [ ( "color", "green" ) ] ] [ text "$" ]
, text (toString (round (dollars / 1000)) ++ "k") , text (toString (round (dollars / 1000)) ++ "k")
] ]
-} -}
veryCustomColumn veryCustomColumn :
: { name : String { name : String
, viewData : data -> HtmlDetails msg , viewData : data -> HtmlDetails msg
, sorter : Sorter data , sorter : Sorter data
} }
-> Column data msg -> Column data msg
veryCustomColumn = veryCustomColumn =
Column Column
@ -414,89 +439,93 @@ for the table belongs in your `view` code. I very strongly recommend against
putting `Config` in your model. Describe any potential table configurations putting `Config` in your model. Describe any potential table configurations
statically, and look for a different library if you need something crazier than statically, and look for a different library if you need something crazier than
that. that.
-} -}
view : Config data msg -> State -> List data -> Html msg view : Config data msg -> State -> List data -> Html msg
view (Config { toId, toMsg, columns, customizations }) state data = view (Config { toId, toMsg, columns, customizations }) state data =
let let
sortedData = sortedData =
sort state columns data sort state columns data
theadDetails = theadDetails =
customizations.thead (List.map (toHeaderInfo state toMsg) columns) customizations.thead (List.map (toHeaderInfo state toMsg) columns)
thead = thead =
Html.thead theadDetails.attributes theadDetails.children Html.thead theadDetails.attributes theadDetails.children
tbody = tbody =
Keyed.node "tbody" customizations.tbodyAttrs <| Keyed.node "tbody" customizations.tbodyAttrs <|
List.map (viewRow toId columns customizations.rowAttrs) sortedData List.map (viewRow toId columns customizations.rowAttrs) sortedData
withFoot = withFoot =
case customizations.tfoot of case customizations.tfoot of
Nothing -> Nothing ->
tbody :: [] tbody :: []
Just { attributes, children } -> Just { attributes, children } ->
Html.tfoot attributes children :: tbody :: [] Html.tfoot attributes children :: tbody :: []
in in
Html.table customizations.tableAttrs <| Html.table customizations.tableAttrs <|
case customizations.caption of case customizations.caption of
Nothing -> Nothing ->
thead :: withFoot thead :: withFoot
Just { attributes, children } -> Just { attributes, children } ->
Html.caption attributes children :: thead :: withFoot Html.caption attributes children :: thead :: withFoot
toHeaderInfo : State -> (State -> msg) -> ColumnData data msg -> ( String, Status, Attribute msg ) toHeaderInfo : State -> (State -> msg) -> ColumnData data msg -> ( String, Status, Attribute msg )
toHeaderInfo (State sortName isReversed) toMsg { name, sorter } = toHeaderInfo (State sortName isReversed) toMsg { name, sorter } =
case sorter of case sorter of
None -> None ->
( name, Unsortable, onClick sortName isReversed toMsg ) ( name, Unsortable, onClick sortName isReversed toMsg )
Increasing _ -> Increasing _ ->
( name, Sortable (name == sortName), onClick name False toMsg ) ( name, Sortable (name == sortName), onClick name False toMsg )
Decreasing _ -> Decreasing _ ->
( name, Sortable (name == sortName), onClick name False toMsg ) ( name, Sortable (name == sortName), onClick name False toMsg )
IncOrDec _ -> IncOrDec _ ->
if name == sortName then if name == sortName then
( name, Reversible (Just isReversed), onClick name (not isReversed) toMsg ) ( name, Reversible (Just isReversed), onClick name (not isReversed) toMsg )
else
( name, Reversible Nothing, onClick name False toMsg )
DecOrInc _ -> else
if name == sortName then ( name, Reversible Nothing, onClick name False toMsg )
( name, Reversible (Just isReversed), onClick name (not isReversed) toMsg )
else DecOrInc _ ->
( name, Reversible Nothing, onClick name False toMsg ) if name == sortName then
( name, Reversible (Just isReversed), onClick name (not isReversed) toMsg )
else
( name, Reversible Nothing, onClick name False toMsg )
onClick : String -> Bool -> (State -> msg) -> Attribute msg onClick : String -> Bool -> (State -> msg) -> Attribute msg
onClick name isReversed toMsg = onClick name isReversed toMsg =
E.on "click" <| Json.map toMsg <| E.on "click" <|
Json.object2 State (Json.succeed name) (Json.succeed isReversed) Json.map toMsg <|
Json.map2 State (Json.succeed name) (Json.succeed isReversed)
viewRow : (data -> String) -> List (ColumnData data msg) -> (data -> List (Attribute msg)) -> data -> ( String, Html msg ) viewRow : (data -> String) -> List (ColumnData data msg) -> (data -> List (Attribute msg)) -> data -> ( String, Html msg )
viewRow toId columns toRowAttrs data = viewRow toId columns toRowAttrs data =
( toId data ( toId data
, lazy3 viewRowHelp columns toRowAttrs data , lazy3 viewRowHelp columns toRowAttrs data
) )
viewRowHelp : List (ColumnData data msg) -> (data -> List (Attribute msg)) -> data -> Html msg viewRowHelp : List (ColumnData data msg) -> (data -> List (Attribute msg)) -> data -> Html msg
viewRowHelp columns toRowAttrs data = viewRowHelp columns toRowAttrs data =
Html.tr (toRowAttrs data) (List.map (viewCell data) columns) Html.tr (toRowAttrs data) (List.map (viewCell data) columns)
viewCell : data -> ColumnData data msg -> Html msg viewCell : data -> ColumnData data msg -> Html msg
viewCell data {viewData} = viewCell data { viewData } =
let let
details = details =
viewData data viewData data
in in
Html.td details.attributes details.children Html.td details.attributes details.children
@ -506,44 +535,53 @@ viewCell data {viewData} =
sort : State -> List (ColumnData data msg) -> List data -> List data sort : State -> List (ColumnData data msg) -> List data -> List data
sort (State selectedColumn isReversed) columnData data = sort (State selectedColumn isReversed) columnData data =
case findSorter selectedColumn columnData of case findSorter selectedColumn columnData of
Nothing -> Nothing ->
data data
Just sorter -> Just sorter ->
applySorter isReversed sorter data applySorter isReversed sorter data
applySorter : Bool -> Sorter data -> List data -> List data applySorter : Bool -> Sorter data -> List data -> List data
applySorter isReversed sorter data = applySorter isReversed sorter data =
case sorter of case sorter of
None -> None ->
data data
Increasing sort -> Increasing sort_ ->
sort data sort_ data
Decreasing sort -> Decreasing sort_ ->
List.reverse (sort data) List.reverse (sort_ data)
IncOrDec sort -> IncOrDec sort_ ->
if isReversed then List.reverse (sort data) else sort data if isReversed then
List.reverse (sort_ data)
DecOrInc sort -> else
if isReversed then sort data else List.reverse (sort data) sort_ data
DecOrInc sort_ ->
if isReversed then
sort_ data
else
List.reverse (sort_ data)
findSorter : String -> List (ColumnData data msg) -> Maybe (Sorter data) findSorter : String -> List (ColumnData data msg) -> Maybe (Sorter data)
findSorter selectedColumn columnData = findSorter selectedColumn columnData =
case columnData of case columnData of
[] -> [] ->
Nothing Nothing
{name, sorter} :: remainingColumnData -> { name, sorter } :: remainingColumnData ->
if name == selectedColumn then if name == selectedColumn then
Just sorter Just sorter
else
findSorter selectedColumn remainingColumnData else
findSorter selectedColumn remainingColumnData
@ -553,11 +591,11 @@ findSorter selectedColumn columnData =
{-| Specifies a particular way of sorting data. {-| Specifies a particular way of sorting data.
-} -}
type Sorter data type Sorter data
= None = None
| Increasing (List data -> List data) | Increasing (List data -> List data)
| Decreasing (List data -> List data) | Decreasing (List data -> List data)
| IncOrDec (List data -> List data) | IncOrDec (List data -> List data)
| DecOrInc (List data -> List data) | DecOrInc (List data -> List data)
{-| A sorter for columns that are unsortable. Maybe you have a column in your {-| A sorter for columns that are unsortable. Maybe you have a column in your
@ -566,7 +604,7 @@ sort based on that column.
-} -}
unsortable : Sorter data unsortable : Sorter data
unsortable = unsortable =
None None
{-| Create a sorter that can only display the data in increasing order. If we {-| Create a sorter that can only display the data in increasing order. If we
@ -574,11 +612,12 @@ want a table of people, sorted alphabetically by name, we would say this:
sorter : Sorter { a | name : comparable } sorter : Sorter { a | name : comparable }
sorter = sorter =
increasingBy .name increasingBy .name
-} -}
increasingBy : (data -> comparable) -> Sorter data increasingBy : (data -> comparable) -> Sorter data
increasingBy toComparable = increasingBy toComparable =
Increasing (List.sortBy toComparable) Increasing (List.sortBy toComparable)
{-| Create a sorter that can only display the data in decreasing order. If we {-| Create a sorter that can only display the data in decreasing order. If we
@ -587,35 +626,38 @@ would say this:
sorter : Sorter { a | population : comparable } sorter : Sorter { a | population : comparable }
sorter = sorter =
decreasingBy .population decreasingBy .population
-} -}
decreasingBy : (data -> comparable) -> Sorter data decreasingBy : (data -> comparable) -> Sorter data
decreasingBy toComparable = decreasingBy toComparable =
Decreasing (List.sortBy toComparable) Decreasing (List.sortBy toComparable)
{-| Sometimes you want to be able to sort data in increasing *or* decreasing {-| Sometimes you want to be able to sort data in increasing _or_ decreasing
order. Maybe you have a bunch of data about orange juice, and you want to know order. Maybe you have a bunch of data about orange juice, and you want to know
both which has the most sugar, and which has the least sugar. Both interesting! both which has the most sugar, and which has the least sugar. Both interesting!
This function lets you see both, starting with decreasing order. This function lets you see both, starting with decreasing order.
sorter : Sorter { a | sugar : comparable } sorter : Sorter { a | sugar : comparable }
sorter = sorter =
decreasingOrIncreasingBy .sugar decreasingOrIncreasingBy .sugar
-} -}
decreasingOrIncreasingBy : (data -> comparable) -> Sorter data decreasingOrIncreasingBy : (data -> comparable) -> Sorter data
decreasingOrIncreasingBy toComparable = decreasingOrIncreasingBy toComparable =
DecOrInc (List.sortBy toComparable) DecOrInc (List.sortBy toComparable)
{-| Sometimes you want to be able to sort data in increasing *or* decreasing {-| Sometimes you want to be able to sort data in increasing _or_ decreasing
order. Maybe you have race times for the 100 meter sprint. This function lets order. Maybe you have race times for the 100 meter sprint. This function lets
sort by best time by default, but also see the other order. sort by best time by default, but also see the other order.
sorter : Sorter { a | time : comparable } sorter : Sorter { a | time : comparable }
sorter = sorter =
increasingOrDecreasingBy .time increasingOrDecreasingBy .time
-} -}
increasingOrDecreasingBy : (data -> comparable) -> Sorter data increasingOrDecreasingBy : (data -> comparable) -> Sorter data
increasingOrDecreasingBy toComparable = increasingOrDecreasingBy toComparable =
IncOrDec (List.sortBy toComparable) IncOrDec (List.sortBy toComparable)