import Html exposing (..) import Html.Events exposing (..) import Html.Attributes exposing (href, style, src) import Task exposing (Task) import Http exposing (Error) import JavaScript.Decode exposing (..) import List as L -------------- -- HELPER CODE -------------- -- Operator from Clojure (>!) : a -> Stream.Input a -> Stream.Message (>!) a input' = Stream.message input'.address a -- Javascript Decoding Helper andMap : Decoder (a -> b) -> Decoder a -> Decoder b andMap = object2 (<|) -- Status type type Status = Initial | Loading | Ready | Failed -------- -- MODEL -------- -- The entire model for the app type alias Model = { status : Status , posts : List Post } type alias Post = { url : String , title : String , score : Int } -- The initial model initialModel : Model initialModel = { status = Initial , posts = [] } ------------------ -- Reddit Homepage ------------------ redditHomeUrl : String redditHomeUrl = "https://www.reddit.com/.json" ---------------------------- -- JSON Parsing and Decoding ---------------------------- {-| Reddit JSON data appears as : { "data": { "children": [ { "data": { "url": "www.catsareawesome.org", "title": "Cats are amazing", "score": 100 } }, { "data": { "url": "www.dogsareawesome.org", "title": "Dogs are amazing", "score": 90 } } ] } } -} type alias RedditJson = { data : RedditJsonData } redditJsonDecoder : Decoder RedditJson redditJsonDecoder = RedditJson `map` ("data" := redditJsonDataDecoder) type alias RedditJsonData = { children : List RedditJsonPost } redditJsonDataDecoder : Decoder RedditJsonData redditJsonDataDecoder = RedditJsonData `map` ("children" := list redditJsonPostDecoder) type alias RedditJsonPost = { data : RedditJsonPostData } redditJsonPostDecoder : Decoder RedditJsonPost redditJsonPostDecoder = RedditJsonPost `map` ("data" := redditJsonPostDataDecoder) type alias RedditJsonPostData = { url : String , title : String , score : Int } redditJsonPostDataDecoder : Decoder RedditJsonPostData redditJsonPostDataDecoder = RedditJsonPostData `map` ("url" := string) `andMap` ("title" := string) `andMap` ("score" := int) -------------------------- -- Convert Posts from JSON -------------------------- postsFromJson : RedditJson -> List Post postsFromJson json = let convertChild : RedditJsonPost -> Post convertChild child = { url = child.data.url , title = child.data.title , score = child.data.score } in List.map convertChild json.data.children --------- -- INPUTS --------- -- The url(s) requested by the user input urlRequests : Stream.Input String -- The results from requesting the url input results : Stream.Stream (Result Error RedditJson) input results from Stream.map requestRedditPage urlRequests.stream -------- -- TASKS -------- requestRedditPage : String -> Task Error RedditJson requestRedditPage url = Http.get redditJsonDecoder url ---------- -- ACTIONS ---------- type Action = Load | SetPosts (List Post) | Fail resultToAction : Result Error RedditJson -> Action resultToAction result = case result of Err _ -> Fail Ok json -> SetPosts (postsFromJson json) actions : Stream Action actions = Stream.map resultToAction results --------- -- UPDATE --------- update : Action -> Model -> Model update action model = case action of Load -> { model | status <- Loading } Fail -> { model | status <- Failed } SetPosts posts -> { model | status <- Ready , posts <- posts } ------- -- VIEW ------- view : Model -> Html view model = case model.status of Initial -> viewInitialPage Loading -> viewLoadingPage Ready -> viewPage model.posts Failed -> viewFailedPage viewInitialPage : Html viewInitialPage = button [ onClick (redditHomeUrl >! urlRequests) ] [ text "Get Reddit Front Page" ] viewLoadingPage : Html viewLoadingPage = text "Loading Reddit...." viewFailedPage : Html viewFailedPage = section [] [ p [] [ text "Oh noes! Request went bad!" ] , p [] [ text "Care to try again?" ] , viewInitialPage ] viewPage : List Post -> Html viewPage posts = div [] [ pageHeader , ul [] (List.map viewPost posts) ] viewPost : Post -> Html viewPost post = let scoreStyle = style [("height", "50px") ,("flex", "1") ,("align-self", "center") ] itemStyle = style [("display", "flex") ,("margin-bottom", "10px") ] linkStyle = style [("flex", "8") ,("align-self", "flex-start") ] in li [ itemStyle ] [ div [ scoreStyle ] [ text (toString post.score) ] , a [ linkStyle , href post.url ] [ div [] [text post.title] ] ] pageHeader : Html pageHeader = let headerStyle = style [ ("display", "flex") , ("align-items", "center") ] textStyle = style [ ("flex", "8") ] in header [ headerStyle ] [ elmLogo , div [ textStyle ] [ text "Reddit Client in Elm" ] ] -- The Glorious Elm logo! elmLogo : Html elmLogo = let logoStyle = style [("height", "50px") ,("width", "50px") ,("min-height", "40px") ,("min-width", "40px") ,("flex", "1") ] in img [ src "http://elm-lang.org/logo.svg" , logoStyle ][] ------- -- MAIN ------- main = Varying.map view (Stream.fold update initialModel actions)