Elm-pages: Dynamic sub page

Greetings, everyone!

I’m building a website with elm-pages and want to use Mastodon as my commenting platform. To do so, I want to load & display posts from there dynamically, which works reasonably well in an Ellie example.

What I do struggle with though is embedding this logic in an elm-pages project. Here is my code (using elm-ui):

module Page.Playground exposing (Data, Model, Msg, page)

import Browser.Navigation
import DataSource exposing (DataSource)
import Element exposing (text)
import Head
import Head.Seo as Seo
import Http
import Json.Decode exposing (Decoder, at, field, string)
import Page exposing (Page, PageWithState, StaticPayload)
import Pages.PageUrl exposing (PageUrl)
import Pages.Url
import Shared
import View exposing (View)


-- TYPES AND ASSOCIATED FUNCTIONS --

type alias Model =
    ()


type alias Msg =
    Never


type alias RouteParams =
    {}


type alias Toot =
    { author : String, content : String }


fetchToots : Cmd TootMsg
fetchToots =
    Http.get
        { url = "https://metalhead.club/api/v1/accounts/31437/statuses"
        , expect = Http.expectJson GotToots tootsDecoder
        }


tootsDecoder : Decoder (List Toot)
tootsDecoder =
    Json.Decode.list
        (Json.Decode.map2
            Toot
            (at [ "account", "username" ] string)
            (field "content" string)
        )


type TootModel
    = List Toot
    | Loading


type TootMsg
    = FetchToots
    | GotToots (Result Http.Error (List Toot))


type alias Data =
    ()


-- ELM AND ELM-PAGES FUNCTIONS --

init : Maybe PageUrl -> Shared.Model -> StaticPayload templateData routeParams -> ( TootModel, Cmd TootMsg )
init maybePageUrl model staticPayload =
    ( Loading, fetchToots )


update : PageUrl -> Maybe Browser.Navigation.Key -> Shared.Model -> StaticPayload templateData routeParams -> templateMsg -> templateModel -> ( TootModel, Cmd TootMsg )
update pageUrl maybeKey model staticPayload templateMsg templateModel =
    case templateMsg of
        _ ->
            ( Loading, fetchToots )



view :
    Maybe PageUrl
    -> Shared.Model
    -> templateModel
    -> StaticPayload Data RouteParams
    -> View TootMsg
view maybeUrl sharedModel templateModel static =
    { title = "Play"
    , body = [ text "Play!" ] -- todo
    }

page : Page.PageWithState RouteParams Data TootModel TootMsg
page =
    Page.single
        { head = head
        , data = data
        }
        |> Page.buildWithLocalState
            { init = \_ _ _ -> ( Loading, Cmd.none )
            , subscriptions = \_ _ _ _ -> Sub.none
            , update = update
            , view = view
            }


data : DataSource Data
data =
    DataSource.succeed ()


head :
    StaticPayload Data RouteParams
    -> List Head.Tag
head static =
    Seo.summary
        { canonicalUrlOverride = Nothing
        , siteName = "elm-pages"
        , image =
            { url = Pages.Url.external "TODO"
            , alt = "elm-pages logo"
            , dimensions = Nothing
            , mimeType = Nothing
            }
        , description = "TODO"
        , locale = Nothing
        , title = "TODO title" -- metadata.title -- TODO
        }
        |> Seo.website

When doing so, I run into an error that’s only displayed in the terminal window running npm start while not rendering any content for the dev server to serve:

Ran elm-review in 861ms
Trace: Unhandled:  undefined
    at process.<anonymous> (/home/aarkon/src/website/node_modules/elm-pages/generator/src/build.js:25:11)
    at process.emit (node:events:513:28)
    at emit (node:internal/process/promises:150:20)
    at processPromiseRejections (node:internal/process/promises:284:27)
    at process.processTicksAndRejections (node:internal/process/task_queues:96:32)

This error looks to be rooted deep in the bowels of some internal logic of elm-pages that I can’t imagine how to take apart myself. I’d be therefore glad to be shown where my assumptions about e.g. what type to put in where are wrong. Thanks in advance!

Hi @Aarkon, for your Page Modules you’ll need to expose Model. I suspect there may be some other compiler error or something like an incompatible version of elm-review throwing it off, usually the elm-pages dev server runs elm-review if it runs into a compiler error to check that you’re exposing all of the needed values from the module so it can tell you you’re missing an expose so it can provide a more meaningful error message to the user.

Feel free to ask questions in the #elm-pages Slack channel as well, that’s usually a better place to get unstuck with questions like this.

And to clarify, you’re not using your Model is never used, TootModel is the type that’s actually being used there.

The type of your Model for your page needs to be named Model. If you wanted to you could do type alias Model = TootModel, or you could rename TootModel to Model and delete type alias Model = ().

Hope that helps!

Also, a lot of these error reports are better in the upcoming v3 release, so hopefully you’ll have a better experience once v3 is out :smiley:

1 Like

Thanks for joining in!

You were right that I had to use Model instead of my own type, but also Msg instead of TootMsg. The type signatures in the library made me believe I had to define something separate. Still looking forward to elm-pages v3 though! :slight_smile:

For the sake of completeness, this code does what the example does, but under the elm-pages paradigma:

module Page.Playground exposing (Data, Model, Msg, page)

import Browser.Navigation
import DataSource exposing (DataSource)
import Element exposing (Element, column, row, text)
import Head
import Head.Seo as Seo
import Http
import Json.Decode exposing (Decoder, at, field, string)
import Page exposing (Page, PageWithState, StaticPayload)
import Pages.PageUrl exposing (PageUrl)
import Pages.Url
import Shared
import View exposing (View)


type alias RouteParams =
    {}



-- MODEL


type alias Toot =
    { author : String, content : String }


tootsDecoder : Decoder (List Toot)
tootsDecoder =
    Json.Decode.list
        (Json.Decode.map2
            Toot
            (at [ "account", "username" ] string)
            (field "content" string)
        )


type Model
    = Failure
    | Loading
    | Success (List Toot)


fetchToots : Cmd Msg
fetchToots =
    Http.get
        { url = "https://metalhead.club/api/v1/accounts/31437/statuses"
        , expect = Http.expectJson GotToots tootsDecoder
        }


type Msg
    = GotToots (Result Http.Error (List Toot))


update :
    PageUrl
    -> Maybe Browser.Navigation.Key
    -> Shared.Model
    -> StaticPayload templateData routeParams
    -> Msg
    -> Model
    -> ( Model, Cmd Msg )
update _ _ _ _ msg _ =
    case msg of
        GotToots result ->
            case result of
                Ok toots ->
                    ( Success toots, Cmd.none )

                Err _ ->
                    ( Failure, Cmd.none )


page : Page.PageWithState RouteParams Data Model Msg
page =
    Page.single
        { head = head
        , data = data
        }
        |> Page.buildWithLocalState
            { init = \_ _ _ -> ( Loading, fetchToots )
            , subscriptions = \_ _ _ _ -> Sub.none
            , update = update
            , view = view
            }


type alias Data =
    ()


data : DataSource Data
data =
    DataSource.succeed ()


head :
    StaticPayload Data RouteParams
    -> List Head.Tag
head _ =
    Seo.summary
        { canonicalUrlOverride = Nothing
        , siteName = "elm-pages"
        , image =
            { url = Pages.Url.external "TODO"
            , alt = "elm-pages logo"
            , dimensions = Nothing
            , mimeType = Nothing
            }
        , description = "TODO"
        , locale = Nothing
        , title = "TODO title" -- metadata.title -- TODO
        }
        |> Seo.website


view :
    Maybe PageUrl
    -> Shared.Model
    -> Model
    -> StaticPayload Data RouteParams
    -> View Msg
view _ sharedModel model _ =
    { title = "Play"
    , body = [ body model ]
    , english = sharedModel.english
    }


body model =
    case model of
        Failure ->
            text "I was unable to load the toots."

        Loading ->
            text "Loading..."

        Success toots ->
            column [] <| tootView toots


tootView : List Toot -> List (Element Msg)
tootView toots =
    List.map
        (\toot ->
            row []
                [ text toot.author
                , text ": "
                , text toot.content
                ]
        )
        toots

1 Like