Update part8 and part9

This commit is contained in:
Richard Feldman
2016-04-03 08:50:35 -07:00
parent dfbff3f448
commit 63e357f146
11 changed files with 176 additions and 123 deletions

View File

@@ -1,18 +1,19 @@
module ElmHub (..) where module ElmHub (..) where
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (class, target, href, property)
import Html.Events exposing (..) import Html.Events exposing (..)
import Http import Http
import Auth import Auth
import Task exposing (Task) import Task exposing (Task)
import Effects exposing (Effects) import Effects exposing (Effects)
import Json.Decode exposing (Decoder, (:=)) import Json.Decode exposing (Decoder, (:=))
import Json.Decode.Pipeline exposing (..)
import Json.Encode import Json.Encode
import Signal exposing (Address) import Signal exposing (Address)
searchFeed : Address String -> String -> Task x Action searchFeed : Address String -> String -> Effects Action
searchFeed address query = searchFeed address query =
let let
-- See https://developer.github.com/v3/search/#example for how to customize! -- See https://developer.github.com/v3/search/#example for how to customize!
@@ -23,13 +24,15 @@ searchFeed address query =
++ query ++ query
++ "+language:elm&sort=stars&order=desc" ++ "+language:elm&sort=stars&order=desc"
-- These only talk to JavaScript ports now. They don't -- These only talk to JavaScript ports now. They never result in Actions
-- actually do any actions themselves. -- actually do any actions themselves.
task = task =
Signal.send address query performAction
|> Task.map (\_ -> DoNothing) (\_ -> DoNothing)
(\_ -> DoNothing)
(Signal.send address query)
in in
Task.onError task (\_ -> Task.succeed DoNothing) Effects.task task
responseDecoder : Decoder (List SearchResult) responseDecoder : Decoder (List SearchResult)
@@ -39,16 +42,40 @@ responseDecoder =
searchResultDecoder : Decoder SearchResult searchResultDecoder : Decoder SearchResult
searchResultDecoder = searchResultDecoder =
Json.Decode.object3 decode SearchResult
SearchResult |> required "id" Json.Decode.int
("id" := Json.Decode.int) |> required "full_name" Json.Decode.string
("full_name" := Json.Decode.string) |> required "stargazers_count" Json.Decode.int
("stargazers_count" := Json.Decode.int)
{-| Note: this will be a standard function in the next release of Elm.
Example:
type Action =
HandleResponse String | HandleError Http.Error
performAction
(\responseString -> HandleResponse responseString)
(\httpError -> HandleError httpError)
(Http.getString "https://google.com?q=something")
-}
performAction : (a -> b) -> (y -> b) -> Task y a -> Task x b
performAction successToAction errorToAction task =
let
successTask =
Task.map successToAction task
in
Task.onError successTask (\err -> Task.succeed (errorToAction err))
type alias Model = type alias Model =
{ query : String { query : String
, results : List SearchResult , results : List SearchResult
, errorMessage : Maybe String
} }
@@ -67,6 +94,7 @@ initialModel : Model
initialModel = initialModel =
{ query = "tutorial" { query = "tutorial"
, results = [] , results = []
, errorMessage = Nothing
} }
@@ -81,12 +109,23 @@ view address model =
] ]
, input [ class "search-query", onInput address SetQuery, defaultValue model.query ] [] , input [ class "search-query", onInput address SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick address Search ] [ text "Search" ] , button [ class "search-button", onClick address Search ] [ text "Search" ]
, viewErrorMessage model.errorMessage
, ul , ul
[ class "results" ] [ class "results" ]
(List.map (viewSearchResult address) model.results) (List.map (viewSearchResult address) model.results)
] ]
viewErrorMessage : Maybe String -> Html
viewErrorMessage errorMessage =
case errorMessage of
Just message ->
div [ class "error" ] [ text message ]
Nothing ->
text ""
onInput address wrap = onInput address wrap =
on "input" targetValue (\val -> Signal.message address (wrap val)) on "input" targetValue (\val -> Signal.message address (wrap val))
@@ -114,6 +153,7 @@ type Action
| SetQuery String | SetQuery String
| DeleteById ResultId | DeleteById ResultId
| SetResults (List SearchResult) | SetResults (List SearchResult)
| SetErrorMessage (Maybe String)
| DoNothing | DoNothing
@@ -121,17 +161,16 @@ update : Address String -> Action -> Model -> ( Model, Effects Action )
update searchAddress action model = update searchAddress action model =
case action of case action of
Search -> Search ->
( model, Effects.task (searchFeed searchAddress model.query) ) ( model, searchFeed searchAddress model.query )
SetQuery query -> SetQuery query ->
( { model | query = query }, Effects.none ) ( { model | query = query }, Effects.none )
SetResults results -> SetResults results ->
let ( { model | results = results }, Effects.none )
newModel =
{ model | results = results } SetErrorMessage errorMessage ->
in ( { model | errorMessage = errorMessage }, Effects.none )
( newModel, Effects.none )
DeleteById idToHide -> DeleteById idToHide ->
let let

View File

@@ -20,7 +20,7 @@ app =
StartApp.start StartApp.start
{ view = view { view = view
, update = update search.address , update = update search.address
, init = ( initialModel, Effects.task (searchFeed search.address initialModel.query) ) , init = ( initialModel, searchFeed search.address initialModel.query )
, inputs = [ responseActions ] , inputs = [ responseActions ]
} }
@@ -47,12 +47,11 @@ responseActions =
decodeGithubResponse : Json.Encode.Value -> Action decodeGithubResponse : Json.Encode.Value -> Action
decodeGithubResponse value = decodeGithubResponse value =
case Json.Decode.decodeValue responseDecoder value of -- TODO use Json.Decode.DecodeValue to decode the response into an Action.
Ok results -> --
SetResults results -- Hint: look at ElmHub.elm, specifically the definition of Action and
-- the deefinition of responseDecoder
Err _ -> SetErrorMessage (Just "TODO decode the response!")
DoNothing
port githubResponse : Signal Json.Encode.Value port githubResponse : Signal Json.Encode.Value

View File

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

View File

@@ -1,20 +1,21 @@
module ElmHub (..) where module ElmHub (..) where
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (class, target, href, property)
import Html.Events exposing (..) import Html.Events exposing (..)
import Http import Http
import Auth import Auth
import Task exposing (Task) import Task exposing (Task)
import Effects exposing (Effects) import Effects exposing (Effects)
import Json.Decode exposing (Decoder, (:=)) import Json.Decode exposing (Decoder, (:=))
import Json.Decode.Pipeline exposing (..)
import Json.Encode import Json.Encode
import Signal exposing (Address) import Signal exposing (Address)
import Dict exposing (Dict) import Dict exposing (Dict)
searchFeed : String -> Task x Action searchFeed : Address String -> String -> Effects Action
searchFeed query = searchFeed address query =
let let
-- See https://developer.github.com/v3/search/#example for how to customize! -- See https://developer.github.com/v3/search/#example for how to customize!
url = url =
@@ -24,11 +25,15 @@ searchFeed query =
++ query ++ query
++ "+language:elm&sort=stars&order=desc" ++ "+language:elm&sort=stars&order=desc"
-- These only talk to JavaScript ports now. They never result in Actions
-- actually do any actions themselves.
task = task =
Http.get responseDecoder url performAction
|> Task.map SetResults (\_ -> DoNothing)
(\_ -> DoNothing)
(Signal.send address query)
in in
Task.onError task (\_ -> Task.succeed (SetResults [])) Effects.task task
responseDecoder : Decoder (List SearchResult) responseDecoder : Decoder (List SearchResult)
@@ -38,16 +43,40 @@ responseDecoder =
searchResultDecoder : Decoder SearchResult searchResultDecoder : Decoder SearchResult
searchResultDecoder = searchResultDecoder =
Json.Decode.object3 decode SearchResult
SearchResult |> required "id" Json.Decode.int
("id" := Json.Decode.int) |> required "full_name" Json.Decode.string
("full_name" := Json.Decode.string) |> required "stargazers_count" Json.Decode.int
("stargazers_count" := Json.Decode.int)
{-| Note: this will be a standard function in the next release of Elm.
Example:
type Action =
HandleResponse String | HandleError Http.Error
performAction
(\responseString -> HandleResponse responseString)
(\httpError -> HandleError httpError)
(Http.getString "https://google.com?q=something")
-}
performAction : (a -> b) -> (y -> b) -> Task y a -> Task x b
performAction successToAction errorToAction task =
let
successTask =
Task.map successToAction task
in
Task.onError successTask (\err -> Task.succeed (errorToAction err))
type alias Model = type alias Model =
{ query : String { query : String
, results : Dict ResultId SearchResult , results : Dict ResultId SearchResult
, errorMessage : Maybe String
} }
@@ -66,6 +95,7 @@ initialModel : Model
initialModel = initialModel =
{ query = "tutorial" { query = "tutorial"
, results = Dict.empty , results = Dict.empty
, errorMessage = Nothing
} }
@@ -119,13 +149,15 @@ type Action
| SetQuery String | SetQuery String
| DeleteById ResultId | DeleteById ResultId
| SetResults (List SearchResult) | SetResults (List SearchResult)
| SetErrorMessage (Maybe String)
| DoNothing
update : Action -> Model -> ( Model, Effects Action ) update : Address String -> Action -> Model -> ( Model, Effects Action )
update action model = update searchAddress action model =
case action of case action of
Search -> Search ->
( model, Effects.task (searchFeed model.query) ) ( model, searchFeed searchAddress model.query )
SetQuery query -> SetQuery query ->
( { model | query = query }, Effects.none ) ( { model | query = query }, Effects.none )
@@ -142,3 +174,9 @@ update action model =
DeleteById id -> DeleteById id ->
-- TODO delete the result with the given id -- TODO delete the result with the given id
( model, Effects.none ) ( model, Effects.none )
SetErrorMessage errorMessage ->
( { model | errorMessage = errorMessage }, Effects.none )
DoNothing ->
( model, Effects.none )

View File

@@ -5,6 +5,9 @@ import ElmHub exposing (..)
import Effects exposing (Effects) import Effects exposing (Effects)
import Task exposing (Task) import Task exposing (Task)
import Html exposing (Html) import Html exposing (Html)
import Signal
import Json.Encode
import Json.Decode
main : Signal Html main : Signal Html
@@ -16,12 +19,40 @@ app : StartApp.App Model
app = app =
StartApp.start StartApp.start
{ view = view { view = view
, update = update , update = update search.address
, init = ( initialModel, Effects.task (searchFeed initialModel.query) ) , init = ( initialModel, searchFeed search.address initialModel.query )
, inputs = [] , inputs = [ responseActions ]
} }
port tasks : Signal (Task Effects.Never ()) port tasks : Signal (Task Effects.Never ())
port tasks = port tasks =
app.tasks app.tasks
search : Signal.Mailbox String
search =
Signal.mailbox ""
port githubSearch : Signal String
port githubSearch =
search.signal
responseActions : Signal Action
responseActions =
Signal.map decodeGithubResponse githubResponse
decodeGithubResponse : Json.Encode.Value -> Action
decodeGithubResponse value =
case Json.Decode.decodeValue responseDecoder value of
Ok results ->
SetResults results
Err message ->
SetErrorMessage (Just message)
port githubResponse : Signal Json.Encode.Value

View File

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

2
part9/github.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -4,23 +4,34 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>ElmHub</title> <title>ElmHub</title>
<script type="text/javascript" src="github.js"></script>
<script type="text/javascript" src="elm.js"></script> <script type="text/javascript" src="elm.js"></script>
<!-- Uncomment the below line to enable elm-reactor support. -->
<!-- <script type="text/javascript" src="/_reactor/debug.js"></script> -->
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<link rel="icon" type="image/png" href="elm-hub.png"> <link rel="icon" type="image/png" href="elm-hub.png">
</head> </head>
<body> <body>
<div id="elm-landing-pad"></div>
</body> </body>
<script type="text/javascript"> <script type="text/javascript">
var app = Elm.fullscreen(Elm.Main, {}); // documentation: https://github.com/michael/github
var github = new Github();
// Uncomment this line and comment out the above to enable elm-reactor support. var app = Elm.embed(
// var app = Elm.fullscreenDebug("ElmHub", "Main.elm"); Elm.Main,
document.getElementById("elm-landing-pad"),
{githubResponse: []});
function searchGithub(query) {
var search = github.getSearch(query);
search.repositories({}, function (err, repositories) {
app.ports.githubResponse.send(repositories);
});
}
app.ports.githubSearch.subscribe(searchGithub);
</script> </script>
</html> </html>

View File

@@ -1,15 +0,0 @@
module Main where
import Signal exposing (Signal)
import ElmTest exposing (consoleRunner)
import Console exposing (IO, run)
import Task
import Tests
console : IO ()
console = consoleRunner Tests.all
port runner : Signal (Task.Task x ())
port runner = run console

View File

@@ -1,35 +0,0 @@
module Tests (..) where
import ElmTest exposing (..)
import ElmHub exposing (responseDecoder)
import Json.Decode exposing (decodeString)
all : Test
all =
suite
"Decoding responses from GitHub"
[ test "they can decode empty responses"
<| let
emptyResponse =
"""{ "items": [] }"""
in
assertEqual
(decodeString responseDecoder emptyResponse)
(Ok [])
, test "they can decode responses with results in them"
<| let
response =
"""{ "items": [
{ "id": 5, "full_name": "foo", "stargazers_count": 42 },
{ "id": 3, "full_name": "bar", "stargazers_count": 77 }
] }"""
in
assertEqual
(decodeString responseDecoder response)
(Ok
[ { id = 5, name = "foo", stars = 42 }
, { id = 3, name = "bar", stars = 77 }
]
)
]

View File

@@ -1,21 +0,0 @@
{
"version": "1.0.0",
"summary": "Like GitHub, but for Elm stuff.",
"repository": "https://github.com/rtfeldman/elm-workshop.git",
"license": "BSD-3-Clause",
"source-directories": [
".",
".."
],
"exposed-modules": [],
"dependencies": {
"deadfoxygrandpa/elm-test": "3.1.1 <= v < 4.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",
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0",
"laszlopandy/elm-console": "1.0.3 <= v < 2.0.0"
},
"elm-version": "0.16.0 <= v < 0.17.0"
}