Skip to content

Instantly share code, notes, and snippets.

@mgold
Created February 22, 2016 16:54
Show Gist options
  • Save mgold/eafae5e645f34f5196d7 to your computer and use it in GitHub Desktop.
Save mgold/eafae5e645f34f5196d7 to your computer and use it in GitHub Desktop.
Toggle in Elm

#Toggling Presidents in Elm

from Max Goldstein

This is a simple website in Elm to demonstrate a toggle component. The reusable component is visually attractive and can be customized to a small amount (but a larger API can expand that). The toggle does not require explicit state in the model, and it animates without being passed the current time. However, it very hackily inserts a style tag within the body of the bag.

elm package install --yes
elm reactor

Open Main.elm

{
"version": "1.0.0",
"summary": "helpful summary of your project, less than 80 characters",
"repository": "https://github.com/user/project.git",
"license": "BSD3",
"source-directories": [
"."
],
"exposed-modules": [],
"dependencies": {
"elm-lang/core": "3.0.0 <= v < 4.0.0",
"evancz/elm-effects": "2.0.1 <= v < 3.0.0",
"evancz/elm-html": "4.0.2 <= v < 5.0.0",
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.2 <= v < 3.0.0",
"mgold/elm-date-format": "1.1.2 <= v < 2.0.0"
},
"elm-version": "0.16.0 <= v < 0.17.0"
}
module Main (..) where
import Color
import Date exposing (Date)
import Task exposing (Task)
import Json.Decode
import Html exposing (Html, div, tr, td, th)
import Html.Attributes as Attrs exposing (class)
import Html.Events exposing (onClick, on)
import StartApp exposing (start)
import Effects exposing (Effects, Never)
import Http
import Date.Format
import President exposing (President, decodePresident)
import Toggle
type alias Model =
{ all : List President
, filtered : List President
}
type Action
= NoOp
| Load (List President)
| Filter (President -> Bool)
| Reset
init =
( Model [] [], initialLoad )
initialLoad : Effects Action
initialLoad =
Http.get (Json.Decode.list decodePresident) "presidents.json"
|> Task.toMaybe
|> Task.map (Maybe.map Load >> Maybe.withDefault NoOp)
|> Effects.task
update : Action -> Model -> ( Model, Effects Action )
update action model =
(\m -> ( m, Effects.none ))
<| case action of
NoOp ->
model
Load ps ->
Model ps ps
Filter pred ->
{ model | filtered = List.filter pred model.all }
Reset ->
{ model | filtered = model.all }
view : Signal.Address Action -> Model -> Html
view address { all, filtered } =
div
[]
[ Toggle.toggle
|> Toggle.label "Democrats Only"
|> Toggle.onChange
address
(\b ->
if b then
Filter (\p -> p.party == "Democratic")
else
Reset
)
|> Toggle.id "dems"
|> Toggle.color Color.blue
|> Toggle.render
, Toggle.toggle
|> Toggle.label "Republicans Only"
|> Toggle.onChange
address
(\b ->
if b then
Filter (\p -> p.party == "Republican")
else
Reset
)
|> Toggle.id "reps"
|> Toggle.color Color.red
|> Toggle.render
, table filtered
]
table : List President -> Html
table presidents =
Html.table []
<| tableHead
:: List.map view1 presidents
tableHead : Html
tableHead =
tr []
<| List.map
(\s -> th [] [ Html.text s ])
[ "Number", "Name", "Born", "Died", "Took Office", "Left Office", "Party" ]
view1 : President -> Html
view1 p =
tr []
<| List.map
(\s -> td [] [ Html.text s ])
[ toString p.number
, p.name
, toString p.birthYear
, p.deathYear |> Maybe.map toString |> Maybe.withDefault ""
, formatDate p.tookOffice
, p.leftOffice |> Maybe.map formatDate |> Maybe.withDefault "Incumbent"
, p.party
]
formatDate : Date -> String
formatDate =
Date.Format.format "%b %e, %Y"
app =
start { init = init, view = view, update = update, inputs = [] }
main =
app.html
port tasks : Signal (Task.Task Never ())
port tasks =
app.tasks
module President (President, decodePresident) where
import Date exposing (Date)
import Json.Decode exposing (Decoder, (:=), object7, map, null, oneOf, int, string)
type alias President =
{ number : Int
, name : String
, birthYear : Int
, deathYear : Maybe Int
, tookOffice : Date
, leftOffice : Maybe Date
, party : String
}
date : Decoder Date
date =
Json.Decode.customDecoder string Date.fromString
decodePresident : Decoder President
decodePresident =
object7
President
("number" := int)
("president" := string)
("birth_year" := int)
("death_year" := oneOf [ null Nothing, map Just int ])
("took_office" := date)
("left_office" := oneOf [ null Nothing, map Just date ])
("party" := string)
[
{"number":1,"president":"George Washington","birth_year":1732,"death_year":1799,"took_office":"1789-04-30","left_office":"1797-03-04","party":"No Party"},
{"number":2,"president":"John Adams","birth_year":1735,"death_year":1826,"took_office":"1797-03-04","left_office":"1801-03-04","party":"Federalist"},
{"number":3,"president":"Thomas Jefferson","birth_year":1743,"death_year":1826,"took_office":"1801-03-04","left_office":"1809-03-04","party":"Democratic-Republican"},
{"number":4,"president":"James Madison","birth_year":1751,"death_year":1836,"took_office":"1809-03-04","left_office":"1817-03-04","party":"Democratic-Republican"},
{"number":5,"president":"James Monroe","birth_year":1758,"death_year":1831,"took_office":"1817-03-04","left_office":"1825-03-04","party":"Democratic-Republican"},
{"number":6,"president":"John Quincy Adams","birth_year":1767,"death_year":1848,"took_office":"1825-03-04","left_office":"1829-03-04","party":"Democratic-Republican"},
{"number":7,"president":"Andrew Jackson","birth_year":1767,"death_year":1845,"took_office":"1829-03-04","left_office":"1837-03-04","party":"Democratic"},
{"number":8,"president":"Martin Van Buren","birth_year":1782,"death_year":1862,"took_office":"1837-03-04","left_office":"1841-03-04","party":"Democratic"},
{"number":9,"president":"William Henry Harrison","birth_year":1773,"death_year":1841,"took_office":"1841-03-04","left_office":"1841-04-04","party":"Whig"},
{"number":10,"president":"John Tyler","birth_year":1790,"death_year":1862,"took_office":"1841-04-04","left_office":"1845-03-04","party":"Whig"},
{"number":11,"president":"James K. Polk","birth_year":1795,"death_year":1849,"took_office":"1845-03-04","left_office":"1849-03-04","party":"Democratic"},
{"number":12,"president":"Zachary Taylor","birth_year":1784,"death_year":1850,"took_office":"1849-03-04","left_office":"1850-07-09","party":"Whig"},
{"number":13,"president":"Millard Fillmore","birth_year":1800,"death_year":1874,"took_office":"1850-07-09","left_office":"1853-03-04","party":"Whig"},
{"number":14,"president":"Franklin Pierce","birth_year":1804,"death_year":1869,"took_office":"1853-03-04","left_office":"1857-03-04","party":"Democratic"},
{"number":15,"president":"James Buchanan","birth_year":1791,"death_year":1868,"took_office":"1857-03-04","left_office":"1861-03-04","party":"Democratic"},
{"number":16,"president":"Abraham Lincoln","birth_year":1809,"death_year":1865,"took_office":"1861-03-04","left_office":"1865-04-15","party":"Republican"},
{"number":17,"president":"Andrew Johnson","birth_year":1808,"death_year":1875,"took_office":"1865-04-15","left_office":"1869-03-04","party":"Democratic"},
{"number":18,"president":"Ulysses S. Grant","birth_year":1822,"death_year":1885,"took_office":"1869-03-04","left_office":"1877-03-04","party":"Republican"},
{"number":19,"president":"Rutherford B. Hayes","birth_year":1822,"death_year":1893,"took_office":"1877-03-04","left_office":"1881-03-04","party":"Republican"},
{"number":20,"president":"James A. Garfield","birth_year":1831,"death_year":1881,"took_office":"1881-03-04","left_office":"1881-09-19","party":"Republican"},
{"number":21,"president":"Chester A. Arthur","birth_year":1829,"death_year":1886,"took_office":"1881-09-19","left_office":"1885-03-04","party":"Republican"},
{"number":22,"president":"Grover Cleveland","birth_year":1837,"death_year":1908,"took_office":"1885-03-04","left_office":"1889-03-04","party":"Democratic"},
{"number":23,"president":"Benjamin Harrison","birth_year":1833,"death_year":1901,"took_office":"1889-03-04","left_office":"1893-03-04","party":"Republican"},
{"number":24,"president":"Grover Cleveland","birth_year":1837,"death_year":1908,"took_office":"1893-03-04","left_office":"1897-03-04","party":"Democratic"},
{"number":25,"president":"William McKinley","birth_year":1843,"death_year":1901,"took_office":"1897-03-04","left_office":"1901-09-14","party":"Republican"},
{"number":26,"president":"Theodore Roosevelt","birth_year":1858,"death_year":1919,"took_office":"1901-09-14","left_office":"1909-03-04","party":"Republican"},
{"number":27,"president":"William Howard Taft","birth_year":1857,"death_year":1930,"took_office":"1909-03-04","left_office":"1913-03-04","party":"Republican"},
{"number":28,"president":"Woodrow Wilson","birth_year":1856,"death_year":1924,"took_office":"1913-03-04","left_office":"1921-03-04","party":"Democratic"},
{"number":29,"president":"Warren G. Harding","birth_year":1865,"death_year":1923,"took_office":"1921-03-04","left_office":"1923-08-02","party":"Republican"},
{"number":30,"president":"Calvin Coolidge","birth_year":1872,"death_year":1933,"took_office":"1923-08-02","left_office":"1929-03-04","party":"Republican"},
{"number":31,"president":"Herbert Hoover","birth_year":1874,"death_year":1964,"took_office":"1929-03-04","left_office":"1933-03-04","party":"Republican"},
{"number":32,"president":"Franklin D. Roosevelt","birth_year":1882,"death_year":1945,"took_office":"1933-03-04","left_office":"1945-04-12","party":"Democratic"},
{"number":33,"president":"Harry S. Truman","birth_year":1884,"death_year":1972,"took_office":"1945-04-12","left_office":"1953-01-20","party":"Democratic"},
{"number":34,"president":"Dwight D. Eisenhower","birth_year":1890,"death_year":1969,"took_office":"1953-01-20","left_office":"1961-01-20","party":"Republican"},
{"number":35,"president":"John F. Kennedy","birth_year":1917,"death_year":1963,"took_office":"1961-01-20","left_office":"1963-11-22","party":"Democratic"},
{"number":36,"president":"Lyndon B. Johnson","birth_year":1908,"death_year":1973,"took_office":"1963-11-22","left_office":"1969-01-20","party":"Democratic"},
{"number":37,"president":"Richard Nixon","birth_year":1913,"death_year":1994,"took_office":"1969-01-20","left_office":"1974-08-09","party":"Republican"},
{"number":38,"president":"Gerald Ford","birth_year":1913,"death_year":2006,"took_office":"1974-08-09","left_office":"1977-01-20","party":"Republican"},
{"number":39,"president":"Jimmy Carter","birth_year":1924,"death_year":null,"took_office":"1977-01-20","left_office":"1981-01-20","party":"Democratic"},
{"number":40,"president":"Ronald Reagan","birth_year":1911,"death_year":2004,"took_office":"1981-01-20","left_office":"1989-01-20","party":"Republican"},
{"number":41,"president":"George H. W. Bush","birth_year":1924,"death_year":null,"took_office":"1989-01-20","left_office":"1993-01-20","party":"Republican"},
{"number":42,"president":"Bill Clinton","birth_year":1946,"death_year":null,"took_office":"1993-01-20","left_office":"2001-01-20","party":"Democratic"},
{"number":43,"president":"George W. Bush","birth_year":1946,"death_year":null,"took_office":"2001-01-20","left_office":"2009-01-20","party":"Republican"},
{"number":44,"president":"Barack Obama","birth_year":1961,"death_year":null,"took_office":"2009-01-20","left_office":null,"party":"Democratic"}
]
module Toggle (Toggle, toggle, onChange, label, id, color, status, render) where
{-| Customize a toggle control, then render it to HTML.
This will hopefully be a pattern that can be used by other controls.
# Create a Toggle
@docs Toggle, toggle, render
# Customize a Toggle
@docs onChange, id, label, color, status
-}
import Color exposing (Color)
import Html exposing (Html, Attribute, div, input, p)
import Html.Attributes as Attrs exposing (style)
import Html.Events
type alias Record =
{ label : String, handler : Maybe Attribute, startChecked : Bool, id : String, color : Color }
{-| A model of a toggle component
-}
type Toggle
= T Record
{-| A basic toggle, ready to be customized.
-}
toggle : Toggle
toggle =
T <| Record "" Nothing False "uniqueID" (Color.rgb 66 165 222)
{-| Register a callback for when the toggle changes.
-}
onChange : Signal.Address action -> (Bool -> action) -> Toggle -> Toggle
onChange address handler (T tog) =
T { tog | handler = Just <| Html.Events.on "change" Html.Events.targetChecked (handler >> Signal.message address) }
{-| Text label for the toggle.
-}
label : String -> Toggle -> Toggle
label s (T tog) =
T { tog | label = s }
{-| The id for the toggle. This must be unique for each toggle or events will be routed oddly between toggles.
-}
id : String -> Toggle -> Toggle
id s (T tog) =
T { tog | id = s }
{-| Set the active color for the toggle. Defaults to a shade of blue.
-}
color : Color -> Toggle -> Toggle
color clr (T tog) =
T { tog | color = clr }
{-| Set whether the toggle starts on (`True`) or off (`False`). Default is off. Does not affect the toggle in subsequent
renders. The registered callback, if any, is not called for the initial state.
-}
status : Bool -> Toggle -> Toggle
status b (T tog) =
T { tog | startChecked = b }
{-| Transform the toggle model into HTML.
-}
render : Toggle -> Html
render (T tog) =
div
(tog.handler |> Maybe.map (\x -> x :: topLevelAttrs) |> Maybe.withDefault topLevelAttrs)
[ input (checkboxAttrs tog) []
, Html.label
(labelAttrs tog)
[ div
cellAttrs
[ div knobAttrs [] ]
, p textAttrs [ Html.text tog.label ]
]
, styles tog
]
(=>) =
(,)
singleton x =
[ x ]
topLevelAttrs : List Attribute
topLevelAttrs =
[ Attrs.class "toggle"
, style
[ "position" => "relative"
, "width" => "65px"
, "-webkit-user-select" => "none"
, "-moz-user-select" => "none"
, "-ms-user-select" => " none"
]
]
checkboxAttrs : Record -> List Attribute
checkboxAttrs { id, startChecked } =
[ Attrs.type' "checkbox"
, Attrs.id id
, Attrs.checked startChecked
, style [ "display" => "none" ]
]
labelAttrs : Record -> List Attribute
labelAttrs { id } =
[ Attrs.for id ]
cellAttrs : List Attribute
cellAttrs =
[ Attrs.class "cell"
, style
[ "display" => "block"
, "overflow" => "hidden"
, "cursor" => "pointer"
, "height" => "26px"
, "padding" => "0"
, "line-height" => "26px"
, "border-radius" => "26px"
, "transition" => "background-color 0.3s ease-in"
]
]
knobAttrs : List Attribute
knobAttrs =
[ Attrs.class "knob"
, style
[ "content" => ""
, "display" => "block"
, "width" => "26px"
, "height" => "26px"
, "margin" => "0px"
, "background" => "#FFFFFF"
, "position" => "absolute"
, "top" => "0"
, "bottom" => "0"
, "border-radius" => "26px"
, "transition" => "all 0.3s ease-in 0s"
]
]
{-| This is the hacky part: a style node inserted into body! It's used only for the styles that change when the checkbox
is selected, but because they can't override inline styles, we also have to have the unchecked versions here too.
-}
styles : Record -> Html
styles tog =
let
{ red, green, blue } =
Color.toRgb tog.color
clr =
"rgb(" ++ toString red ++ "," ++ toString green ++ "," ++ toString blue ++ ")"
in
Html.node
"style"
[]
[ Html.text
<| "input#" ++ tog.id ++ " + label .cell { background-color: #FFFFFF; }"
++ "input#" ++ tog.id ++ ":checked + label .cell { background-color: " ++ clr ++ "; }"
++ "input#" ++ tog.id ++ " + label .cell, input + label .knob { border: 2px solid #CCCCCC; }"
++ "input#" ++ tog.id ++ ":checked + label .cell,"
++ "input#" ++ tog.id ++ ":checked + label .knob { border: 2px solid " ++ clr ++ "; }"
++ "input#" ++ tog.id ++ " + label .knob { right: 37px; }"
++ "input#" ++ tog.id ++ ":checked + label .knob { right: 0px; }"
]
textAttrs : List Attribute
textAttrs =
singleton
<| style
[ "height" => "26px"
, "width" => "99999px"
, "margin-left" => "80px"
, "margin-top" => "-26px"
, "font-family" => "sans-serif"
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment