Initial commit with draft API
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
elm-stuff
 | 
			
		||||
							
								
								
									
										30
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
Copyright (c) 2016-present, Evan Czaplicki
 | 
			
		||||
 | 
			
		||||
All rights reserved.
 | 
			
		||||
 | 
			
		||||
Redistribution and use in source and binary forms, with or without
 | 
			
		||||
modification, are permitted provided that the following conditions are met:
 | 
			
		||||
 | 
			
		||||
    * Redistributions of source code must retain the above copyright
 | 
			
		||||
      notice, this list of conditions and the following disclaimer.
 | 
			
		||||
 | 
			
		||||
    * Redistributions in binary form must reproduce the above
 | 
			
		||||
      copyright notice, this list of conditions and the following
 | 
			
		||||
      disclaimer in the documentation and/or other materials provided
 | 
			
		||||
      with the distribution.
 | 
			
		||||
 | 
			
		||||
    * Neither the name of Evan Czaplicki nor the names of other
 | 
			
		||||
      contributors may be used to endorse or promote products derived
 | 
			
		||||
      from this software without specific prior written permission.
 | 
			
		||||
 | 
			
		||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | 
			
		||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 | 
			
		||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 | 
			
		||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 | 
			
		||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | 
			
		||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 | 
			
		||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 | 
			
		||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 | 
			
		||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | 
			
		||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 | 
			
		||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
							
								
								
									
										17
									
								
								elm-package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								elm-package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
{
 | 
			
		||||
    "version": "1.0.0",
 | 
			
		||||
    "summary": "Sortable tables for data of any shape.",
 | 
			
		||||
    "repository": "https://github.com/evancz/elm-table.git",
 | 
			
		||||
    "license": "BSD3",
 | 
			
		||||
    "source-directories": [
 | 
			
		||||
        "src"
 | 
			
		||||
    ],
 | 
			
		||||
    "exposed-modules": [
 | 
			
		||||
        "Table"
 | 
			
		||||
    ],
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "elm-lang/core": "4.0.0 <= v < 5.0.0",
 | 
			
		||||
        "elm-lang/html": "1.1.0 <= v < 2.0.0"
 | 
			
		||||
    },
 | 
			
		||||
    "elm-version": "0.17.0 <= v < 0.18.0"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										131
									
								
								examples/Presidents.elm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								examples/Presidents.elm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
import Html exposing (Html, div, h1, text)
 | 
			
		||||
import Html.App as App
 | 
			
		||||
import Table
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
main =
 | 
			
		||||
  App.program
 | 
			
		||||
    { init = init presidents
 | 
			
		||||
    , update = update
 | 
			
		||||
    , view = view
 | 
			
		||||
    , subscriptions = \_ -> Sub.none
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- MODEL
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type alias Model =
 | 
			
		||||
  { people : List Person
 | 
			
		||||
  , tableState : Table.State
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
init : List Person -> ( Model, Cmd Msg )
 | 
			
		||||
init people =
 | 
			
		||||
  ( Model people (Table.ascending "Year")
 | 
			
		||||
  , Cmd.none
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- UPDATE
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type Msg
 | 
			
		||||
  = UpdateTableState Table.State
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
update : Msg -> Model -> ( Model, Cmd Msg )
 | 
			
		||||
update msg model =
 | 
			
		||||
  case msg of
 | 
			
		||||
    UpdateTableState newState ->
 | 
			
		||||
      ( Model model.people newState, Cmd.none )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- VIEW
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
view : Model -> Html Msg
 | 
			
		||||
view {people, tableState} =
 | 
			
		||||
  div []
 | 
			
		||||
    [ h1 [] [ text "Birthplaces of U.S. Presidents" ]
 | 
			
		||||
    , Table.view config tableState people
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
config : Table.Config Person Msg
 | 
			
		||||
config =
 | 
			
		||||
  Table.config
 | 
			
		||||
    { toId = .name
 | 
			
		||||
    , toMsg = UpdateTableState
 | 
			
		||||
    , columns =
 | 
			
		||||
        [ Table.stringColumn "Name" .name
 | 
			
		||||
        , Table.intColumn "Year" .year
 | 
			
		||||
        , Table.stringColumn "City" .city
 | 
			
		||||
        , Table.stringColumn "State" .state
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- PEOPLE
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type alias Person =
 | 
			
		||||
  { name : String
 | 
			
		||||
  , year : Int
 | 
			
		||||
  , city : String
 | 
			
		||||
  , state : String
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
presidents : List Person
 | 
			
		||||
presidents =
 | 
			
		||||
  [ Person "George Washington" 1732 "Westmoreland County" "Virginia"
 | 
			
		||||
  , Person "John Adams" 1735 "Braintree" "Massachusetts"
 | 
			
		||||
  , Person "Thomas Jefferson" 1743 "Shadwell" "Virginia"
 | 
			
		||||
  , Person "James Madison" 1751 "Port Conway" "Virginia"
 | 
			
		||||
  , Person "James Monroe" 1758 "Monroe Hall" "Virginia"
 | 
			
		||||
  , Person "Andrew Jackson" 1767 "Waxhaws Region" "South/North Carolina"
 | 
			
		||||
  , Person "John Quincy Adams" 1767 "Braintree" "Massachusetts"
 | 
			
		||||
  , Person "William Henry Harrison" 1773 "Charles City County" "Virginia"
 | 
			
		||||
  , Person "Martin Van Buren" 1782 "Kinderhook" "New York"
 | 
			
		||||
  , Person "Zachary Taylor" 1784 "Barboursville" "Virginia"
 | 
			
		||||
  , Person "John Tyler" 1790 "Charles City County" "Virginia"
 | 
			
		||||
  , Person "James Buchanan" 1791 "Cove Gap" "Pennsylvania"
 | 
			
		||||
  , Person "James K. Polk" 1795 "Pineville" "North Carolina"
 | 
			
		||||
  , Person "Millard Fillmore" 1800 "Summerhill" "New York"
 | 
			
		||||
  , Person "Franklin Pierce" 1804 "Hillsborough" "New Hampshire"
 | 
			
		||||
  , Person "Andrew Johnson" 1808 "Raleigh" "North Carolina"
 | 
			
		||||
  , Person "Abraham Lincoln" 1809 "Sinking spring" "Kentucky"
 | 
			
		||||
  , Person "Ulysses S. Grant" 1822 "Point Pleasant" "Ohio"
 | 
			
		||||
  , Person "Rutherford B. Hayes" 1822 "Delaware" "Ohio"
 | 
			
		||||
  , Person "Chester A. Arthur" 1829 "Fairfield" "Vermont"
 | 
			
		||||
  , Person "James A. Garfield" 1831 "Moreland Hills" "Ohio"
 | 
			
		||||
  , Person "Benjamin Harrison" 1833 "North Bend" "Ohio"
 | 
			
		||||
  , Person "Grover Cleveland" 1837 "Caldwell" "New Jersey"
 | 
			
		||||
  , Person "William McKinley" 1843 "Niles" "Ohio"
 | 
			
		||||
  , Person "Woodrow Wilson" 1856 "Staunton" "Virginia"
 | 
			
		||||
  , Person "William Howard Taft" 1857 "Cincinnati" "Ohio"
 | 
			
		||||
  , Person "Theodore Roosevelt" 1858 "New York City" "New York"
 | 
			
		||||
  , Person "Warren G. Harding" 1865 "Blooming Grove" "Ohio"
 | 
			
		||||
  , Person "Calvin Coolidge" 1872 "Plymouth" "Vermont"
 | 
			
		||||
  , Person "Herbert Hoover" 1874 "West Branch" "Iowa"
 | 
			
		||||
  , Person "Franklin D. Roosevelt" 1882 "Hyde Park" "New York"
 | 
			
		||||
  , Person "Harry S. Truman" 1884 "Lamar" "Missouri"
 | 
			
		||||
  , Person "Dwight D. Eisenhower" 1890 "Denison" "Texas"
 | 
			
		||||
  , Person "Lyndon B. Johnson" 1908 "Stonewall" "Texas"
 | 
			
		||||
  , Person "Ronald Reagan" 1911 "Tampico" "Illinois"
 | 
			
		||||
  , Person "Richard M. Nixon" 1913 "Yorba Linda" "California"
 | 
			
		||||
  , Person "Gerald R. Ford" 1913 "Omaha" "Nebraska"
 | 
			
		||||
  , Person "John F. Kennedy" 1917 "Brookline" "Massachusetts"
 | 
			
		||||
  , Person "George H. W. Bush" 1924 "Milton" "Massachusetts"
 | 
			
		||||
  , Person "Jimmy Carter" 1924 "Plains" "Georgia"
 | 
			
		||||
  , Person "George W. Bush" 1946 "New Haven" "Connecticut"
 | 
			
		||||
  , Person "Bill Clinton" 1946 "Hope" "Arkansas"
 | 
			
		||||
  , Person "Barack Obama" 1961 "Honolulu" "Hawaii"
 | 
			
		||||
  ]
 | 
			
		||||
							
								
								
									
										591
									
								
								src/Table.elm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										591
									
								
								src/Table.elm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,591 @@
 | 
			
		||||
module Table exposing
 | 
			
		||||
  ( view
 | 
			
		||||
  , State, initialSort
 | 
			
		||||
  , Config, config
 | 
			
		||||
  , Column, stringColumn, intColumn, floatColumn, column
 | 
			
		||||
  , Sorter, unsortable, increasingBy, decreasingBy
 | 
			
		||||
  , increasingOrDecreasingBy, decreasingOrIncreasingBy
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
{-|
 | 
			
		||||
 | 
			
		||||
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-tables/tree/master/examples
 | 
			
		||||
 | 
			
		||||
# View
 | 
			
		||||
@docs view
 | 
			
		||||
 | 
			
		||||
# Configuration
 | 
			
		||||
@docs config, stringColumn, intColumn, floatColumn
 | 
			
		||||
 | 
			
		||||
# State
 | 
			
		||||
@docs State, initialSort
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Crazy Customization
 | 
			
		||||
 | 
			
		||||
If you are new to this library, you can probably stop reading here. After this
 | 
			
		||||
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,
 | 
			
		||||
  Sorter, unsortable, increasingBy, decreasingBy,
 | 
			
		||||
  increasingOrDecreasingBy, decreasingOrIncreasingBy
 | 
			
		||||
 | 
			
		||||
## Custom Tables
 | 
			
		||||
 | 
			
		||||
@docs Config, customConfig,
 | 
			
		||||
  Customizations, HtmlDetails, Status, defaultCustomizations
 | 
			
		||||
-}
 | 
			
		||||
 | 
			
		||||
import Html exposing (Html, Attribute)
 | 
			
		||||
import Html.Attributes as Attr
 | 
			
		||||
import Html.Events as E
 | 
			
		||||
import Html.Keyed as Keyed
 | 
			
		||||
import Html.Lazy exposing (lazy2, lazy3)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- STATE
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Tracks which column to sort by.
 | 
			
		||||
-}
 | 
			
		||||
type State =
 | 
			
		||||
  State String Bool
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Create a table state. By providing a column name, you determine which
 | 
			
		||||
column should be used for sorting by default. So if you want your table of
 | 
			
		||||
yachts to be sorted by length by default, you might say:
 | 
			
		||||
 | 
			
		||||
    import Table
 | 
			
		||||
 | 
			
		||||
    Table.initialSort "Length"
 | 
			
		||||
-}
 | 
			
		||||
initialSort : String -> State
 | 
			
		||||
initialSort header =
 | 
			
		||||
  State header False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- CONFIG
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Configuration for your table, describing your columns.
 | 
			
		||||
 | 
			
		||||
**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 (Column data msg)
 | 
			
		||||
    , customizations : Customizations data msg
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Create the `Config` for your `view` function. Everything you need to
 | 
			
		||||
render your columns efficiently and handle selection of columns.
 | 
			
		||||
 | 
			
		||||
Say we have a `List Person` that we want to show as a table. The table should
 | 
			
		||||
have a column for name and age. We would create a `Config` like this:
 | 
			
		||||
 | 
			
		||||
    import Table
 | 
			
		||||
 | 
			
		||||
    type Msg = NewTableState State | ...
 | 
			
		||||
 | 
			
		||||
    config : Table.Config Person Msg
 | 
			
		||||
    config =
 | 
			
		||||
      Table.config
 | 
			
		||||
        { toId = .name
 | 
			
		||||
        , toMsg = NewTableState
 | 
			
		||||
        , columns =
 | 
			
		||||
            [ Table.stringColumn "Name" .name
 | 
			
		||||
            , Table.intColumn "Age" .age
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
  - `columns` — specify some columns to show.
 | 
			
		||||
  - `toMsg` — a way send new table states to your app as messages.
 | 
			
		||||
 | 
			
		||||
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-tables/tree/master/examples
 | 
			
		||||
-}
 | 
			
		||||
config
 | 
			
		||||
  : { toId : data -> String
 | 
			
		||||
    , toMsg : State -> msg
 | 
			
		||||
    , columns : List (Column data msg)
 | 
			
		||||
    }
 | 
			
		||||
  -> Config data msg
 | 
			
		||||
config { toId, toMsg, columns } =
 | 
			
		||||
  Config
 | 
			
		||||
    { toId = toId
 | 
			
		||||
    , toMsg = toMsg
 | 
			
		||||
    , columns = List.map (\(Column cData) -> cData) columns
 | 
			
		||||
    , customizations = defaultCustomizations
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
customConfig
 | 
			
		||||
  : { toId : data -> String
 | 
			
		||||
    , toMsg : State -> msg
 | 
			
		||||
    , columns : List (Column data msg)
 | 
			
		||||
    , customizations : Customizations data msg
 | 
			
		||||
    }
 | 
			
		||||
  -> Config data msg
 | 
			
		||||
customConfig { toId, toMsg, columns, 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
 | 
			
		||||
a `<caption>` which can be styled via CSS. You can do crazy stuff with
 | 
			
		||||
`<thead>` to group columns in weird ways. You can have a `<tfoot>` tag for
 | 
			
		||||
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
 | 
			
		||||
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 Cusomizations 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)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Sometimes you must use a `<td>` tag, but the attributes and children are up
 | 
			
		||||
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)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
defaultCustomizations : Customizations data msg
 | 
			
		||||
defaultCustomizations =
 | 
			
		||||
  { tableAttrs = []
 | 
			
		||||
  , caption = Nothing
 | 
			
		||||
  , thead = simpleThead
 | 
			
		||||
  , tfoot = Nothing
 | 
			
		||||
  , tbodyAttrs = []
 | 
			
		||||
  , rowAttrs = simpleRowAttrs
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
simpleThead : List (String, Status, Attribute msg) -> HtmlDetails msg
 | 
			
		||||
simpleThead headers =
 | 
			
		||||
  HtmlDetails [] (List.map simpleTheadHelp headers)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
simpleTheadHelp : ( String, Status, Attribute msg ) -> Html msg
 | 
			
		||||
simpleTheadHelp (name, status, onClick) =
 | 
			
		||||
  let
 | 
			
		||||
    content =
 | 
			
		||||
      case status of
 | 
			
		||||
        Unsortable ->
 | 
			
		||||
          [ text name ]
 | 
			
		||||
 | 
			
		||||
        Sortable selected ->
 | 
			
		||||
          [ text name
 | 
			
		||||
          , if selected then darkGrey "↓" else lightGrey "↓"
 | 
			
		||||
          ]
 | 
			
		||||
 | 
			
		||||
        Reversable Nothing ->
 | 
			
		||||
          [ text name
 | 
			
		||||
          , lightGrey "↕"
 | 
			
		||||
          ]
 | 
			
		||||
 | 
			
		||||
        Reversable (Just isReversed) ->
 | 
			
		||||
          [ text name
 | 
			
		||||
          , darkGrey (if isReversed then "↑" else "↓")
 | 
			
		||||
          ]
 | 
			
		||||
  in
 | 
			
		||||
    Html.th [ onClick ] content
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
darkGrey : String -> Html msg
 | 
			
		||||
darkGrey symbol =
 | 
			
		||||
  Html.span [ Attr.style [("color", "#ccc")] ] [ Html.text (" " ++ symbol) ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
lightGrey : String -> Html msg
 | 
			
		||||
lightGrey symbol =
 | 
			
		||||
  Html.span [ Attr.style [("color", "#999")] ] [ Html.text (" " ++ symbol) ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
simpleRowAttrs : data -> Attribute msg
 | 
			
		||||
simpleRowAttrs _ =
 | 
			
		||||
  []
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type Status
 | 
			
		||||
  = Unsortable
 | 
			
		||||
  | Sortable Bool
 | 
			
		||||
  | Reversable (Maybe Bool)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- COLUMNS
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Describes how to turn `data` into a column in your table.
 | 
			
		||||
-}
 | 
			
		||||
type Column data msg =
 | 
			
		||||
  Column (ColumnData data msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type alias ColumnData data msg =
 | 
			
		||||
  { 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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-|-}
 | 
			
		||||
intColumn : String -> (data -> Int) -> Column data msg
 | 
			
		||||
intColumn name 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 << toString << toFloat
 | 
			
		||||
    , sorter = increasingOrDecreasingBy toFloat
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
textDetails : String -> HtmlDetails msg
 | 
			
		||||
textDetails str =
 | 
			
		||||
  HtmlDetails [] [ Html.text str ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Perhaps the basic columns are not quite what you want. Maybe you want to
 | 
			
		||||
display monetary values in thousands of dollars, and `floatColumn` does not
 | 
			
		||||
quite cut it. You could define a custom column like this:
 | 
			
		||||
 | 
			
		||||
    import Table
 | 
			
		||||
 | 
			
		||||
    dollarColumn : String -> (data -> Float) -> Column data msg
 | 
			
		||||
    dollarColumn name toDollars =
 | 
			
		||||
      Table.customColumn
 | 
			
		||||
        { name = name
 | 
			
		||||
        , viewData = \data -> viewDollars (toDollars data)
 | 
			
		||||
        , sorter = Table.decreasingBy toDollars
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    viewDollars : Float -> String
 | 
			
		||||
    viewDollars dollars =
 | 
			
		||||
      "$" ++ 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.
 | 
			
		||||
More about sorters soon!
 | 
			
		||||
-}
 | 
			
		||||
customColumn
 | 
			
		||||
  : { name : String
 | 
			
		||||
    , viewData : data -> String
 | 
			
		||||
    , sorter : Sorter data
 | 
			
		||||
    }
 | 
			
		||||
  -> Column data msg
 | 
			
		||||
customColumn { name, viewData, sorter } =
 | 
			
		||||
  Column <|
 | 
			
		||||
    ColumnData name (textDetails << viewData) sorter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| 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 (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
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    viewDollars : Float -> Table.HtmlDetails msg
 | 
			
		||||
    viewDollars dollars =
 | 
			
		||||
      Table.HtmlDetails []
 | 
			
		||||
        [ span [ style [("color","green")] ] [ text "$" ]
 | 
			
		||||
        , text (toString (round (dollars / 1000)) ++ "k")
 | 
			
		||||
        ]
 | 
			
		||||
-}
 | 
			
		||||
veryCustomColumn
 | 
			
		||||
  : { name : String
 | 
			
		||||
    , viewData : data -> HtmlDetails msg
 | 
			
		||||
    , sorter : Sorter data
 | 
			
		||||
    }
 | 
			
		||||
  -> Column data msg
 | 
			
		||||
veryCustomColumn =
 | 
			
		||||
  Column
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- VIEW
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Take a list of data and turn it into a table. The `Config` argument is the
 | 
			
		||||
configuration for the table. It describes the columns that we want to show. The
 | 
			
		||||
`State` argument describes which column we are sorting by at the moment.
 | 
			
		||||
 | 
			
		||||
**Note:** The `State` and `List data` should live in your `Model`. The `Config`
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
    theadDetails =
 | 
			
		||||
      customizations.thead (List.map Debug.crash columns)
 | 
			
		||||
 | 
			
		||||
    thead =
 | 
			
		||||
      Html.thead theadDetails.attributes theadDetails.children
 | 
			
		||||
 | 
			
		||||
    tbody =
 | 
			
		||||
      List.map (viewRow toId columns) sortedData
 | 
			
		||||
 | 
			
		||||
    withFoot =
 | 
			
		||||
      case customizations.tfoot of
 | 
			
		||||
        Nothing ->
 | 
			
		||||
          tbody
 | 
			
		||||
 | 
			
		||||
        Just { attributes, children } ->
 | 
			
		||||
          Html.tfoot attributes children :: tbody
 | 
			
		||||
  in
 | 
			
		||||
    Html.table customizations.tableAttrs <|
 | 
			
		||||
      case customizations.caption of
 | 
			
		||||
        Nothing ->
 | 
			
		||||
          thead :: withFoot
 | 
			
		||||
 | 
			
		||||
        Just { attributes, children } ->
 | 
			
		||||
          Html.caption attributes children :: thead :: withFoot
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
viewHeader : List (ColumnData a msg) -> (State -> msg) -> State -> Html msg
 | 
			
		||||
viewHeader columnData toMsg state =
 | 
			
		||||
  Html.tr [] (List.map (lazy3 viewHeaderHelp toMsg state) columnData)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
viewHeaderHelp : (State -> msg) -> State -> ColumnData a msg -> Html msg
 | 
			
		||||
viewHeaderHelp toMsg state ({name} as column) =
 | 
			
		||||
  let
 | 
			
		||||
 | 
			
		||||
  Html.th
 | 
			
		||||
    [ class (String.join " " classes)
 | 
			
		||||
    , onClick name state toMsg
 | 
			
		||||
    ]
 | 
			
		||||
    [ html
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
onClick : String -> State -> (State -> msg) -> Attribute msg
 | 
			
		||||
onClick name (State selectedColumn isReversed) toMsg =
 | 
			
		||||
  E.on "click" <| Json.map toMsg <|
 | 
			
		||||
    Json.object2
 | 
			
		||||
      State
 | 
			
		||||
      (Json.succed name)
 | 
			
		||||
      (Json.succed (name == selectedColumn && not isReversed)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
descendingClass : Attribute msg
 | 
			
		||||
descendingClass =
 | 
			
		||||
  class "elm-table-selected elm-table-descending"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ascendingClass : Attribute msg
 | 
			
		||||
ascendingClass =
 | 
			
		||||
  class "elm-table-selected elm-table-ascending"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
viewRow : (a -> String) -> List (ColumnData a msg) -> a -> ( String, Html msg )
 | 
			
		||||
viewRow toId columnData entry =
 | 
			
		||||
  ( toId entry
 | 
			
		||||
  , lazy2 viewRowHelp columnData entry
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
viewRowHelp : List (ColumnData a msg) -> a -> Html msg
 | 
			
		||||
viewRowHelp columnData entry =
 | 
			
		||||
  Html.tr [] (List.map (\{toCell} -> Html.td [] [ toCell entry ]) columnData)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- SORTING
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sort : State -> List (ColumnData data msg) -> List data -> List data
 | 
			
		||||
sort (State selectedColumn isReversed) columnData data =
 | 
			
		||||
  case findSorter selectedColumn columnData of
 | 
			
		||||
    Nothing ->
 | 
			
		||||
      data
 | 
			
		||||
 | 
			
		||||
    Just sorter ->
 | 
			
		||||
      applySorter isReversed sorter data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
applySorter : Bool -> Sorter data -> List data -> List data
 | 
			
		||||
applySorter isReversed sorter data =
 | 
			
		||||
  case sorter of
 | 
			
		||||
    None ->
 | 
			
		||||
      data
 | 
			
		||||
 | 
			
		||||
    Increasing sort ->
 | 
			
		||||
      sort data
 | 
			
		||||
 | 
			
		||||
    Decreasing sort ->
 | 
			
		||||
      List.reverse (sort data)
 | 
			
		||||
 | 
			
		||||
    IncOrDec sort ->
 | 
			
		||||
      if isReversed then List.reverse (sort data) else 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
 | 
			
		||||
 | 
			
		||||
    {name, sorter} :: remainingColumnData ->
 | 
			
		||||
      if name == selectedColumn then
 | 
			
		||||
        sorter
 | 
			
		||||
      else
 | 
			
		||||
        findSorter selectedColumn remainingColumnData
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- SORTERS
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| 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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| A sorter for columns that are unsortable. Maybe you have a column in your
 | 
			
		||||
table for delete buttons that delete the row. It would not make any sense to
 | 
			
		||||
sort based on that column.
 | 
			
		||||
-}
 | 
			
		||||
unsortable : Sorter data
 | 
			
		||||
unsortable =
 | 
			
		||||
  None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Create a sorter that can only display the data in increasing order. If we
 | 
			
		||||
want a table of people, sorted alphabetically by name, we would say this:
 | 
			
		||||
 | 
			
		||||
    sorter : Sorter { a | name : comparable }
 | 
			
		||||
    sorter =
 | 
			
		||||
      increasingBy .name
 | 
			
		||||
-}
 | 
			
		||||
increasingBy : (data -> comparable) -> Sorter data
 | 
			
		||||
increasingBy toComparable =
 | 
			
		||||
  Increasing (List.sortBy toComparable)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Create a sorter that can only display the data in decreasing order. If we
 | 
			
		||||
want a table of countries, sorted by population from highest to lowest, we
 | 
			
		||||
would say this:
 | 
			
		||||
 | 
			
		||||
    sorter : Sorter { a | population : comparable }
 | 
			
		||||
    sorter =
 | 
			
		||||
      decreasingBy .population
 | 
			
		||||
-}
 | 
			
		||||
decreasingBy : (data -> comparable) -> Sorter data
 | 
			
		||||
decreasingBy toComparable =
 | 
			
		||||
  Decreasing (List.sortBy toComparable)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| 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 : (data -> comparable) -> Sorter data
 | 
			
		||||
decreasingOrIncreasingBy toComparable =
 | 
			
		||||
  DecOrInc (List.sortBy toComparable)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| 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 : (data -> comparable) -> Sorter data
 | 
			
		||||
increasingOrDecreasingBy toComparable =
 | 
			
		||||
  IncOrDec (List.sortBy toComparable)
 | 
			
		||||
		Reference in New Issue
	
	Block a user