Bump part3 and part4
@@ -1,16 +0,0 @@
|
|||||||
# Part 3
|
|
||||||
|
|
||||||
To build everything, `cd` into this `part3/` directory and run:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
elm make src/Main.elm --output=../server/public/elm.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Then open [http://localhost:3000](http://localhost:3000) in your browser.
|
|
||||||
|
|
||||||
## Exercise
|
|
||||||
|
|
||||||
Resolve the TODOs in these files:
|
|
||||||
* `src/Page/Home.elm`
|
|
||||||
* `src/Page/Settings.elm`
|
|
||||||
* `src/Page/Article.elm`
|
|
||||||
@@ -10,4 +10,7 @@ Then open [http://localhost:3000](http://localhost:3000) in your browser.
|
|||||||
|
|
||||||
## Exercise
|
## Exercise
|
||||||
|
|
||||||
Open `src/Article/Feed.elm` in your editor and resolve the TODO there.
|
Resolve the TODOs in these files:
|
||||||
|
* `src/Page/Home.elm`
|
||||||
|
* `src/Page/Settings.elm`
|
||||||
|
* `src/Page/Article.elm`
|
||||||
|
|||||||
@@ -110,50 +110,11 @@ viewArticles timeZone (Model { articles, sources, session }) =
|
|||||||
FeedSources.selected sources
|
FeedSources.selected sources
|
||||||
|
|
||||||
pagination =
|
pagination =
|
||||||
viewPaginatedList articles (limit feedSource)
|
PaginatedList.view ClickedFeedPage articles (limit feedSource)
|
||||||
in
|
in
|
||||||
List.append articlesHtml [ pagination ]
|
List.append articlesHtml [ pagination ]
|
||||||
|
|
||||||
|
|
||||||
{-| 👉 TODO Move this logic into PaginatedList.view and make it reusable,
|
|
||||||
so we can use it on other pages too!
|
|
||||||
💡 HINT: Make `PaginatedList.view` return `Html msg` instead of `Html Msg`. (The function will need to accept an extra argument for this to work.)
|
|
||||||
-}
|
|
||||||
viewPaginatedList : PaginatedList a -> Int -> Html Msg
|
|
||||||
viewPaginatedList paginatedList resultsPerPage =
|
|
||||||
let
|
|
||||||
totalPages =
|
|
||||||
ceiling (toFloat (PaginatedList.total paginatedList) / toFloat resultsPerPage)
|
|
||||||
|
|
||||||
activePage =
|
|
||||||
PaginatedList.page paginatedList
|
|
||||||
|
|
||||||
viewPageLink currentPage =
|
|
||||||
pageLink currentPage (currentPage == activePage)
|
|
||||||
in
|
|
||||||
if totalPages > 1 then
|
|
||||||
List.range 1 totalPages
|
|
||||||
|> List.map viewPageLink
|
|
||||||
|> ul [ class "pagination" ]
|
|
||||||
|
|
||||||
else
|
|
||||||
Html.text ""
|
|
||||||
|
|
||||||
|
|
||||||
pageLink : Int -> Bool -> Html Msg
|
|
||||||
pageLink targetPage isActive =
|
|
||||||
li [ classList [ ( "page-item", True ), ( "active", isActive ) ] ]
|
|
||||||
[ a
|
|
||||||
[ class "page-link"
|
|
||||||
, onClick (ClickedFeedPage targetPage)
|
|
||||||
|
|
||||||
-- The RealWorld CSS requires an href to work properly.
|
|
||||||
, href ""
|
|
||||||
]
|
|
||||||
[ text (String.fromInt targetPage) ]
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
viewPreview : Maybe Cred -> Time.Zone -> Article Preview -> Html Msg
|
viewPreview : Maybe Cred -> Time.Zone -> Article Preview -> Html Msg
|
||||||
viewPreview maybeCred timeZone article =
|
viewPreview maybeCred timeZone article =
|
||||||
let
|
let
|
||||||
|
|||||||
@@ -84,19 +84,8 @@ view model =
|
|||||||
[ viewBanner
|
[ viewBanner
|
||||||
, div [ class "container page" ]
|
, div [ class "container page" ]
|
||||||
[ div [ class "row" ]
|
[ div [ class "row" ]
|
||||||
[ div [ class "col-md-9" ] <|
|
[ div [ class "col-md-9" ]
|
||||||
case model.feed of
|
(viewFeed model)
|
||||||
Loaded feed ->
|
|
||||||
viewFeed model.timeZone feed
|
|
||||||
|
|
||||||
Loading ->
|
|
||||||
[]
|
|
||||||
|
|
||||||
LoadingSlowly ->
|
|
||||||
[ Loading.icon ]
|
|
||||||
|
|
||||||
Failed ->
|
|
||||||
[ Loading.error "feed" ]
|
|
||||||
, div [ class "col-md-3" ] <|
|
, div [ class "col-md-3" ] <|
|
||||||
case model.tags of
|
case model.tags of
|
||||||
Loaded tags ->
|
Loaded tags ->
|
||||||
@@ -130,11 +119,25 @@ viewBanner =
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
viewFeed : Time.Zone -> Feed.Model -> List (Html Msg)
|
{-| 👉 TODO refactor this to accept narrower types than the entire Model.
|
||||||
viewFeed timeZone feed =
|
💡 HINT: It may end up with multiple arguments!
|
||||||
div [ class "feed-toggle" ]
|
-}
|
||||||
[ Feed.viewFeedSources feed |> Html.map GotFeedMsg ]
|
viewFeed : Model -> List (Html Msg)
|
||||||
:: (Feed.viewArticles timeZone feed |> List.map (Html.map GotFeedMsg))
|
viewFeed model =
|
||||||
|
case model.feed of
|
||||||
|
Loaded feed ->
|
||||||
|
div [ class "feed-toggle" ]
|
||||||
|
[ Feed.viewFeedSources feed |> Html.map GotFeedMsg ]
|
||||||
|
:: (Feed.viewArticles model.timeZone feed |> List.map (Html.map GotFeedMsg))
|
||||||
|
|
||||||
|
Loading ->
|
||||||
|
[]
|
||||||
|
|
||||||
|
LoadingSlowly ->
|
||||||
|
[ Loading.icon ]
|
||||||
|
|
||||||
|
Failed ->
|
||||||
|
[ Loading.error "feed" ]
|
||||||
|
|
||||||
|
|
||||||
viewTags : List Tag -> Html Msg
|
viewTags : List Tag -> Html Msg
|
||||||
|
|||||||
@@ -98,6 +98,10 @@ type ValidForm
|
|||||||
|
|
||||||
view : Model -> { title : String, content : Html Msg }
|
view : Model -> { title : String, content : Html Msg }
|
||||||
view model =
|
view model =
|
||||||
|
let
|
||||||
|
form =
|
||||||
|
viewForm model
|
||||||
|
in
|
||||||
{ title = "Settings"
|
{ title = "Settings"
|
||||||
, content =
|
, content =
|
||||||
case Session.cred model.session of
|
case Session.cred model.session of
|
||||||
@@ -109,7 +113,7 @@ view model =
|
|||||||
[ h1 [ class "text-xs-center" ] [ text "Your Settings" ]
|
[ h1 [ class "text-xs-center" ] [ text "Your Settings" ]
|
||||||
, ul [ class "error-messages" ]
|
, ul [ class "error-messages" ]
|
||||||
(List.map viewProblem model.problems)
|
(List.map viewProblem model.problems)
|
||||||
, viewForm cred model.form
|
, form
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@@ -120,62 +124,74 @@ view model =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
viewForm : Cred -> Form -> Html Msg
|
{-| 👉 TODO refactor this to accept narrower types than the entire Model.
|
||||||
viewForm cred form =
|
💡 HINT: It may end up with multiple arguments!
|
||||||
Html.form [ onSubmit (SubmittedForm cred) ]
|
-}
|
||||||
[ fieldset []
|
viewForm : Model -> Html Msg
|
||||||
[ fieldset [ class "form-group" ]
|
viewForm model =
|
||||||
[ input
|
let
|
||||||
[ class "form-control"
|
form =
|
||||||
, placeholder "URL of profile picture"
|
model.form
|
||||||
, value form.avatar
|
in
|
||||||
, onInput EnteredAvatar
|
case Session.cred model.session of
|
||||||
|
Nothing ->
|
||||||
|
text ""
|
||||||
|
|
||||||
|
Just cred ->
|
||||||
|
Html.form [ onSubmit (SubmittedForm cred) ]
|
||||||
|
[ fieldset []
|
||||||
|
[ fieldset [ class "form-group" ]
|
||||||
|
[ input
|
||||||
|
[ class "form-control"
|
||||||
|
, placeholder "URL of profile picture"
|
||||||
|
, value form.avatar
|
||||||
|
, onInput EnteredAvatar
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, fieldset [ class "form-group" ]
|
||||||
|
[ input
|
||||||
|
[ class "form-control form-control-lg"
|
||||||
|
, placeholder "Username"
|
||||||
|
, value form.username
|
||||||
|
, onInput EnteredUsername
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, fieldset [ class "form-group" ]
|
||||||
|
[ textarea
|
||||||
|
[ class "form-control form-control-lg"
|
||||||
|
, placeholder "Short bio about you"
|
||||||
|
, attribute "rows" "8"
|
||||||
|
, value form.bio
|
||||||
|
, onInput EnteredBio
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, fieldset [ class "form-group" ]
|
||||||
|
[ input
|
||||||
|
[ class "form-control form-control-lg"
|
||||||
|
, placeholder "Email"
|
||||||
|
, value form.email
|
||||||
|
, onInput EnteredEmail
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, fieldset [ class "form-group" ]
|
||||||
|
[ input
|
||||||
|
[ class "form-control form-control-lg"
|
||||||
|
, type_ "password"
|
||||||
|
, placeholder "Password"
|
||||||
|
, value form.password
|
||||||
|
, onInput EnteredPassword
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, button
|
||||||
|
[ class "btn btn-lg btn-primary pull-xs-right" ]
|
||||||
|
[ text "Update Settings" ]
|
||||||
]
|
]
|
||||||
[]
|
|
||||||
]
|
]
|
||||||
, fieldset [ class "form-group" ]
|
|
||||||
[ input
|
|
||||||
[ class "form-control form-control-lg"
|
|
||||||
, placeholder "Username"
|
|
||||||
, value form.username
|
|
||||||
, onInput EnteredUsername
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
, fieldset [ class "form-group" ]
|
|
||||||
[ textarea
|
|
||||||
[ class "form-control form-control-lg"
|
|
||||||
, placeholder "Short bio about you"
|
|
||||||
, attribute "rows" "8"
|
|
||||||
, value form.bio
|
|
||||||
, onInput EnteredBio
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
, fieldset [ class "form-group" ]
|
|
||||||
[ input
|
|
||||||
[ class "form-control form-control-lg"
|
|
||||||
, placeholder "Email"
|
|
||||||
, value form.email
|
|
||||||
, onInput EnteredEmail
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
, fieldset [ class "form-group" ]
|
|
||||||
[ input
|
|
||||||
[ class "form-control form-control-lg"
|
|
||||||
, type_ "password"
|
|
||||||
, placeholder "Password"
|
|
||||||
, value form.password
|
|
||||||
, onInput EnteredPassword
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
, button
|
|
||||||
[ class "btn btn-lg btn-primary pull-xs-right" ]
|
|
||||||
[ text "Update Settings" ]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
viewProblem : Problem -> Html msg
|
viewProblem : Problem -> Html msg
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
module PaginatedList exposing (PaginatedList, fromList, map, mapPage, page, total, values)
|
module PaginatedList exposing (PaginatedList, fromList, map, mapPage, page, total, values, view)
|
||||||
|
|
||||||
import Html exposing (Html, a, li, text, ul)
|
import Html exposing (Html, a, li, text, ul)
|
||||||
import Html.Attributes exposing (class, classList, href)
|
import Html.Attributes exposing (class, classList, href)
|
||||||
@@ -61,3 +61,38 @@ mapPage transform (PaginatedList info) =
|
|||||||
|
|
||||||
|
|
||||||
-- VIEW
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view : (Int -> msg) -> PaginatedList a -> Int -> Html msg
|
||||||
|
view toMsg list resultsPerPage =
|
||||||
|
let
|
||||||
|
totalPages =
|
||||||
|
ceiling (toFloat (total list) / toFloat resultsPerPage)
|
||||||
|
|
||||||
|
activePage =
|
||||||
|
page list
|
||||||
|
|
||||||
|
viewPageLink currentPage =
|
||||||
|
pageLink toMsg currentPage (currentPage == activePage)
|
||||||
|
in
|
||||||
|
if totalPages > 1 then
|
||||||
|
List.range 1 totalPages
|
||||||
|
|> List.map viewPageLink
|
||||||
|
|> ul [ class "pagination" ]
|
||||||
|
|
||||||
|
else
|
||||||
|
Html.text ""
|
||||||
|
|
||||||
|
|
||||||
|
pageLink : (Int -> msg) -> Int -> Bool -> Html msg
|
||||||
|
pageLink toMsg targetPage isActive =
|
||||||
|
li [ classList [ ( "page-item", True ), ( "active", isActive ) ] ]
|
||||||
|
[ a
|
||||||
|
[ class "page-link"
|
||||||
|
, onClick (toMsg targetPage)
|
||||||
|
|
||||||
|
-- The RealWorld CSS requires an href to work properly.
|
||||||
|
, href ""
|
||||||
|
]
|
||||||
|
[ text (String.fromInt targetPage) ]
|
||||||
|
]
|
||||||
|
|||||||
13
advanced/part5/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Part 5
|
||||||
|
|
||||||
|
To build everything, `cd` into this `part5/` directory and run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
elm make src/Main.elm --output=../server/public/elm.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open [http://localhost:3000](http://localhost:3000) in your browser.
|
||||||
|
|
||||||
|
## Exercise
|
||||||
|
|
||||||
|
Open `src/Article/Feed.elm` in your editor and resolve the TODO there.
|
||||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 736 B After Width: | Height: | Size: 736 B |
|
Before Width: | Height: | Size: 985 B After Width: | Height: | Size: 985 B |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
@@ -110,11 +110,50 @@ viewArticles timeZone (Model { articles, sources, session }) =
|
|||||||
FeedSources.selected sources
|
FeedSources.selected sources
|
||||||
|
|
||||||
pagination =
|
pagination =
|
||||||
PaginatedList.view ClickedFeedPage articles (limit feedSource)
|
viewPaginatedList articles (limit feedSource)
|
||||||
in
|
in
|
||||||
List.append articlesHtml [ pagination ]
|
List.append articlesHtml [ pagination ]
|
||||||
|
|
||||||
|
|
||||||
|
{-| 👉 TODO Move this logic into PaginatedList.view and make it reusable,
|
||||||
|
so we can use it on other pages too!
|
||||||
|
💡 HINT: Make `PaginatedList.view` return `Html msg` instead of `Html Msg`. (The function will need to accept an extra argument for this to work.)
|
||||||
|
-}
|
||||||
|
viewPaginatedList : PaginatedList a -> Int -> Html Msg
|
||||||
|
viewPaginatedList paginatedList resultsPerPage =
|
||||||
|
let
|
||||||
|
totalPages =
|
||||||
|
ceiling (toFloat (PaginatedList.total paginatedList) / toFloat resultsPerPage)
|
||||||
|
|
||||||
|
activePage =
|
||||||
|
PaginatedList.page paginatedList
|
||||||
|
|
||||||
|
viewPageLink currentPage =
|
||||||
|
pageLink currentPage (currentPage == activePage)
|
||||||
|
in
|
||||||
|
if totalPages > 1 then
|
||||||
|
List.range 1 totalPages
|
||||||
|
|> List.map viewPageLink
|
||||||
|
|> ul [ class "pagination" ]
|
||||||
|
|
||||||
|
else
|
||||||
|
Html.text ""
|
||||||
|
|
||||||
|
|
||||||
|
pageLink : Int -> Bool -> Html Msg
|
||||||
|
pageLink targetPage isActive =
|
||||||
|
li [ classList [ ( "page-item", True ), ( "active", isActive ) ] ]
|
||||||
|
[ a
|
||||||
|
[ class "page-link"
|
||||||
|
, onClick (ClickedFeedPage targetPage)
|
||||||
|
|
||||||
|
-- The RealWorld CSS requires an href to work properly.
|
||||||
|
, href ""
|
||||||
|
]
|
||||||
|
[ text (String.fromInt targetPage) ]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
viewPreview : Maybe Cred -> Time.Zone -> Article Preview -> Html Msg
|
viewPreview : Maybe Cred -> Time.Zone -> Article Preview -> Html Msg
|
||||||
viewPreview maybeCred timeZone article =
|
viewPreview maybeCred timeZone article =
|
||||||
let
|
let
|
||||||
@@ -84,8 +84,19 @@ view model =
|
|||||||
[ viewBanner
|
[ viewBanner
|
||||||
, div [ class "container page" ]
|
, div [ class "container page" ]
|
||||||
[ div [ class "row" ]
|
[ div [ class "row" ]
|
||||||
[ div [ class "col-md-9" ]
|
[ div [ class "col-md-9" ] <|
|
||||||
(viewFeed model)
|
case model.feed of
|
||||||
|
Loaded feed ->
|
||||||
|
viewFeed model.timeZone feed
|
||||||
|
|
||||||
|
Loading ->
|
||||||
|
[]
|
||||||
|
|
||||||
|
LoadingSlowly ->
|
||||||
|
[ Loading.icon ]
|
||||||
|
|
||||||
|
Failed ->
|
||||||
|
[ Loading.error "feed" ]
|
||||||
, div [ class "col-md-3" ] <|
|
, div [ class "col-md-3" ] <|
|
||||||
case model.tags of
|
case model.tags of
|
||||||
Loaded tags ->
|
Loaded tags ->
|
||||||
@@ -119,25 +130,11 @@ viewBanner =
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
{-| 👉 TODO refactor this to accept narrower types than the entire Model.
|
viewFeed : Time.Zone -> Feed.Model -> List (Html Msg)
|
||||||
💡 HINT: It may end up with multiple arguments!
|
viewFeed timeZone feed =
|
||||||
-}
|
div [ class "feed-toggle" ]
|
||||||
viewFeed : Model -> List (Html Msg)
|
[ Feed.viewFeedSources feed |> Html.map GotFeedMsg ]
|
||||||
viewFeed model =
|
:: (Feed.viewArticles timeZone feed |> List.map (Html.map GotFeedMsg))
|
||||||
case model.feed of
|
|
||||||
Loaded feed ->
|
|
||||||
div [ class "feed-toggle" ]
|
|
||||||
[ Feed.viewFeedSources feed |> Html.map GotFeedMsg ]
|
|
||||||
:: (Feed.viewArticles model.timeZone feed |> List.map (Html.map GotFeedMsg))
|
|
||||||
|
|
||||||
Loading ->
|
|
||||||
[]
|
|
||||||
|
|
||||||
LoadingSlowly ->
|
|
||||||
[ Loading.icon ]
|
|
||||||
|
|
||||||
Failed ->
|
|
||||||
[ Loading.error "feed" ]
|
|
||||||
|
|
||||||
|
|
||||||
viewTags : List Tag -> Html Msg
|
viewTags : List Tag -> Html Msg
|
||||||
@@ -98,10 +98,6 @@ type ValidForm
|
|||||||
|
|
||||||
view : Model -> { title : String, content : Html Msg }
|
view : Model -> { title : String, content : Html Msg }
|
||||||
view model =
|
view model =
|
||||||
let
|
|
||||||
form =
|
|
||||||
viewForm model
|
|
||||||
in
|
|
||||||
{ title = "Settings"
|
{ title = "Settings"
|
||||||
, content =
|
, content =
|
||||||
case Session.cred model.session of
|
case Session.cred model.session of
|
||||||
@@ -113,7 +109,7 @@ view model =
|
|||||||
[ h1 [ class "text-xs-center" ] [ text "Your Settings" ]
|
[ h1 [ class "text-xs-center" ] [ text "Your Settings" ]
|
||||||
, ul [ class "error-messages" ]
|
, ul [ class "error-messages" ]
|
||||||
(List.map viewProblem model.problems)
|
(List.map viewProblem model.problems)
|
||||||
, form
|
, viewForm cred model.form
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@@ -124,74 +120,62 @@ view model =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{-| 👉 TODO refactor this to accept narrower types than the entire Model.
|
viewForm : Cred -> Form -> Html Msg
|
||||||
💡 HINT: It may end up with multiple arguments!
|
viewForm cred form =
|
||||||
-}
|
Html.form [ onSubmit (SubmittedForm cred) ]
|
||||||
viewForm : Model -> Html Msg
|
[ fieldset []
|
||||||
viewForm model =
|
[ fieldset [ class "form-group" ]
|
||||||
let
|
[ input
|
||||||
form =
|
[ class "form-control"
|
||||||
model.form
|
, placeholder "URL of profile picture"
|
||||||
in
|
, value form.avatar
|
||||||
case Session.cred model.session of
|
, onInput EnteredAvatar
|
||||||
Nothing ->
|
|
||||||
text ""
|
|
||||||
|
|
||||||
Just cred ->
|
|
||||||
Html.form [ onSubmit (SubmittedForm cred) ]
|
|
||||||
[ fieldset []
|
|
||||||
[ fieldset [ class "form-group" ]
|
|
||||||
[ input
|
|
||||||
[ class "form-control"
|
|
||||||
, placeholder "URL of profile picture"
|
|
||||||
, value form.avatar
|
|
||||||
, onInput EnteredAvatar
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
, fieldset [ class "form-group" ]
|
|
||||||
[ input
|
|
||||||
[ class "form-control form-control-lg"
|
|
||||||
, placeholder "Username"
|
|
||||||
, value form.username
|
|
||||||
, onInput EnteredUsername
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
, fieldset [ class "form-group" ]
|
|
||||||
[ textarea
|
|
||||||
[ class "form-control form-control-lg"
|
|
||||||
, placeholder "Short bio about you"
|
|
||||||
, attribute "rows" "8"
|
|
||||||
, value form.bio
|
|
||||||
, onInput EnteredBio
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
, fieldset [ class "form-group" ]
|
|
||||||
[ input
|
|
||||||
[ class "form-control form-control-lg"
|
|
||||||
, placeholder "Email"
|
|
||||||
, value form.email
|
|
||||||
, onInput EnteredEmail
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
, fieldset [ class "form-group" ]
|
|
||||||
[ input
|
|
||||||
[ class "form-control form-control-lg"
|
|
||||||
, type_ "password"
|
|
||||||
, placeholder "Password"
|
|
||||||
, value form.password
|
|
||||||
, onInput EnteredPassword
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
, button
|
|
||||||
[ class "btn btn-lg btn-primary pull-xs-right" ]
|
|
||||||
[ text "Update Settings" ]
|
|
||||||
]
|
]
|
||||||
|
[]
|
||||||
]
|
]
|
||||||
|
, fieldset [ class "form-group" ]
|
||||||
|
[ input
|
||||||
|
[ class "form-control form-control-lg"
|
||||||
|
, placeholder "Username"
|
||||||
|
, value form.username
|
||||||
|
, onInput EnteredUsername
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, fieldset [ class "form-group" ]
|
||||||
|
[ textarea
|
||||||
|
[ class "form-control form-control-lg"
|
||||||
|
, placeholder "Short bio about you"
|
||||||
|
, attribute "rows" "8"
|
||||||
|
, value form.bio
|
||||||
|
, onInput EnteredBio
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, fieldset [ class "form-group" ]
|
||||||
|
[ input
|
||||||
|
[ class "form-control form-control-lg"
|
||||||
|
, placeholder "Email"
|
||||||
|
, value form.email
|
||||||
|
, onInput EnteredEmail
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, fieldset [ class "form-group" ]
|
||||||
|
[ input
|
||||||
|
[ class "form-control form-control-lg"
|
||||||
|
, type_ "password"
|
||||||
|
, placeholder "Password"
|
||||||
|
, value form.password
|
||||||
|
, onInput EnteredPassword
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, button
|
||||||
|
[ class "btn btn-lg btn-primary pull-xs-right" ]
|
||||||
|
[ text "Update Settings" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
viewProblem : Problem -> Html msg
|
viewProblem : Problem -> Html msg
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
module PaginatedList exposing (PaginatedList, fromList, map, mapPage, page, total, values, view)
|
module PaginatedList exposing (PaginatedList, fromList, map, mapPage, page, total, values)
|
||||||
|
|
||||||
import Html exposing (Html, a, li, text, ul)
|
import Html exposing (Html, a, li, text, ul)
|
||||||
import Html.Attributes exposing (class, classList, href)
|
import Html.Attributes exposing (class, classList, href)
|
||||||
@@ -61,38 +61,3 @@ mapPage transform (PaginatedList info) =
|
|||||||
|
|
||||||
|
|
||||||
-- VIEW
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
view : (Int -> msg) -> PaginatedList a -> Int -> Html msg
|
|
||||||
view toMsg list resultsPerPage =
|
|
||||||
let
|
|
||||||
totalPages =
|
|
||||||
ceiling (toFloat (total list) / toFloat resultsPerPage)
|
|
||||||
|
|
||||||
activePage =
|
|
||||||
page list
|
|
||||||
|
|
||||||
viewPageLink currentPage =
|
|
||||||
pageLink toMsg currentPage (currentPage == activePage)
|
|
||||||
in
|
|
||||||
if totalPages > 1 then
|
|
||||||
List.range 1 totalPages
|
|
||||||
|> List.map viewPageLink
|
|
||||||
|> ul [ class "pagination" ]
|
|
||||||
|
|
||||||
else
|
|
||||||
Html.text ""
|
|
||||||
|
|
||||||
|
|
||||||
pageLink : (Int -> msg) -> Int -> Bool -> Html msg
|
|
||||||
pageLink toMsg targetPage isActive =
|
|
||||||
li [ classList [ ( "page-item", True ), ( "active", isActive ) ] ]
|
|
||||||
[ a
|
|
||||||
[ class "page-link"
|
|
||||||
, onClick (toMsg targetPage)
|
|
||||||
|
|
||||||
-- The RealWorld CSS requires an href to work properly.
|
|
||||||
, href ""
|
|
||||||
]
|
|
||||||
[ text (String.fromInt targetPage) ]
|
|
||||||
]
|
|
||||||