Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
Evan Czaplicki | 5953926588 | |
Evan Czaplicki | 8405daff6a |
|
@ -1 +0,0 @@
|
|||
elm 0.19.1
|
|
@ -7,8 +7,8 @@ This library also lets you customize `<caption>`, `<tbody>`, `<tr>`, etc. for yo
|
|||
|
||||
## Examples
|
||||
|
||||
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](https://evancz.github.io/elm-sortable-table/travel.html) / [Code](https://github.com/evancz/elm-sortable-table/blob/master/examples/2-travel.elm)
|
||||
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)
|
||||
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)
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
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 “get stuck” and display out of date information.
|
||||
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 “get stuck” and display out of date information.
|
||||
|
||||
To make this more clear, let’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?
|
||||
|
||||
|
@ -56,4 +56,4 @@ I designed this library to have a very smooth learning curve. As you read the do
|
|||
|
||||
The trick is that all these simple functions are defined in terms of crazier ones that allow for more customization. As the user **NEEDS** that complexity, they can read on and gradually use the parts that are relevant to them. This means the user never finds themselves in a situation where they have to learn a bunch of stuff that does not actually matter to them. At the same time, that stuff is there when they need it.
|
||||
|
||||
To turn this into advice about API design, **helper functions can make a library simpler to learn and use.** Ultimately, people may not use `Table.floatColumn` very often in real stuff, but that function is crucial for learning. So when you find yourself with a tough API, one way to ramp people up is to create specialized helper functions that let you get common functionality without confronting people with all the details.
|
||||
To turn this into advice about API design, **helper functions can make a library simpler to learn and use.** Ultimately, people may not use `Table.floatColumn` very often in real stuff, but that function is crucial for learning. So when you find yourself with a tough API, one way to ramp people up is to create specialized helper functions that let you get common functionality without confronting people with all the details.
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.0",
|
||||
"summary": "Sortable tables for data of any shape.",
|
||||
"repository": "https://github.com/evancz/elm-sortable-table.git",
|
||||
"repository": "https://github.com/evancz/elm-table.git",
|
||||
"license": "BSD3",
|
||||
"source-directories": [
|
||||
"src"
|
||||
|
@ -10,8 +10,8 @@
|
|||
"Table"
|
||||
],
|
||||
"dependencies": {
|
||||
"elm-lang/core": "5.0.0 <= v < 6.0.0",
|
||||
"elm-lang/html": "2.0.0 <= v < 3.0.0"
|
||||
"elm-lang/core": "4.0.0 <= v < 5.0.0",
|
||||
"elm-lang/html": "1.1.0 <= v < 2.0.0"
|
||||
},
|
||||
"elm-version": "0.18.0 <= v < 0.19.0"
|
||||
"elm-version": "0.17.0 <= v < 0.18.0"
|
||||
}
|
||||
|
|
17
elm.json
17
elm.json
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"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": {}
|
||||
}
|
|
@ -1,12 +1,14 @@
|
|||
import Html exposing (Html, div, h1, input, text)
|
||||
import Html.App as App
|
||||
import Html.Attributes exposing (placeholder)
|
||||
import Html.Events exposing (onInput)
|
||||
import String
|
||||
import Table
|
||||
|
||||
|
||||
|
||||
main =
|
||||
Html.program
|
||||
App.program
|
||||
{ init = init presidents
|
||||
, update = update
|
||||
, view = view
|
||||
|
@ -151,5 +153,4 @@ presidents =
|
|||
, Person "George W. Bush" 1946 "New Haven" "Connecticut"
|
||||
, Person "Bill Clinton" 1946 "Hope" "Arkansas"
|
||||
, Person "Barack Obama" 1961 "Honolulu" "Hawaii"
|
||||
, Person "Donald Trump" 1946 "New York City" "New York"
|
||||
]
|
||||
]
|
|
@ -1,14 +1,16 @@
|
|||
import Html exposing (Html, Attribute, div, h1, input, p, text)
|
||||
import Html.Attributes exposing (checked, style, type_)
|
||||
import Html.App as App
|
||||
import Html.Attributes exposing (checked, style, type')
|
||||
import Html.Events exposing (onClick)
|
||||
import Html.Lazy exposing (lazy)
|
||||
import String
|
||||
import Table exposing (defaultCustomizations)
|
||||
import Time exposing (Time)
|
||||
|
||||
|
||||
|
||||
main =
|
||||
Html.program
|
||||
App.program
|
||||
{ init = init missionSights
|
||||
, update = update
|
||||
, view = view
|
||||
|
@ -169,7 +171,7 @@ checkboxColumn =
|
|||
viewCheckbox : Sight -> Table.HtmlDetails Msg
|
||||
viewCheckbox {selected} =
|
||||
Table.HtmlDetails []
|
||||
[ input [ type_ "checkbox", checked selected ] []
|
||||
[ input [ type' "checkbox", checked selected ] []
|
||||
]
|
||||
|
||||
|
||||
|
@ -196,4 +198,4 @@ missionSights =
|
|||
, Sight "Get catcalled at BART" (5 * Time.minute) 0 1.6 False
|
||||
, Sight "Buy a painting at \"Stuff\"" (45 * Time.minute) 400 4.7 False
|
||||
, Sight "McDonalds at 24th" (20 * Time.minute) 5 2.8 False
|
||||
]
|
||||
]
|
|
@ -1,7 +1,7 @@
|
|||
# Examples
|
||||
|
||||
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](https://evancz.github.io/elm-sortable-table/travel.html)
|
||||
1. [U.S. Presidents by Birth Place](http://evancz.github.io/elm-sortable-tables/presidents.html)
|
||||
2. [Travel Planner for the Mission District in San Francisco](http://evancz.github.io/elm-sortable-tables/travel.html)
|
||||
|
||||
|
||||
## Build Instructions
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.0",
|
||||
"summary": "helpful summary of your project, less than 80 characters",
|
||||
"repository": "https://github.com/user/project.git",
|
||||
"license": "BSD3",
|
||||
|
@ -8,9 +8,9 @@
|
|||
],
|
||||
"exposed-modules": [],
|
||||
"dependencies": {
|
||||
"elm-lang/core": "5.0.0 <= v < 6.0.0",
|
||||
"elm-lang/html": "2.0.0 <= v < 3.0.0",
|
||||
"evancz/elm-sortable-table": "1.0.1 <= v < 2.0.0"
|
||||
"elm-lang/core": "4.0.3 <= v < 5.0.0",
|
||||
"elm-lang/html": "1.1.0 <= v < 2.0.0",
|
||||
"evancz/elm-sortable-table": "1.0.0 <= v < 2.0.0"
|
||||
},
|
||||
"elm-version": "0.18.0 <= v < 0.19.0"
|
||||
"elm-version": "0.17.0 <= v < 0.18.0"
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
530
src/Table.elm
530
src/Table.elm
|
@ -1,35 +1,34 @@
|
|||
module Table exposing
|
||||
( view
|
||||
, config, stringColumn, intColumn, floatColumn
|
||||
, State, initialSort, initialRevSort
|
||||
, Column, customColumn, veryCustomColumn
|
||||
, Sorter, unsortable, increasingBy, decreasingBy
|
||||
, increasingOrDecreasingBy, decreasingOrIncreasingBy
|
||||
, Config, customConfig, Customizations, HtmlDetails, Status(..)
|
||||
, defaultCustomizations
|
||||
)
|
||||
( view
|
||||
, config, stringColumn, intColumn, floatColumn
|
||||
, State, initialSort
|
||||
, Column, customColumn, veryCustomColumn
|
||||
, Sorter, unsortable, increasingBy, decreasingBy
|
||||
, increasingOrDecreasingBy, decreasingOrIncreasingBy
|
||||
, Config, customConfig
|
||||
, Customizations, HtmlDetails, Status(..), 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
|
||||
you. This way you are free to change your data without worrying about the table
|
||||
“getting out of sync” with the data. Having a single source of
|
||||
truth is pretty great!
|
||||
|
||||
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
|
||||
I recommend checking out the [examples][] to get a feel for how it works.
|
||||
|
||||
[examples]: https://github.com/evancz/elm-tables/tree/master/examples
|
||||
|
||||
# View
|
||||
|
||||
@docs view
|
||||
|
||||
|
||||
# Configuration
|
||||
|
||||
@docs config, stringColumn, intColumn, floatColumn
|
||||
|
||||
|
||||
# State
|
||||
|
||||
@docs State, initialSort
|
||||
|
@ -42,22 +41,19 @@ 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
|
||||
is not that crazy.
|
||||
|
||||
|
||||
## Custom Columns
|
||||
|
||||
@docs Column, customColumn, veryCustomColumn
|
||||
@docs Sorter, unsortable, increasingBy, decreasingBy
|
||||
@docs increasingOrDecreasingBy, decreasingOrIncreasingBy
|
||||
|
||||
@docs Column, customColumn, veryCustomColumn,
|
||||
Sorter, unsortable, increasingBy, decreasingBy,
|
||||
increasingOrDecreasingBy, decreasingOrIncreasingBy
|
||||
|
||||
## Custom Tables
|
||||
|
||||
@docs Config, customConfig, Customizations, HtmlDetails, Status
|
||||
@docs defaultCustomizations
|
||||
|
||||
@docs Config, customConfig, Customizations, HtmlDetails, Status,
|
||||
defaultCustomizations
|
||||
-}
|
||||
|
||||
import Html exposing (Attribute, Html)
|
||||
import Html exposing (Html, Attribute)
|
||||
import Html.Attributes as Attr
|
||||
import Html.Events as E
|
||||
import Html.Keyed as Keyed
|
||||
|
@ -71,8 +67,8 @@ import Json.Decode as Json
|
|||
|
||||
{-| Tracks which column to sort by.
|
||||
-}
|
||||
type State
|
||||
= State String Bool
|
||||
type State =
|
||||
State String Bool
|
||||
|
||||
|
||||
{-| Create a table state. By providing a column name, you determine which
|
||||
|
@ -82,15 +78,10 @@ yachts to be sorted by length by default, you might say:
|
|||
import Table
|
||||
|
||||
Table.initialSort "Length"
|
||||
|
||||
-}
|
||||
initialSort : String -> State
|
||||
initialSort header =
|
||||
State header False
|
||||
|
||||
initialRevSort : String -> State
|
||||
initialRevSort header =
|
||||
State header True
|
||||
State header False
|
||||
|
||||
|
||||
|
||||
|
@ -99,17 +90,16 @@ initialRevSort header =
|
|||
|
||||
{-| 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.
|
||||
|
||||
-}
|
||||
type Config data msg
|
||||
= Config
|
||||
{ toId : data -> String
|
||||
, toMsg : State -> msg
|
||||
, columns : List (ColumnData data msg)
|
||||
, customizations : Customizations data msg
|
||||
}
|
||||
type Config data msg =
|
||||
Config
|
||||
{ toId : data -> String
|
||||
, toMsg : State -> msg
|
||||
, columns : List (ColumnData data msg)
|
||||
, customizations : Customizations data msg
|
||||
}
|
||||
|
||||
|
||||
{-| Create the `Config` for your `view` function. Everything you need to
|
||||
|
@ -136,47 +126,46 @@ have a column for name and age. We would create a `Config` like this:
|
|||
You provide the following information in your table configuration:
|
||||
|
||||
- `toId` — 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` — specify some columns to show.
|
||||
- `toMsg` — a way to send new table states to your app as messages.
|
||||
- `toMsg` — a way 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
|
||||
[examples]: https://github.com/evancz/elm-sortable-table/tree/master/examples
|
||||
|
||||
[examples]: https://github.com/evancz/elm-tables/tree/master/examples
|
||||
-}
|
||||
config :
|
||||
{ toId : data -> String
|
||||
config
|
||||
: { toId : data -> String
|
||||
, toMsg : State -> msg
|
||||
, columns : List (Column data msg)
|
||||
}
|
||||
-> Config data msg
|
||||
-> Config data msg
|
||||
config { toId, toMsg, columns } =
|
||||
Config
|
||||
{ toId = toId
|
||||
, toMsg = toMsg
|
||||
, columns = List.map (\(Column cData) -> cData) columns
|
||||
, customizations = defaultCustomizations
|
||||
}
|
||||
Config
|
||||
{ toId = toId
|
||||
, toMsg = toMsg
|
||||
, columns = List.map (\(Column cData) -> cData) columns
|
||||
, customizations = defaultCustomizations
|
||||
}
|
||||
|
||||
|
||||
{-| Just like `config` but you can specify a bunch of table customizations.
|
||||
-}
|
||||
customConfig :
|
||||
{ toId : data -> String
|
||||
customConfig
|
||||
: { toId : data -> String
|
||||
, toMsg : State -> msg
|
||||
, columns : List (Column data msg)
|
||||
, customizations : Customizations data msg
|
||||
}
|
||||
-> Config data msg
|
||||
-> Config data msg
|
||||
customConfig { toId, toMsg, columns, customizations } =
|
||||
Config
|
||||
{ toId = toId
|
||||
, toMsg = toMsg
|
||||
, columns = List.map (\(Column cData) -> cData) columns
|
||||
, customizations = customizations
|
||||
}
|
||||
Config
|
||||
{ toId = toId
|
||||
, toMsg = toMsg
|
||||
, columns = List.map (\(Column cData) -> cData) columns
|
||||
, customizations = customizations
|
||||
}
|
||||
|
||||
|
||||
{-| There are quite a lot of ways to customize the `<table>` tag. You can add
|
||||
|
@ -186,19 +175,18 @@ 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.
|
||||
|
||||
**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
|
||||
you have. Stories make it possible to design better!
|
||||
|
||||
-}
|
||||
type alias Customizations data msg =
|
||||
{ tableAttrs : List (Attribute msg)
|
||||
, caption : Maybe (HtmlDetails msg)
|
||||
, thead : List ( String, Status, Attribute msg ) -> HtmlDetails msg
|
||||
, tfoot : Maybe (HtmlDetails msg)
|
||||
, tbodyAttrs : List (Attribute msg)
|
||||
, rowAttrs : data -> List (Attribute msg)
|
||||
}
|
||||
{ tableAttrs : List (Attribute msg)
|
||||
, caption : Maybe (HtmlDetails msg)
|
||||
, thead : List (String, Status, Attribute msg) -> HtmlDetails msg
|
||||
, tfoot : Maybe (HtmlDetails msg)
|
||||
, tbodyAttrs : List (Attribute msg)
|
||||
, rowAttrs : data -> List (Attribute msg)
|
||||
}
|
||||
|
||||
|
||||
{-| Sometimes you must use a `<td>` tag, but the attributes and children are up
|
||||
|
@ -206,78 +194,68 @@ to you. This type lets you specify all the details of an HTML node except the
|
|||
tag name.
|
||||
-}
|
||||
type alias HtmlDetails msg =
|
||||
{ attributes : List (Attribute msg)
|
||||
, children : List (Html msg)
|
||||
}
|
||||
{ attributes : List (Attribute msg)
|
||||
, children : List (Html msg)
|
||||
}
|
||||
|
||||
|
||||
{-| The customizations used in `config` by default.
|
||||
-}
|
||||
defaultCustomizations : Customizations data msg
|
||||
defaultCustomizations =
|
||||
{ tableAttrs = []
|
||||
, caption = Nothing
|
||||
, thead = simpleThead
|
||||
, tfoot = Nothing
|
||||
, tbodyAttrs = []
|
||||
, rowAttrs = simpleRowAttrs
|
||||
}
|
||||
{ tableAttrs = []
|
||||
, caption = Nothing
|
||||
, thead = simpleThead
|
||||
, tfoot = Nothing
|
||||
, tbodyAttrs = []
|
||||
, rowAttrs = simpleRowAttrs
|
||||
}
|
||||
|
||||
|
||||
simpleThead : List ( String, Status, Attribute msg ) -> HtmlDetails msg
|
||||
simpleThead : List (String, Status, Attribute msg) -> HtmlDetails msg
|
||||
simpleThead headers =
|
||||
HtmlDetails [] (List.map simpleTheadHelp headers)
|
||||
HtmlDetails [] (List.map simpleTheadHelp headers)
|
||||
|
||||
|
||||
simpleTheadHelp : ( String, Status, Attribute msg ) -> Html msg
|
||||
simpleTheadHelp ( name, status, onClick_ ) =
|
||||
let
|
||||
content =
|
||||
case status of
|
||||
Unsortable ->
|
||||
[ Html.text name ]
|
||||
simpleTheadHelp (name, status, onClick) =
|
||||
let
|
||||
content =
|
||||
case status of
|
||||
Unsortable ->
|
||||
[ Html.text name ]
|
||||
|
||||
Sortable selected ->
|
||||
[ Html.text name
|
||||
, if selected then
|
||||
darkGrey "↓"
|
||||
Sortable selected ->
|
||||
[ Html.text name
|
||||
, if selected then darkGrey "↓" else lightGrey "↓"
|
||||
]
|
||||
|
||||
else
|
||||
lightGrey "↓"
|
||||
]
|
||||
Reversible Nothing ->
|
||||
[ Html.text name
|
||||
, lightGrey "↕"
|
||||
]
|
||||
|
||||
Reversible Nothing ->
|
||||
[ Html.text name
|
||||
, lightGrey "↕"
|
||||
]
|
||||
|
||||
Reversible (Just isReversed) ->
|
||||
[ Html.text name
|
||||
, darkGrey
|
||||
(if isReversed then
|
||||
"↑"
|
||||
|
||||
else
|
||||
"↓"
|
||||
)
|
||||
]
|
||||
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 symbol =
|
||||
Html.span [ Attr.style "color" "#555" ] [ Html.text (" " ++ symbol) ]
|
||||
Html.span [ Attr.style [("color", "#555")] ] [ Html.text (" " ++ symbol) ]
|
||||
|
||||
|
||||
lightGrey : String -> Html msg
|
||||
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 _ =
|
||||
[]
|
||||
[]
|
||||
|
||||
|
||||
{-| The status of a particular column, for use in the `thead` field of your
|
||||
|
@ -294,12 +272,11 @@ simpleRowAttrs _ =
|
|||
is sorted.
|
||||
|
||||
This information lets you do custom header decorations for each scenario.
|
||||
|
||||
-}
|
||||
type Status
|
||||
= Unsortable
|
||||
| Sortable Bool
|
||||
| Reversible (Maybe Bool)
|
||||
= Unsortable
|
||||
| Sortable Bool
|
||||
| Reversible (Maybe Bool)
|
||||
|
||||
|
||||
|
||||
|
@ -308,50 +285,50 @@ type Status
|
|||
|
||||
{-| Describes how to turn `data` into a column in your table.
|
||||
-}
|
||||
type Column data msg
|
||||
= Column (ColumnData data msg)
|
||||
type Column data msg =
|
||||
Column (ColumnData data msg)
|
||||
|
||||
|
||||
type alias ColumnData data msg =
|
||||
{ name : String
|
||||
, viewData : data -> HtmlDetails msg
|
||||
, sorter : Sorter data
|
||||
{ name : String
|
||||
, viewData : data -> HtmlDetails msg
|
||||
, sorter : Sorter data
|
||||
}
|
||||
|
||||
|
||||
{-|-}
|
||||
stringColumn : String -> (data -> String) -> Column data msg
|
||||
stringColumn name toStr =
|
||||
Column
|
||||
{ name = name
|
||||
, viewData = textDetails << toStr
|
||||
, sorter = increasingOrDecreasingBy toStr
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
stringColumn : String -> (data -> String) -> Column data msg
|
||||
stringColumn name toStr =
|
||||
Column
|
||||
{ name = name
|
||||
, viewData = textDetails << toStr
|
||||
, sorter = increasingOrDecreasingBy toStr
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
{-|-}
|
||||
intColumn : String -> (data -> Int) -> Column data msg
|
||||
intColumn name toInt =
|
||||
Column
|
||||
{ name = name
|
||||
, viewData = textDetails << String.fromInt << toInt
|
||||
, sorter = increasingOrDecreasingBy toInt
|
||||
}
|
||||
Column
|
||||
{ name = name
|
||||
, viewData = textDetails << toString << toInt
|
||||
, sorter = increasingOrDecreasingBy toInt
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
{-|-}
|
||||
floatColumn : String -> (data -> Float) -> Column data msg
|
||||
floatColumn name toFloat =
|
||||
Column
|
||||
{ name = name
|
||||
, viewData = textDetails << String.fromFloat << toFloat
|
||||
, sorter = increasingOrDecreasingBy toFloat
|
||||
}
|
||||
Column
|
||||
{ name = name
|
||||
, viewData = textDetails << toString << toFloat
|
||||
, sorter = increasingOrDecreasingBy toFloat
|
||||
}
|
||||
|
||||
|
||||
textDetails : String -> HtmlDetails msg
|
||||
textDetails str =
|
||||
HtmlDetails [] [ Html.text str ]
|
||||
HtmlDetails [] [ Html.text str ]
|
||||
|
||||
|
||||
{-| Perhaps the basic columns are not quite what you want. Maybe you want to
|
||||
|
@ -362,68 +339,66 @@ quite cut it. You could define a custom column like this:
|
|||
|
||||
dollarColumn : String -> (data -> Float) -> Column data msg
|
||||
dollarColumn name toDollars =
|
||||
Table.customColumn
|
||||
{ name = name
|
||||
, viewData = \data -> viewDollars (toDollars data)
|
||||
, sorter = Table.decreasingBy toDollars
|
||||
}
|
||||
Table.customColumn
|
||||
{ name = name
|
||||
, viewData = \data -> viewDollars (toDollars data)
|
||||
, sorter = Table.decreasingBy toDollars
|
||||
}
|
||||
|
||||
viewDollars : Float -> String
|
||||
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 `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!
|
||||
|
||||
-}
|
||||
customColumn :
|
||||
{ name : String
|
||||
customColumn
|
||||
: { name : String
|
||||
, viewData : data -> String
|
||||
, sorter : Sorter data
|
||||
}
|
||||
-> Column data msg
|
||||
-> Column data msg
|
||||
customColumn { name, viewData, sorter } =
|
||||
Column <|
|
||||
ColumnData name (textDetails << viewData) sorter
|
||||
Column <|
|
||||
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
|
||||
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.
|
||||
|
||||
import Html exposing (Attribute, Html, span, text)
|
||||
import Html exposing (Html, Attribute, span, text)
|
||||
import Html.Attributes exposing (style)
|
||||
import Table
|
||||
|
||||
dollarColumn : String -> (data -> Float) -> Column data msg
|
||||
dollarColumn name toDollars =
|
||||
Table.veryCustomColumn
|
||||
{ name = name
|
||||
, viewData = \data -> viewDollars (toDollars data)
|
||||
, sorter = Table.decreasingBy toDollars
|
||||
}
|
||||
Table.veryCustomColumn
|
||||
{ name = name
|
||||
, viewData = \data -> viewDollars (toDollars data)
|
||||
, sorter = Table.decreasingBy toDollars
|
||||
}
|
||||
|
||||
viewDollars : Float -> Table.HtmlDetails msg
|
||||
viewDollars dollars =
|
||||
Table.HtmlDetails []
|
||||
[ span [ style [ ( "color", "green" ) ] ] [ text "$" ]
|
||||
, text (toString (round (dollars / 1000)) ++ "k")
|
||||
]
|
||||
|
||||
Table.HtmlDetails []
|
||||
[ span [ style [("color","green")] ] [ text "$" ]
|
||||
, text (toString (round (dollars / 1000)) ++ "k")
|
||||
]
|
||||
-}
|
||||
veryCustomColumn :
|
||||
{ name : String
|
||||
veryCustomColumn
|
||||
: { name : String
|
||||
, viewData : data -> HtmlDetails msg
|
||||
, sorter : Sorter data
|
||||
}
|
||||
-> Column data msg
|
||||
-> Column data msg
|
||||
veryCustomColumn =
|
||||
Column
|
||||
Column
|
||||
|
||||
|
||||
|
||||
|
@ -439,93 +414,89 @@ for the table belongs in your `view` code. I very strongly recommend against
|
|||
putting `Config` in your model. Describe any potential table configurations
|
||||
statically, and look for a different library if you need something crazier than
|
||||
that.
|
||||
|
||||
-}
|
||||
view : Config data msg -> State -> List data -> Html msg
|
||||
view (Config { toId, toMsg, columns, customizations }) state data =
|
||||
let
|
||||
sortedData =
|
||||
sort state columns data
|
||||
let
|
||||
sortedData =
|
||||
sort state columns data
|
||||
|
||||
theadDetails =
|
||||
customizations.thead (List.map (toHeaderInfo state toMsg) columns)
|
||||
theadDetails =
|
||||
customizations.thead (List.map (toHeaderInfo state toMsg) columns)
|
||||
|
||||
thead =
|
||||
Html.thead theadDetails.attributes theadDetails.children
|
||||
thead =
|
||||
Html.thead theadDetails.attributes theadDetails.children
|
||||
|
||||
tbody =
|
||||
Keyed.node "tbody" customizations.tbodyAttrs <|
|
||||
List.map (viewRow toId columns customizations.rowAttrs) sortedData
|
||||
tbody =
|
||||
Keyed.node "tbody" customizations.tbodyAttrs <|
|
||||
List.map (viewRow toId columns customizations.rowAttrs) sortedData
|
||||
|
||||
withFoot =
|
||||
case customizations.tfoot of
|
||||
Nothing ->
|
||||
tbody :: []
|
||||
withFoot =
|
||||
case customizations.tfoot of
|
||||
Nothing ->
|
||||
tbody :: []
|
||||
|
||||
Just { attributes, children } ->
|
||||
Html.tfoot attributes children :: tbody :: []
|
||||
in
|
||||
Just { attributes, children } ->
|
||||
Html.tfoot attributes children :: tbody :: []
|
||||
in
|
||||
Html.table customizations.tableAttrs <|
|
||||
case customizations.caption of
|
||||
Nothing ->
|
||||
thead :: withFoot
|
||||
case customizations.caption of
|
||||
Nothing ->
|
||||
thead :: withFoot
|
||||
|
||||
Just { attributes, children } ->
|
||||
Html.caption attributes children :: thead :: withFoot
|
||||
Just { attributes, children } ->
|
||||
Html.caption attributes children :: thead :: withFoot
|
||||
|
||||
|
||||
toHeaderInfo : State -> (State -> msg) -> ColumnData data msg -> ( String, Status, Attribute msg )
|
||||
toHeaderInfo (State sortName isReversed) toMsg { name, sorter } =
|
||||
case sorter of
|
||||
None ->
|
||||
( name, Unsortable, onClick sortName isReversed toMsg )
|
||||
case sorter of
|
||||
None ->
|
||||
( name, Unsortable, onClick sortName isReversed toMsg )
|
||||
|
||||
Increasing _ ->
|
||||
( name, Sortable (name == sortName), onClick name False toMsg )
|
||||
Increasing _ ->
|
||||
( name, Sortable (name == sortName), onClick name False toMsg )
|
||||
|
||||
Decreasing _ ->
|
||||
( name, Sortable (name == sortName), onClick name False toMsg )
|
||||
Decreasing _ ->
|
||||
( name, Sortable (name == sortName), onClick name False toMsg )
|
||||
|
||||
IncOrDec _ ->
|
||||
if name == sortName then
|
||||
( name, Reversible (Just isReversed), onClick name (not isReversed) toMsg )
|
||||
IncOrDec _ ->
|
||||
if name == sortName then
|
||||
( name, Reversible (Just isReversed), onClick name (not isReversed) toMsg )
|
||||
else
|
||||
( name, Reversible Nothing, onClick name False toMsg )
|
||||
|
||||
else
|
||||
( name, Reversible Nothing, onClick name False toMsg )
|
||||
|
||||
DecOrInc _ ->
|
||||
if name == sortName then
|
||||
( name, Reversible (Just isReversed), onClick name (not isReversed) toMsg )
|
||||
|
||||
else
|
||||
( name, Reversible Nothing, onClick name False toMsg )
|
||||
DecOrInc _ ->
|
||||
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 name isReversed toMsg =
|
||||
E.on "click" <|
|
||||
Json.map toMsg <|
|
||||
Json.map2 State (Json.succeed name) (Json.succeed isReversed)
|
||||
E.on "click" <| Json.map toMsg <|
|
||||
Json.object2 State (Json.succeed name) (Json.succeed isReversed)
|
||||
|
||||
|
||||
viewRow : (data -> String) -> List (ColumnData data msg) -> (data -> List (Attribute msg)) -> data -> ( String, Html msg )
|
||||
viewRow toId columns toRowAttrs data =
|
||||
( toId data
|
||||
, lazy3 viewRowHelp columns toRowAttrs data
|
||||
)
|
||||
( toId data
|
||||
, lazy3 viewRowHelp columns toRowAttrs data
|
||||
)
|
||||
|
||||
|
||||
viewRowHelp : List (ColumnData data msg) -> (data -> List (Attribute msg)) -> data -> Html msg
|
||||
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 { viewData } =
|
||||
let
|
||||
details =
|
||||
viewData data
|
||||
in
|
||||
viewCell data {viewData} =
|
||||
let
|
||||
details =
|
||||
viewData data
|
||||
in
|
||||
Html.td details.attributes details.children
|
||||
|
||||
|
||||
|
@ -535,53 +506,44 @@ viewCell data { viewData } =
|
|||
|
||||
sort : State -> List (ColumnData data msg) -> List data -> List data
|
||||
sort (State selectedColumn isReversed) columnData data =
|
||||
case findSorter selectedColumn columnData of
|
||||
Nothing ->
|
||||
data
|
||||
case findSorter selectedColumn columnData of
|
||||
Nothing ->
|
||||
data
|
||||
|
||||
Just sorter ->
|
||||
applySorter isReversed sorter data
|
||||
Just sorter ->
|
||||
applySorter isReversed sorter data
|
||||
|
||||
|
||||
applySorter : Bool -> Sorter data -> List data -> List data
|
||||
applySorter isReversed sorter data =
|
||||
case sorter of
|
||||
None ->
|
||||
data
|
||||
case sorter of
|
||||
None ->
|
||||
data
|
||||
|
||||
Increasing sort_ ->
|
||||
sort_ data
|
||||
Increasing sort ->
|
||||
sort data
|
||||
|
||||
Decreasing sort_ ->
|
||||
List.reverse (sort_ data)
|
||||
Decreasing sort ->
|
||||
List.reverse (sort data)
|
||||
|
||||
IncOrDec sort_ ->
|
||||
if isReversed then
|
||||
List.reverse (sort_ data)
|
||||
IncOrDec sort ->
|
||||
if isReversed then List.reverse (sort data) else sort data
|
||||
|
||||
else
|
||||
sort_ data
|
||||
|
||||
DecOrInc sort_ ->
|
||||
if isReversed then
|
||||
sort_ data
|
||||
|
||||
else
|
||||
List.reverse (sort_ data)
|
||||
DecOrInc sort ->
|
||||
if isReversed then sort data else List.reverse (sort data)
|
||||
|
||||
|
||||
findSorter : String -> List (ColumnData data msg) -> Maybe (Sorter data)
|
||||
findSorter selectedColumn columnData =
|
||||
case columnData of
|
||||
[] ->
|
||||
Nothing
|
||||
case columnData of
|
||||
[] ->
|
||||
Nothing
|
||||
|
||||
{ name, sorter } :: remainingColumnData ->
|
||||
if name == selectedColumn then
|
||||
Just sorter
|
||||
|
||||
else
|
||||
findSorter selectedColumn remainingColumnData
|
||||
{name, sorter} :: remainingColumnData ->
|
||||
if name == selectedColumn then
|
||||
Just sorter
|
||||
else
|
||||
findSorter selectedColumn remainingColumnData
|
||||
|
||||
|
||||
|
||||
|
@ -591,11 +553,11 @@ findSorter selectedColumn columnData =
|
|||
{-| Specifies a particular way of sorting data.
|
||||
-}
|
||||
type Sorter data
|
||||
= None
|
||||
| Increasing (List data -> List data)
|
||||
| Decreasing (List data -> List data)
|
||||
| IncOrDec (List data -> List data)
|
||||
| DecOrInc (List data -> List data)
|
||||
= None
|
||||
| Increasing (List data -> List data)
|
||||
| Decreasing (List data -> List data)
|
||||
| IncOrDec (List data -> List data)
|
||||
| DecOrInc (List data -> List data)
|
||||
|
||||
|
||||
{-| A sorter for columns that are unsortable. Maybe you have a column in your
|
||||
|
@ -604,7 +566,7 @@ sort based on that column.
|
|||
-}
|
||||
unsortable : Sorter data
|
||||
unsortable =
|
||||
None
|
||||
None
|
||||
|
||||
|
||||
{-| Create a sorter that can only display the data in increasing order. If we
|
||||
|
@ -612,12 +574,11 @@ want a table of people, sorted alphabetically by name, we would say this:
|
|||
|
||||
sorter : Sorter { a | name : comparable }
|
||||
sorter =
|
||||
increasingBy .name
|
||||
|
||||
increasingBy .name
|
||||
-}
|
||||
increasingBy : (data -> comparable) -> Sorter data
|
||||
increasingBy toComparable =
|
||||
Increasing (List.sortBy toComparable)
|
||||
Increasing (List.sortBy toComparable)
|
||||
|
||||
|
||||
{-| Create a sorter that can only display the data in decreasing order. If we
|
||||
|
@ -626,38 +587,35 @@ would say this:
|
|||
|
||||
sorter : Sorter { a | population : comparable }
|
||||
sorter =
|
||||
decreasingBy .population
|
||||
|
||||
decreasingBy .population
|
||||
-}
|
||||
decreasingBy : (data -> comparable) -> Sorter data
|
||||
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
|
||||
both which has the most sugar, and which has the least sugar. Both interesting!
|
||||
This function lets you see both, starting with decreasing order.
|
||||
|
||||
sorter : Sorter { a | sugar : comparable }
|
||||
sorter =
|
||||
decreasingOrIncreasingBy .sugar
|
||||
|
||||
decreasingOrIncreasingBy .sugar
|
||||
-}
|
||||
decreasingOrIncreasingBy : (data -> comparable) -> Sorter data
|
||||
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
|
||||
sort by best time by default, but also see the other order.
|
||||
|
||||
sorter : Sorter { a | time : comparable }
|
||||
sorter =
|
||||
increasingOrDecreasingBy .time
|
||||
|
||||
increasingOrDecreasingBy .time
|
||||
-}
|
||||
increasingOrDecreasingBy : (data -> comparable) -> Sorter data
|
||||
increasingOrDecreasingBy toComparable =
|
||||
IncOrDec (List.sortBy toComparable)
|
||||
IncOrDec (List.sortBy toComparable)
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue