diff --git a/examples/pokemon/src/Page/Index.elm b/examples/pokemon/src/Page/Index.elm index 03e072ab..c335d296 100644 --- a/examples/pokemon/src/Page/Index.elm +++ b/examples/pokemon/src/Page/Index.elm @@ -9,6 +9,7 @@ import OptimizedDecoder as Decode import Page exposing (Page, PageWithState, StaticPayload) import Pages.PageUrl exposing (PageUrl) import Pages.Url +import Route import Secrets import Shared import View exposing (View) diff --git a/examples/pokemon/src/Page/PokedexNumber_.elm b/examples/pokemon/src/Page/PokedexNumber_.elm new file mode 100644 index 00000000..9f0fe5a2 --- /dev/null +++ b/examples/pokemon/src/Page/PokedexNumber_.elm @@ -0,0 +1,106 @@ +module Page.PokedexNumber_ exposing (Data, Model, Msg, page) + +import DataSource exposing (DataSource) +import DataSource.Http +import Head +import Head.Seo as Seo +import Html exposing (..) +import Html.Attributes exposing (src) +import OptimizedDecoder as Decode +import Page exposing (Page, PageWithState, StaticPayload) +import Pages.PageUrl exposing (PageUrl) +import Pages.Url +import Secrets +import Shared +import View exposing (View) + + +type alias Model = + () + + +type alias Msg = + Never + + +type alias RouteParams = + { pokedexnumber : String } + + +page : Page RouteParams Data +page = + Page.prerenderedRouteWithFallback + { head = head + , routes = routes + , data = data + , handleFallback = + \{ pokedexnumber } -> + let + asNumber : Int + asNumber = + String.toInt pokedexnumber |> Maybe.withDefault -1 + in + DataSource.succeed + (asNumber > 0 && asNumber < 150) + } + |> Page.buildNoState { view = view } + + +routes : DataSource (List RouteParams) +routes = + DataSource.succeed [] + + +data : RouteParams -> DataSource Data +data routeParams = + DataSource.Http.get (Secrets.succeed ("https://pokeapi.co/api/v2/pokemon/" ++ routeParams.pokedexnumber)) + (Decode.map2 Data + (Decode.field "forms" (Decode.index 0 (Decode.field "name" Decode.string))) + (Decode.field "types" (Decode.list (Decode.field "type" (Decode.field "name" Decode.string)))) + ) + + +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 + + +type alias Data = + { name : String + , abilities : List String + } + + +view : + Maybe PageUrl + -> Shared.Model + -> StaticPayload Data RouteParams + -> View Msg +view maybeUrl sharedModel static = + { title = static.data.name + , body = + [ h1 [] + [ text static.data.name + ] + , text (static.data.abilities |> String.join ", ") + , img + [ src <| "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/" ++ static.routeParams.pokedexnumber ++ ".png" + ] + [] + ] + } diff --git a/generator/src/Page.elm b/generator/src/Page.elm index eb3909a5..3a83040b 100644 --- a/generator/src/Page.elm +++ b/generator/src/Page.elm @@ -1,7 +1,7 @@ module Page exposing ( Builder(..) , StaticPayload - , prerenderedRoute, singleRoute, serverlessRoute + , prerenderedRoute, prerenderedRouteWithFallback, singleRoute, serverlessRoute , Page, buildNoState , PageWithState, buildWithLocalState, buildWithSharedState ) @@ -30,7 +30,7 @@ But before the user even requests the page, we have the following data: - `sharedStatic` - we can access any shared data between pages. For example, you may have fetched the name of a blog ("Jane's Blog") from the API for a Content Management System (CMS). - `static` - this is the static data for this specific page. If you use `noData`, then this will be `()`, meaning there is no page-specific static data. -@docs prerenderedRoute, singleRoute, serverlessRoute +@docs prerenderedRoute, prerenderedRouteWithFallback, singleRoute, serverlessRoute ## Stateless Page Modules @@ -237,6 +237,40 @@ prerenderedRoute { data, head, routes } = } +{-| -} +prerenderedRouteWithFallback : + { data : routeParams -> DataSource data + , routes : DataSource (List routeParams) + , handleFallback : routeParams -> DataSource Bool + , head : StaticPayload data routeParams -> List Head.Tag + } + -> Builder routeParams data +prerenderedRouteWithFallback { data, head, routes, handleFallback } = + WithData + { data = data + , staticRoutes = routes + , head = head + , serverless = False + , handleRoute = + \routeParams -> + handleFallback routeParams + |> DataSource.andThen + (\handleFallbackResult -> + if handleFallbackResult then + DataSource.succeed True + + else + -- we want to lazily evaluate this in our on-demand builders + -- so we try handle fallback first and short-circuit in those cases + -- TODO - we could make an optimization to handle this differently + -- between on-demand builders and the dev server + -- we only need to match the pre-rendered routes in the dev server, + -- not in on-demand builders + routes |> DataSource.map (List.member routeParams) + ) + } + + {-| -} serverlessRoute : { data : (ServerRequest decodedRequest -> DataSource decodedRequest) -> routeParams -> DataSource data