Update 12 and 13

This commit is contained in:
Richard Feldman
2016-04-03 10:07:53 -07:00
parent c004be3f72
commit bb92151d61
9 changed files with 235 additions and 116 deletions

View File

@@ -1,7 +1,7 @@
module ElmHub (..) where
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Attributes exposing (class, target, href, property)
import Html.Events exposing (..)
import Html.Lazy exposing (..)
import Http

View File

@@ -1,4 +1,4 @@
module Component.ElmHub (..) where
module ElmHub (..) where
import Html exposing (..)
import Html.Attributes exposing (..)
@@ -11,7 +11,8 @@ import Effects exposing (Effects)
import Json.Decode exposing (Decoder, (:=))
import Json.Encode
import Signal exposing (Address)
import Component.SearchResult exposing (ResultId)
import Dict exposing (Dict)
import SearchResult exposing (ResultId)
searchFeed : String -> Task x Action
@@ -32,31 +33,21 @@ searchFeed query =
Task.onError task (\_ -> Task.succeed (SetResults []))
responseDecoder : Decoder (List Component.SearchResult.Model)
responseDecoder : Decoder (List SearchResult.Model)
responseDecoder =
"items" := Json.Decode.list searchResultDecoder
searchResultDecoder : Decoder Component.SearchResult.Model
searchResultDecoder =
Json.Decode.object4
Component.SearchResult.Model
("id" := Json.Decode.int)
("full_name" := Json.Decode.string)
("stargazers_count" := Json.Decode.int)
(Json.Decode.succeed True)
"items" := Json.Decode.list SearchResult.decoder
type alias Model =
{ query : String
, results : List Component.SearchResult.Model
, results : Dict SearchResult.ResultId SearchResult.Model
}
initialModel : Model
initialModel =
{ query = "tutorial"
, results = []
, results = Dict.empty
}
@@ -77,24 +68,19 @@ view address model =
]
viewSearchResults : Address Action -> List Component.SearchResult.Model -> List Html
viewSearchResults : Address Action -> Dict ResultId SearchResult.Model -> List Html
viewSearchResults address results =
results
|> filterResults
|> List.map (lazy2 viewSearchResult address)
|> Dict.values
|> List.sortBy (.stars >> negate)
|> List.map (viewSearchResult address)
filterResults : List Component.SearchResult.Model -> List Component.SearchResult.Model
filterResults results =
case results of
[] ->
[]
first :: rest ->
if first.stars > 0 then
first :: (filterResults rest)
else
filterResults rest
viewSearchResult : Address Action -> SearchResult.Model -> Html
viewSearchResult address result =
SearchResult.view
(Signal.forwardTo address (UpdateSearchResult result.id))
result
onInput address wrap =
@@ -105,18 +91,11 @@ defaultValue str =
property "defaultValue" (Json.Encode.string str)
viewSearchResult : Address Action -> Component.SearchResult.Model -> Html
viewSearchResult address result =
Component.SearchResult.view
(Signal.forwardTo address (UpdateSearchResult result.id))
(Debug.log "rendering result..." result)
type Action
= Search
| SetQuery String
| SetResults (List Component.SearchResult.Model)
| UpdateSearchResult ResultId Component.SearchResult.Action
| SetResults (List SearchResult.Model)
| UpdateSearchResult ResultId SearchResult.Action
update : Action -> Model -> ( Model, Effects Action )
@@ -130,31 +109,31 @@ update action model =
SetResults results ->
let
newModel =
{ model | results = results }
resultsById : Dict SearchResult.ResultId SearchResult.Model
resultsById =
results
|> List.map (\result -> ( result.id, result ))
|> Dict.fromList
in
( newModel, Effects.none )
( { model | results = resultsById }, Effects.none )
UpdateSearchResult id childAction ->
let
updateResult childModel =
if childModel.id == id then
let
( newChildModel, childEffects ) =
Component.SearchResult.update childAction childModel
in
( newChildModel
, Effects.map (UpdateSearchResult id) childEffects
)
else
( childModel, Effects.none )
( newResults, effects ) =
updated =
model.results
|> List.map updateResult
|> List.unzip
newModel =
{ model | results = newResults }
|> Dict.get id
|> Maybe.map (SearchResult.update childAction)
in
( newModel, Effects.batch effects )
case updated of
Nothing ->
( model, Effects.none )
Just ( newChildModel, childEffects ) ->
let
effects =
Effects.map (UpdateSearchResult id) childEffects
newResults =
Dict.insert id newChildModel model.results
in
( { model | results = newResults }, effects )

View File

@@ -1,7 +1,7 @@
module Main (..) where
import StartApp
import Component.ElmHub exposing (..)
import ElmHub exposing (..)
import Effects exposing (Effects)
import Task exposing (Task)
import Html exposing (Html)

View File

@@ -15,3 +15,7 @@ to fail; in that case, just run `elm package install` again.)
```bash
elm live Main.elm --open -- --output=elm.js
```
## References
* [Elm Architecture Tutorial](https://github.com/evancz/elm-architecture-tutorial)

View File

@@ -1,10 +1,12 @@
module Component.SearchResult (..) where
module SearchResult (..) where
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Attributes exposing (class, target, href, property)
import Html.Events exposing (..)
import Signal exposing (Address)
import Effects exposing (Effects)
import Json.Decode exposing (Decoder, (:=))
import Json.Decode.Pipeline exposing (..)
type alias Model =
@@ -24,9 +26,18 @@ type Action
| Collapse
decoder : Decoder Model
decoder =
decode Model
|> required "id" Json.Decode.int
|> required "full_name" Json.Decode.string
|> required "stargazers_count" Json.Decode.int
|> hardcoded True
update : Action -> Model -> ( Model, Effects Action )
update action model =
-- TODO make expand and collapse work
-- TODO implement Expand and Collapse logic
( model, Effects.none )

View File

@@ -4,10 +4,12 @@
"repository": "https://github.com/rtfeldman/elm-workshop.git",
"license": "BSD-3-Clause",
"source-directories": [
".", ".."
".",
".."
],
"exposed-modules": [],
"dependencies": {
"NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0",
@@ -15,4 +17,4 @@
"evancz/start-app": "2.0.0 <= v < 3.0.0"
},
"elm-version": "0.16.0 <= v < 0.17.0"
}
}

View File

@@ -12,7 +12,7 @@ import Json.Decode exposing (Decoder, (:=))
import Json.Encode
import Signal exposing (Address)
import Dict exposing (Dict)
import SearchResult
import SearchResult exposing (ResultId)
searchFeed : String -> Task x Action
@@ -68,26 +68,19 @@ view address model =
]
viewSearchResults : Address Action -> Dict SearchResult.ResultId SearchResult.Model -> List Html
viewSearchResults : Address Action -> Dict ResultId SearchResult.Model -> List Html
viewSearchResults address results =
results
|> Dict.values
|> List.sortBy (.stars >> negate)
|> filterResults
|> List.map (lazy3 SearchResult.view address DeleteById)
|> List.map (viewSearchResult address)
filterResults : List SearchResult.Model -> List SearchResult.Model
filterResults results =
case results of
[] ->
[]
result :: rest ->
if result.stars > 0 then
result :: (filterResults rest)
else
filterResults rest
viewSearchResult : Address Action -> SearchResult.Model -> Html
viewSearchResult address result =
SearchResult.view
(Signal.forwardTo address (UpdateSearchResult result.id))
result
onInput address wrap =
@@ -101,8 +94,8 @@ defaultValue str =
type Action
= Search
| SetQuery String
| DeleteById SearchResult.ResultId
| SetResults (List SearchResult.Model)
| UpdateSearchResult ResultId SearchResult.Action
update : Action -> Model -> ( Model, Effects Action )
@@ -124,9 +117,23 @@ update action model =
in
( { model | results = resultsById }, Effects.none )
DeleteById id ->
UpdateSearchResult id childAction ->
let
newModel =
{ model | results = Dict.remove id model.results }
updated =
model.results
|> Dict.get id
|> Maybe.map (SearchResult.update childAction)
in
( newModel, Effects.none )
case updated of
Nothing ->
( model, Effects.none )
Just ( newChildModel, childEffects ) ->
let
effects =
Effects.map (UpdateSearchResult id) childEffects
newResults =
Dict.insert id newChildModel model.results
in
( { model | results = newResults }, effects )

View File

@@ -1,44 +1,65 @@
module SearchResult (..) where
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Attributes exposing (class, target, href, property)
import Html.Events exposing (..)
import Json.Decode exposing (Decoder, (:=))
import Signal exposing (Address)
import Dict exposing (Dict)
import Effects exposing (Effects)
import Json.Decode exposing (Decoder, (:=))
import Json.Decode.Pipeline exposing (..)
type alias Model =
{ id : Int
, name : String
, stars : Int
, expanded : Bool
}
type alias ResultId =
Int
type alias Model =
{ id : ResultId
, name : String
, stars : Int
}
type Action
= Expand
| Collapse
decoder : Decoder Model
decoder =
Json.Decode.object3
Model
("id" := Json.Decode.int)
("full_name" := Json.Decode.string)
("stargazers_count" := Json.Decode.int)
decode Model
|> required "id" Json.Decode.int
|> required "full_name" Json.Decode.string
|> required "stargazers_count" Json.Decode.int
|> hardcoded True
view : Address a -> (Int -> a) -> Model -> Html
view address delete result =
update : Action -> Model -> ( Model, Effects Action )
update action model =
case action of
Expand ->
( { model | expanded = True }, Effects.none )
Collapse ->
( { model | expanded = False }, Effects.none )
view : Address Action -> Model -> Html
view address model =
li
[]
[ span [ class "star-count" ] [ text (toString result.stars) ]
, a
[ href ("https://github.com/" ++ result.name)
, target "_blank"
<| if model.expanded then
[ span [ class "star-count" ] [ text (toString model.stars) ]
, a
[ href ("https://github.com/" ++ model.name), target "_blank" ]
[ text model.name ]
, button
[ class "hide-result", onClick address Collapse ]
[ text "X" ]
]
else
[ button
[ class "expand-result", onClick address Expand ]
[ text "Show" ]
]
[ text result.name ]
, button
[ class "hide-result", onClick address (delete result.id) ]
[ text "X" ]
]

View File

@@ -1,6 +1,101 @@
.content {
width: 960px;
margin: 0 auto;
padding: 30px;
font-family: Helvetica, Arial, serif;
width: 960px;
margin: 0 auto;
padding: 30px;
font-family: Helvetica, Arial, serif;
}
header {
position: relative;
padding: 6px 12px;
height: 36px;
background-color: rgb(96, 181, 204);
}
h1 {
color: white;
font-weight: normal;
margin: 0;
}
.tagline {
color: #eee;
position: absolute;
right: 16px;
top: 12px;
font-size: 24px;
font-style: italic;
}
.results {
list-style-image: url('http://img-cache.cdn.gaiaonline.com/76bd5c99d8f2236e9d3672510e933fdf/http://i278.photobucket.com/albums/kk81/d3m3nt3dpr3p/Tiny-Star-Icon.png');
list-style-position: inside;
padding: 0;
}
.results li {
font-size: 18px;
margin-bottom: 16px;
}
.star-count {
font-weight: bold;
margin-right: 16px;
}
a {
color: rgb(96, 181, 204);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.search-query {
padding: 8px;
font-size: 24px;
margin-bottom: 18px;
margin-top: 36px;
}
.search-button {
padding: 8px 16px;
font-size: 24px;
color: white;
border: 1px solid #ccc;
background-color: rgb(96, 181, 204);
margin-left: 12px
}
.search-button:hover {
color: rgb(96, 181, 204);
background-color: white;
}
.hide-result {
background-color: transparent;
border: 0;
font-weight: bold;
font-size: 18px;
margin-left: 18px;
cursor: pointer;
}
.hide-result:hover {
color: rgb(96, 181, 204);
}
button:focus, input:focus {
outline: none;
}
.error {
background-color: #FF9632;
padding: 20px;
box-sizing: border-box;
overflow-x: auto;
font-family: monospace;
font-size: 18px;
}