Browser

This module helps you set up an Gren Program with functions like sandbox and document.

Sandboxes

sandbox :
{ init : model
, view : model -> Html msg
, update : msg -> model -> model
}
-> Program {} model msg

Create a “sandboxed” program that cannot communicate with the outside world.

This is great for learning the basics of The Elm Architecture, which Gren uses for structuring applications. You can see sandboxes in action in the following examples:

Elements

element :
{ init : flags -> { model : model, command : Cmd msg }
, view : model -> Html msg
, update : msg -> model -> { model : model, command : Cmd msg }
, subscriptions : model -> Sub msg
}
-> Program flags model msg

Create an HTML element managed by Gren. The resulting elements are easy to embed in larger JavaScript projects, and lots of companies that use Gren started with this approach! Try it out on something small. If it works, great, do more! If not, revert, no big deal.

Unlike a sandbox, an element can talk to the outside world in a couple ways:

  • Cmd — you can “command” the Gren runtime to do stuff, like HTTP.
  • Sub — you can “subscribe” to event sources, like clock ticks.
  • flags — JavaScript can pass in data when starting the Gren program
  • ports — set up a client-server relationship with JavaScript

As you read the guide you will run into a bunch of examples of element in this section. You can learn more about flags and ports in the interop section.

Documents

document :
{ init : flags -> { model : model, command : Cmd msg }
, view : model -> Document msg
, update : msg -> model -> { model : model, command : Cmd msg }
, subscriptions : model -> Sub msg
}
-> Program flags model msg

Create an HTML document managed by Gren. This expands upon what element can do in that view now gives you control over the <title> and <body>.

type alias Document msg = { title : String, body : Array (Html msg) }

This data specifies the <title> and all of the nodes that should go in the <body>. This means you can update the title as your application changes. Maybe your "single-page app" navigates to a "different page", maybe a calendar app shows an accurate date in the title, etc.

Note about CSS: This looks similar to an <html> document, but this is not the place to manage CSS assets. If you want to work with CSS, there are a couple ways:

  1. Packages like rtfeldman/elm-css give all of the features of CSS without any CSS files. You can add all the styles you need in your view function, and there is no need to worry about class names matching.

  2. Compile your Gren code to JavaScript with gren make --output=gren.js and then make your own HTML file that loads gren.js and the CSS file you want. With this approach, it does not matter where the CSS comes from. Write it by hand. Generate it. Whatever you want to do.

  3. If you need to change <link> tags dynamically, you can send messages out a port to do it in JavaScript.

The bigger point here is that loading assets involves touching the <head> as an implementation detail of browsers, but that does not mean it should be the responsibility of the view function in Gren. So we do it differently!

Applications

application :
{ init : flags -> Url -> Key -> { model : model, command : Cmd msg }
, view : model -> Document msg
, update : msg -> model -> { model : model, command : Cmd msg }
, subscriptions : model -> Sub msg
, onUrlRequest : UrlRequest -> msg
, onUrlChange : Url -> msg
}
-> Program flags model msg

Create an application that manages Url changes.

When the application starts, init gets the initial Url. You can show different things depending on the Url!

When someone clicks a link, like <a href="/home">Home</a>, it always goes through onUrlRequest. The resulting message goes to your update function, giving you a chance to save scroll position or persist data before changing the URL yourself with pushUrl or load. More info on this in the UrlRequest docs!

When the URL changes, the new Url goes through onUrlChange. The resulting message goes to update where you can decide what to show next.

Applications always use the Browser.Navigation module for precise control over Url changes.

More Info: Here are some example usages of application programs:

These are quite advanced Gren programs, so be sure to go through the guide first to get a solid conceptual foundation before diving in! If you start reading a calculus book from page 314, it might seem confusing. Same here!

Note: Can an element manage the URL too? Read this!

type UrlRequest
= Internal Url
| External String

All links in an application create a UrlRequest. So when you click <a href="/home">Home</a>, it does not just navigate! It notifies onUrlRequest that the user wants to change the Url.

Internal vs External

Imagine we are browsing https://example.com. An Internal link would be like:

  • settings#privacy
  • /home
  • https://example.com/home
  • //example.com/home

All of these links exist under the https://example.com domain. An External link would be like:

  • https://gren-lang.org/examples
  • https://other.example.com/home
  • http://example.com/home

Anything that changes the domain. Notice that changing the protocol from https to http is considered a different domain! (And vice versa!)

Purpose

Having a UrlRequest requires a case in your update like this:

import Browser exposing (..)
import Browser.Navigation as Nav
import Url

type Msg
    = ClickedLink UrlRequest

update : Msg -> Model -> { model : Model, command : Cmd msg }
update msg model =
    case msg of
        ClickedLink urlRequest ->
            case urlRequest of
                Internal url ->
                    { model = model
                    , command = Nav.pushUrl model.key (Url.toString url)
                    }

                External url ->
                    { model = model
                    , command = Nav.load url
                    }

This is useful because it gives you a chance to customize the behavior in each case. Maybe on some Internal links you save the scroll position with Browser.Dom.getViewport so you can restore it later. Maybe on External links you persist parts of the Model on your servers before leaving. Whatever you need to do!

Note: Knowing the scroll position is not enough to restore it! What if the browser dimensions change? The scroll position will not correlate with “what was on screen” anymore. So it may be better to remember “what was on screen” and recreate the position based on that. For example, in a Wikipedia article, remember the header that they were looking at most recently. Browser.Dom.getElement is designed for figuring that out!