DEV Community

Kristian Pedersen
Kristian Pedersen

Posted on • Edited on

#30daysofelm Day 1: Show range slider value in p tag

1/30: Show input value in p tag

All posts

Code+demo: https://ellie-app.com/bS7FDnKVN3ha1

My elm experience before today

I've gone through the Elm intro course on Frontend Masters, but I really don't learn much from following along.

These 30 days, I will instead come up with ideas that are suitable for my current level. That's how I learned JavaScript, so I think it's a good approach.

Disclaimer

I really don't know what I'm doing. Copy my code at your own risk.

Goal

My goal today is to have a range slider's value be shown in a p tag.

Here's the code I'll try to recreate, written in Svelte:

<script>
    let n = 0;
</script>

<input type="range" bind:value={n}>
<p>{n}</p>
Enter fullscreen mode Exit fullscreen mode

Obviously, the Elm equivalent isn't going to be nearly as simple as this, but I'm up for a good challenge.

I'm probably just going to add type annotations to my Elm code. People say they make the code more readable, and it lets the compiler provide better error messages.

Thoughts about cool/confusing aspects of Elm

This is a step-by-step walkthrough of today's code.

Many parts of the code are taken from this example: https://guide.elm-lang.org/architecture/text_fields.html

1. Import statements

module Main exposing (main)

import Browser
import Html exposing (Html, div, input, p, text)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
Enter fullscreen mode Exit fullscreen mode

if you've written HTML before, these words should be familiar. The first line was added automatically by VS Code. (..) means everything.

2. Main function / Browser.sandbox

main : Program () Model Msg
main =
    Browser.sandbox { init = initialState, update = update, view = view }
Enter fullscreen mode Exit fullscreen mode

The first line was suggested to me by VS Code, so why not. Is this a function that returns a "program parenthesis model message"? ¯\(ツ)

What's being passed in here seems like the basic Elm structure that I've heard of before: Model (state), update function and view function.

Browser.sandbox seems interesting. What other things can be added to the main function?

Do people always write update = update and view = view, or is there a more elegant way? In modern JavaScript for example, we can do this:

    // These produce the same result
    const oldWay = { update: update, view: view }
    const newWay = { update, view }
Enter fullscreen mode Exit fullscreen mode

The Browser.sandbox documentation isn't very clear to me. Is it just a construct to get something up and running for complete beginners?

3 Model

type alias Model =
    { value : String
    , message : String
    }


initialState : Model
initialState =
    { value = "0"
    , message = "Hi!"
    }
Enter fullscreen mode Exit fullscreen mode

For such a simple app, this feels unnecessary. I can do that in two lines of JavaScript. At the same time, it does look pretty neat.

From what I understood, the input event listener in Elm only returns event.target.value as a string.

I did try to convert that string to an int, but the String.toInt function returns a Maybe Int and not an Int.

I guess this would be handled with a case statement, but I'll leave that for another day.

4. Update function

update : Msg -> Model -> Model
update msg model =
    case msg of
        Change newContent ->
            { model | value = newContent }
Enter fullscreen mode Exit fullscreen mode

How is this function invoked? Is that taken care of by Browser.sandbox? Is the return value from onInput connected to it?

The phrasing "case msg of" sounds weird, not like something people would say out loud. Am I correct in thinking of it as "check the variable msg for these cases" instead?

Change newContent: I guess Change is a message or symbol, and newContent is the return value from the event listener. Is that right?

5. View function

view : Model -> Html Msg
view model =
    div []
        [ input [ type_ "range", onInput Change ] []
        , p [] [ text model.value ]
        , p [] [ text model.message ]
        ]
Enter fullscreen mode Exit fullscreen mode

I don't know exactly what an Html Msg is yet, but it's probably just HTML.

When I first saw this kind of syntax, I didn't like it at all. Now I think it's alright.

From what I understand, these HTML elements are functions that take two arguments: attributes and a list of child elements.

Having the commas at the beginning of the line is very nice.

type is probably a reserved keyword in Elm, so I guess that's why it's called type_ instead.

Summary

Today, I've taken my first steps in Elm! For such a simple project, I would much rather recommend Svelte, but I'm sure Elm is very useful in more complex projects.

Even before I started this challenge today, I missed Elm:

Earlier today, I was working on an Express/React application for a technical interview. As usual, the React compiler said everything was okay, and a few clicks later - a huge red Chrome screen because of a runtime error.

I don't know what tomorrow's Elm project will be, but it won't have any runtime errors.

Please let me know if you spot any errors, misunderstandings or ugly code! :)

Here's today's full code:

module Main exposing (main)

import Browser
import Html exposing (Html, div, input, p, text)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)


main : Program () Model Msg
main =
    Browser.sandbox { init = initialState, update = update, view = view }


type alias Model =
    { value : String
    , message : String
    }


initialState : Model
initialState =
    { value = "0"
    , message = "Hi!"
    }


type Msg
    = Change String


update : Msg -> Model -> Model
update msg model =
    case msg of
        Change newContent ->
            { model | value = newContent }


view : Model -> Html Msg
view model =
    div []
        [ input [ type_ "range", onInput Change ] []
        , p [] [ text model.value ]
        , p [] [ text model.message ]
        ]
Enter fullscreen mode Exit fullscreen mode

Top comments (4)

Collapse
 
bukkfrig profile image
bukkfrig

Whenever a type has multiple terms, it means its a generic type.

If you're familiar with Java style generics, then List Int in Elm would be Java's List<Integer>. The purpose of generics is so that we can define what a List is once, but on different instances of it the operations (like push and get) have their type signatures updated so that they accept or return different things, like Int values in our case. We didn't have to define a IntegerList and a StringList and a FooList and so on, just List a in Elm or List<T> in Java.

So similarly, Html Msg would be like Html<Msg> in Java. This is some data structure for holding HTML nodes, where some of it's operations (related to event listeners in this case) can produce values that have the type Msg. (Msg is not a special type, you defined it yourself with type Msg = Change String!).

And still similarly, Program is a generic type with 3 type parameters, so Program Flags Model Msg in Elm is like Program<Flags, Model, Msg> in Java. It's a Program that uses some type called Flags in some of it's operations that deal with program startup, some type called Model in some other operations that deal with state, and some type called Msg in other operations that deal with messaging. (Again Model and Msg are simply defined by you!)

All that's left to explain is (), which is also less special than it might first appear. It's just a type that can only have one value - () is the name of the type, and of the value. It just gets called the "unit" type, and the value is actually an empty Tuple. So operations on a Program () Model Msg can only ever get one value, the empty Tuple (), in its operations that deal with starting up - and so a Program generated by Browser.sandbox always starts up the same.

So all of this is a bit like public static void main(String[] args) in Java. As a beginner, you just write it because somebody told you to and the language demands it. Eventually you learn about when you should make things public and when static and when should they return void and how to use String[] arrays, and then you look at the boilerplate code and understand that those things really do (have to) apply to main. Same with Elm's boilerplate eventually, but don't worry too much about it as a beginner.

Collapse
 
wolfadex profile image
Wolfgang Schuster

Really nice to read something from your point of view of being new to Elm.

For the Int vs String with your value you can do, as you said, and use case .. of. Or you could use things from Maybe. With case .. of you might end up with

{ model
    | value =
        case String.toInt newContent of
            Just int -> int
            Nothing -> model.value
}
Enter fullscreen mode Exit fullscreen mode

This means that the value would only change when it gets valid input. Another option would be to have a default when a bad input is given, like so

{ model
    | value =
        case String.toInt newContent of
            Just int -> int
            Nothing -> 0
}
Enter fullscreen mode Exit fullscreen mode

which could be done instead with Maybe functions like so

{ model
    | value = Maybe.withDefault 0 (String.toInt newContent)
}
Enter fullscreen mode Exit fullscreen mode

or if you wanted to write it without parentheses

{ model
    | value =
        newContent
            |> String.toInt
            |> Maybe.withDefault 0
}
Enter fullscreen mode Exit fullscreen mode

I also completely agree with you when you said

For such a simple app, this feels unnecessary. I can do that in two lines of JavaScript.

Elm really shines when you're building something medium to large and not something tiny like an input with a message. Hope your journey with Elm continues to be fun.

Collapse
 
kodefant profile image
Lars Lillo Ulvestad

Seems like you have grasped a lot of the essence. And you are right, such a simple application would probably be simpler in Svelte.

Elm excels when your app reaches a certain complexity and it keeps on being fun :)

Sandbox pretty much gives you the minimal of what Elm can offer. To describe it in JavaScript terms, it lets you set and update state, but nothing else, like it can't make Http requests or talk with the outside world in any way.

Collapse
 
ericfraselle profile image
Eric Fraselle

First of all, it's a great job you're doing.
Just one thing, the range is not at the right place at start, thus you can do this in the view: [ input [ type_ "range", value model.value, onInput Change ] []