Skip to content

Browser Applications

Gren has a few different ways you can write browser applications. We’ll go over each one here, starting with the simplest: sandboxes.

First we need to init a new project. In an empty directory, run:

Terminal window
gren init

This will create a gren.json file and a src directory to hold your source code.

Browser.sandbox

A sandbox is a simplified type of program that can update HTML and react to user interaction, but not much else. We’ll go over the other types of programs in later sections.

Below is an example sandbox program with buttons to increase or decrease a number. It will look like this:

Plus and minus buttons with a zero between them

Here’s the full program. It’s ok if you don’t understand it yet, we will go through it piece by piece in later sections.

src/Main.gren
module Main exposing (main)
import Browser
import Html exposing (Html)
import Html.Attributes as Attribute
import Html.Events as Event
main =
Browser.sandbox
{ init = init
, update = update
, view = view
}
type alias Model =
Int
init : Model
init =
0
type Msg
= Increment
| Decrement
update : Msg -> Model -> Model
update msg model =
when msg is
Increment ->
model + 1
Decrement ->
model - 1
view : Model -> Html Msg
view model =
Html.div []
[ Html.button
[ Event.onClick Decrement ]
[ Html.text "-" ]
, Html.text (String.fromInt model)
, Html.button
[ Event.onClick Increment ]
[ Html.text "+" ]
]

Compile

To run the program you need to compile it. First, save the above code in src/Main.gren. Then compile and open the resulting html file:

Terminal window
gren make src/Main.gren
open index.html

Try clicking the buttons and you should see the number increase and decrease. In the following sections we’ll go over each part of the program to see how it works.

Module definition

src/Main.gren
module Main exposing (main)

This is our module declaration and it is exposing our main function which has our program definition.

Imports

src/Main.gren
import Browser
import Html exposing (Html)
import Html.Attributes as Attribute
import Html.Events as Event

Here we’re importing other modules we will need and creating some helpful aliases for the longer ones.

Main function

src/Main.gren
main =
Browser.sandbox
{ init = init
, update = update
, view = view
}

The main function is where Gren expects to find your program definition. Here we’re defining a sandbox program.

We’re defining the program with a record that points to our init, update, and view functions. These correspond to the components of the elm architecture and we’ll describe them next.

The Model

src/Main.gren
type alias Model =
Int
init : Model
init =
0

Here we’re defining a type alias for our Model.

Your model holds your application state and can be anything. Often you will use a record to hold multiple values, but in our case we only care about the current value of the counter, so we’re just aliasing an Int.

The init function returns the initial value of the model when the program starts. In this case, we’re starting at zero.

Update

src/Main.gren
type Msg
= Increment
| Decrement
update : Msg -> Model -> Model
update msg model =
when msg is
Increment ->
model + 1
Decrement ->
model - 1

This is the “update” part of the elm architecture. Msg is a custom type holding all the events that can happen in the system. When a user triggers one of those messages from the view, Gren calls update to get a new model and will update the view accordingly. Here we’re pattern matching on the message to return a new model that’s either incremented or decremented by 1.

View

src/Main.gren
view : Model -> Html Msg
view model =
Html.div []
[ Html.button
[ Event.onClick Decrement ]
[ Html.text "-" ]
, Html.text (String.fromInt model)
, Html.button
[ Event.onClick Increment ]
[ Html.text "+" ]
]

This is the “view” part of the elm architecture. It’s a function that, given the current model, returns a UI: a visual representation of your current application state, with triggers to fire messages that can update that state.

For browser applications, the view needs to return HTML. The Html module has functions for all of the HTML elements. Most of them take two parameters: an array of attributes, and the contents of the element (for many the contents will be another array of elements).

Our view returns a div with three elements.

The first element in the div is a button:

src/Main.gren
Html.div []
[ Html.button
[ Event.onClick Decrement ]
[ Html.text "-" ]
, Html.text (String.fromInt model)
, Html.button
[ Event.onClick Increment ]
[ Html.text "+" ]
]

The first parameter passed to the button function is an array holding one attribute: an onClick event so when a user clicks the button, our update function will be called with our Decrement message. The second parameter is another array for the contents of the button. In our case that’s just some text with a minus sign.

The second element of our div is some more text showing the current count. We get that by converting our model (an Int) to a string:

src/Main.gren
Html.div []
[ Html.button
[ Event.onClick Decrement ]
[ Html.text "-" ]
, Html.text (String.fromInt model)
, Html.button
[ Event.onClick Increment ]
[ Html.text "+" ]
]

The third element of our div is another button. It’s similar to the first, but clicking it will send the Increment message:

src/Main.gren
Html.div []
[ Html.button
[ Event.onClick Decrement ]
[ Html.text "-" ]
, Html.text (String.fromInt model)
, Html.button
[ Event.onClick Increment ]
[ Html.text "+" ]
]

This should be enough to get you started writing browser applications in Gren. Always reach out if you have any questions.

Browser.element

If functions always have the same output for any given input, can’t raise exceptions, and can’t change anything, how do you do things like get a random number? or make an HTTP call? That requires interacting with the Gren Runtime. This is what Browser.element allows us to do.

Here’s a new program using Browser.element that generates random numbers. We’ll explain it piece by piece in the next sections.

module Main exposing (main)
import Browser
import Html exposing (Html)
import Html.Attributes as Attribute
import Html.Events as Event
import Random
main =
Browser.element
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
}
type alias Model =
Int
init : {} -> { model : Model, command : Cmd Msg }
init _ =
{ model = 0
, command = Cmd.none
}
type Msg
= ClickedButton
| GotRandomNumber Int
getRandomNumber : Cmd Msg
getRandomNumber =
Random.generate GotRandomNumber (Random.int 0 100)
update : Msg -> Model -> { model : Model, command : Cmd Msg }
update msg model =
when msg is
ClickedButton ->
{ model = model
, command = getRandomNumber
}
GotRandomNumber n ->
{ model = n
, command = Cmd.none
}
view : Model -> Html Msg
view model =
Html.div []
[ Html.button
[ Event.onClick ClickedButton ]
[ Html.text "Get random number" ]
, Html.text " "
, Html.text (String.fromInt model)
]
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.none

You can save the above to src/Main.gren and compile and run it the same way you did the sandbox program above.

Notice that we changed from a sandbox program to an element:

main =
Browser.sandbox
Browser.element
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
}

Unlike a sandbox, elements have the ability to send commands from the init and update functions. We’ll cover those in the next sections, as well as that new subscriptions function.

Init

One of the places you can send commands is from init. For now we don’t want to do anything on init, so we’re sending Cmd.none:

src/Main.gren
init : {} -> { model : Model, command : Cmd Msg }
init _ =
{ model = 0
, command = Cmd.none
}

That new parameter to init is a flag. We’re not using flags in this program so we use {} in the type signature and _ as the name, which is usually what you see for parameters that are required but aren’t used.

Update

The other place we can send commands is update.

src/Main.gren
update : Msg -> Model -> { model : Model, command : Cmd Msg }
update msg model =
when msg is
ClickedButton ->
{ model = model
, command = getRandomNumber
}
GotRandomNumber n ->
{ model = n
, command = Cmd.none
}

When the user clicks the button, update is called with a ClickedButton message, and we respond with a call to another function we wrote, getRandomNumber that will give us a command that tells the runtime what kind of random number we want, and what type of message to send it back to us with. We’ll explain that function in the next section.

The GotRandomNumber case will be triggered when we get the random number from the previous case. We’re destructuring it to get the number and update the model.

Random Numbers

Because functions must always return the same value for a given input, we can’t write a function that returns a random number directly. Instead, we write a function that returns a command to the runtime, and the runtime calls our update function with a message holding the random value. This may seem convoluted at first, but it’s a common pattern in Gren that helps keep your code deterministic, and therefore more reliable and easier to reason about.

Here’s where we’re creating the command to get random numbers:

src/Main.gren
getRandomNumber : Cmd Msg
getRandomNumber =
Random.generate GotRandomNumber (Random.int 0 100)

This might look confusing, so we’ll take it one step at a time.

getRandomNumber : Cmd Msg: The signature tells us this function returns a command, and that command could trigger one of our Msg variants.

Random.generate: This is a function in the core package. It takes two parameters: the message we want to receive that will hold our random number, and a generator that says what type of random value we want.

GotRandomNumber: This is the message we want to receive from the runtime with our random number. It’s one of the variants of our Msg type that can hold an integer:

src/Main.gren
type Msg
= ClickedButton
| GotRandomNumber Int

(Random.int 0 100): This creates a generator that produces random integers between 0 and 100.

Subscriptions

The other difference between element and sandbox is the subscription function:

src/Main.gren
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.none

We’re not using subscriptions in this program so we’re returning Sub.none.

See the subscriptions chapter if you want to know how they work.

Browser.document

The third type of browser program is a Document. You would use this if you need to control the <title> tag.

To change the above program to a Document, update your main function to call Browser.document:

main =
Browser.element
Browser.document
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
}

And update the view to return a record with the page’s title and an array of elements for the body:

view : Model -> Html Msg
view : Model -> { title : String, body : Array (Html Msg) }
view model =
{ title = "Random Number Generator"
, body = body
}
body : Array (Html Msg)
body =
Html.div []
[ Html.button
[ Event.onClick ClickedButton ]
[ Html.text "Get random number" ]
, Html.text " "
, Html.text (String.fromInt model)
]

Browser.application

The last type of browser program is an application. This is appropriate for single-page applications (SPA) where you want to manage navigation and serve all the pages client-side.

Here’s a full application with two pages. We’ll go through the differences between it and a document program in the next sections.

src/Main.gren
module Main exposing (main)
import Browser exposing (Document, UrlRequest(..))
import Browser.Navigation as Nav
import Html exposing (Html)
import Html.Attributes as Attribute
import Html.Events as Event
import Url exposing (Url)
main =
Browser.application
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
, onUrlRequest = UrlRequested
, onUrlChange = UrlChanged
}
type alias Model =
{ page : Page
, navKey : Nav.Key
}
type Page
= Home
| About
| NotFound
pageFromUrl : Url -> Page
pageFromUrl url =
when url.path is
"/" -> Home
"/about" -> About
_ -> NotFound
init : {} -> Url -> Nav.Key -> { model : Model, command : Cmd Msg }
init _ url key =
{ model =
{ page = pageFromUrl url
, navKey = key
}
, command = Cmd.none
}
type Msg
= UrlRequested UrlRequest
| UrlChanged Url
update : Msg -> Model -> { model : Model, command : Cmd Msg }
update msg model =
when msg is
UrlRequested urlRequest ->
{ model = model
, command =
when urlRequest is
Internal url ->
Nav.pushUrl model.navKey (Url.toString url)
External url ->
Nav.load url
}
UrlChanged url ->
{ model = { model | page = pageFromUrl url }
, command = Cmd.none
}
view : Model -> Document Msg
view model =
when model.page is
Home ->
{ title = "Home"
, body = [ Html.text "Welcome to my home page" ]
}
About ->
{ title = "About"
, body = [ Html.text "This is an example website" ]
}
NotFound ->
{ title = "Not Found"
, body = [ Html.text "Can't find that url" ]
}
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.none

Running the application

First we need to install the url package that provides the Url module we’re using:

Terminal window
gren package install gren-lang/url

Then we’ll compile this program just like the others:

Terminal window
gren make src/Main.gren

Next, we need a real HTTP server. Browser.application gives you full control over the URL and address bar, which won’t work with the file:/// URLs we’ve been using so far. We’ll use the serve package on npm for that:

Terminal window
npm install serve

and run it in SPA-mode, which will direct all URL requests to our index.html file:

npx serve -s

Now you can try out the routes:

The biggest difference between an application and a document is an application will capture link clicks and URL changes and send us messages instead of reloading the page, so we can handle them however we want. So we need to specify the messages we expect:

src/Main.gren
main =
Browser.application
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
, onUrlRequest = UrlRequested
, onUrlChange = UrlChanged
}
src/Main.gren
type Msg
= UrlRequested UrlRequest
| UrlChanged Url

The onUrlRequest message will be triggered when someone clicks a link. The onUrlChange message will be triggered when we programmatically change the URL. We will handle those messages in our update function, which we’ll cover later.

We also wrote a custom type to track which page we’re on, and a function to route URL paths to the appropriate page:

src/Main.gren
type Page
= Home
| About
| NotFound
pageFromUrl : Url -> Page
pageFromUrl url =
when url.path is
"/" -> Home
"/about" -> About
_ -> NotFound

Init

Our init function is doing more now:

src/Main.gren
init : {} -> Url -> Nav.Key -> { model : Model, command : Cmd Msg }
init _ url key =
{ model =
{ page = pageFromUrl url
, navKey = key
}
, command = Cmd.none
}

It’s receiving the initial URL, which we’re using to set the starting page for our site.

It also receives a navigation key. We’re saving it because it’s required to call functions that change the URL. This is to make sure only Browser.application programs, which are equipped to detect URL changes, can change the URL and affect history.

Update

This is where we respond to link clicks and URL changes:

src/Main.gren
update : Msg -> Model -> { model : Model, command : Cmd Msg }
update msg model =
when msg is
UrlRequested urlRequest ->
{ model = model
, command =
when urlRequest is
Internal url ->
Nav.pushUrl model.navKey (Url.toString url)
External url ->
Nav.load url
}
UrlChanged url ->
{ model = { model | page = pageFromUrl url }
, command = Cmd.none
}
  1. This is the message we receive when someone clicks a link (because we set onUrlRequest = UrlRequested in main). It holds a Browser.UrlRequest.
  2. If the URL request is internal - meaning a path on this domain - we send a pushUrl command to the runtime. This will change the URL and add a new entry to the browser history, but it won’t reload the page (instead, we’ll get our UrlChanged message).
  3. If the URL request is external - meaning a path on a different domain - we send a load command. This will make the browser navigate to that URL.
  4. This is the message we receive when the URL changes via pushUrl (because we set onUrlChange = UrlChanged in main). It holds a Url and we use that to update the page value on our model. The view will handle rendering the appropriate page, all without a page reload!

View

The view is very similar to the document version, but we’re matching the page on our model to decide what the title and body should be:

src/Main.gren
view : Model -> Document Msg
view model =
when model.page is
Home ->
{ title = "Home"
, body = [ Html.text "Welcome to my home page" ]
}
About ->
{ title = "About"
, body = [ Html.text "This is an example website" ]
}
NotFound ->
{ title = "Not Found"
, body = [ Html.text "Can't find that url" ]
}

If your pages start to become more complicated, you may want to put them in their own modules. This is explained on the nested TEA page.