-
-
Save jvoigtlaender/34c686bdaef4258e6bf4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Main where | |
import Signal | |
import Time exposing (Time, millisecond) | |
import String | |
import Html exposing (..) | |
import Html.Attributes exposing (..) | |
import Html.Events exposing (..) | |
import Effects exposing (Never, Effects) | |
import Task exposing (..) | |
import StartApp | |
import Debug exposing (..) | |
import Http | |
import Json.Decode as Json exposing ((:=)) | |
-- MODEL | |
{-- | |
Model type: | |
Has the shape of a record | |
Has a query prop that is of sting type | |
Has a results prop that 'Maybe' is a string or Nothing | |
has a debounceState that is of type 'DebounceState' | |
--} | |
type alias Repo = | |
{ name: String | |
, url: String | |
} | |
type alias Model = { | |
query : String | |
, results : Maybe (List Repo) | |
, debounceState : DebounceState | |
} | |
{-- | |
DebounceState type: | |
Will 'Maybe' have the shape of a record or Nothing | |
Has a query prop that is of sting type | |
Has a prevClockTime prop that is of type Time | |
Has a elapsedTime prop that is of type Time | |
--} | |
type alias DebounceState = Maybe { | |
prevClockTime : Time | |
, elapsedTime: Time | |
} | |
{-- | |
Model (record) | |
Sets a query prop to an empty string | |
Sets results as Nothing, as this value 'maybe' a string or nothing | |
Sets debounceState to Nothing, as this type is a 'Maybe' | |
--} | |
model : (Model, Effects Action) | |
model = | |
({ | |
query = "" | |
, results = Nothing | |
, debounceState = Nothing | |
} | |
, Effects.none ) | |
{-- | |
Action = Union Type | |
It can either be: | |
Input: which is a String | |
UpdateResults: which 'might' be a String | |
--} | |
type Action = Input String | |
| UpdateResults (Maybe (List Repo)) | |
| Tick Time | |
{-- | |
debounceTime = half a second | |
--} | |
debounceTime : Time | |
debounceTime = 500 * millisecond | |
{-- | |
elapsedTimeOf | |
takes debounceState and a clockTime | |
if debounceState Nothing, return 0 | |
otherwise get 'elapsedTime' and 'prevClockTime' | |
and return 'elapsedTime' plus 'clockTime' minus 'prevClockTime' | |
--} | |
elapsedTimeOf : DebounceState -> Time -> Time | |
elapsedTimeOf debounceState clockTime = | |
case debounceState of | |
Nothing -> 0 | |
Just { elapsedTime, prevClockTime } -> elapsedTime + (clockTime - prevClockTime) | |
-- UPDATE | |
{-- | |
Update: | |
Requires an address and model | |
If the address was: | |
Input - Update the query with the accompaning string | |
UpdateResults - update results with a possible value | |
--} | |
update : Action -> Model -> (Model, Effects Action) | |
update action model = | |
case action of | |
{-- | |
on 'Input' | |
- check the model's debounceState record | |
- if it's Nothing, set the query on the model an trigger 'Tick' | |
- otherwise set the query and set 'debounceState' to Nothing and | |
don't trigger anything | |
--} | |
Input query -> | |
case model.debounceState of | |
Nothing -> | |
({ model | query = query }, Effects.tick Tick) | |
Just oldDebounceState -> | |
({ model | query = query, debounceState = Nothing }, Effects.none) | |
{-- | |
on 'UpdateResults' | |
set the results on the model, don't trigger anything | |
--} | |
UpdateResults results -> | |
({ model | results = results }, Effects.none) | |
{-- | |
on 'Tick' | |
first calculate the new newElapsedTime since debounceState was Nothing | |
if the 'newElapsedTime' in greather than 'debounceTime' (500ms) | |
- set debounceState to Nothing on the model and perform HTTP request with the query | |
otherwise set the new debounceState on the model and trigger another Tick | |
--} | |
Tick clockTime -> | |
let | |
newElapsedTime = elapsedTimeOf model.debounceState clockTime | |
in | |
if newElapsedTime > debounceTime then | |
({model | debounceState = Nothing}, search model.query) | |
else | |
({ model | debounceState = Just { elapsedTime = newElapsedTime, prevClockTime = clockTime } } | |
, Effects.tick Tick) | |
{-- | |
search: | |
Look up the github Url | |
- decode repos ? | |
- map the results | |
- I assume wrap it up as a 'Effects.task' ? | |
--} | |
search : String -> Effects Action | |
search query = | |
Http.get repos ("https://api.github.com/users/" ++ query ++ "/repos") | |
|> Task.toMaybe | |
|> Task.map UpdateResults | |
|> Effects.task | |
{-- | |
repos | |
the result of the HTTP request in an array or objects | |
[ | |
{"id": "foo", "name": "bar", "url": "baz", ... }, | |
{"id": "foo", "name": "bar", "url": "baz", ... } | |
] | |
Get the name and url values | |
--} | |
repos : Json.Decoder (List Repo) | |
repos = | |
let repo = | |
Json.object2 Repo | |
("name" := Json.string) | |
("url" := Json.string) | |
in | |
Json.list repo | |
-- VIEW | |
{-- | |
return a li for an item | |
--} | |
result item = | |
li [] [ text item.name ] | |
{-- | |
view takes a Signal Address action and a model | |
on input we explicity tell it to send the targetValue to the address action Input | |
targetValue becomes the 'query' param in our update function | |
if model.results exists, it should render a LI for each repo | |
--} | |
view: Signal.Address Action -> Model -> Html | |
view address model = | |
div [] [ | |
input [ | |
placeholder model.query | |
, value model.query | |
, on "input" targetValue (Signal.message (Signal.forwardTo address Input)) | |
] [] | |
, ul [] (List.map result (Maybe.withDefault [] model.results)) | |
] | |
-- START | |
app = | |
StartApp.start { | |
init = model | |
, view = view | |
, update = update | |
, inputs = [] | |
} | |
main = app.html | |
port tasks : Signal (Task.Task Never ()) | |
port tasks = app.tasks |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment