I am attempting to upgrade an elm application from 0.18 to 0.19.
I am stuck with this error -
Detected errors in 1 module.
-- BAD IMPORT ---------------------------------------- src/Views/Interaction.elm
The `Html.Events` module does not expose `onWithOptions`:
13| import Html.Events exposing (onWithOptions)
^^^^^^^^^^^^^
These names seem close though:
onMouseEnter
onMouseLeave
onMouseOut
onMouseOver
The documentation shows that onWithOptions should be available.
My code is
module Views.Interaction exposing (onClickNoBubble)
{-| Helper functions for page interactions.
# Helpers
#docs onClickNoBubble
-}
import Html
import Html.Events exposing (onWithOptions)
import Json.Decode as Decode
{-| Replicates the onClick function but prevents bubbling
-}
onClickNoBubble : msg -> Html.Attribute msg
onClickNoBubble message =
onWithOptions "click" { stopPropagation = True, preventDefault = True } (Decode.succeed message)
How do I move forward?
Elm 0.19 does not use elm-lang/html. You're reading the wrong documentation. It has been replaced by elm/html which has a custom function that serves the same purpose:
onClickNoBubble : msg -> Html.Attribute msg
onClickNoBubble message =
Html.Events.custom "click" (Decode.succeed { message = message, stopPropagation = True, preventDefault = True })
I made a little helper function to get this to work.
onCustomClick : msg -> Html.Attribute msg
onCustomClick msg =
custom "click"
(Decode.succeed
{ message = msg
, stopPropagation = True
, preventDefault = True
}
)
Related
I am making a program that changes the rendered text on the screen to be whatever the user inputs in a text box. I think I have the model and the update part of the elm architecture correct, but I really don't understand the view portion.
I'm just having trouble wrapping my head around the square bracket view functions.
Anyway, I am getting this error.
This div call produces:
Html #(Model -> Model)#
But the type annotation on view says it should be:
Html #Msg#Elm
But I am not sure how to change my view function to return Html Msg and I am kinda confused between the difference between that and a string.
Thank you everyone!
Here is my code ...
module Main exposing (..)
import Browser
import Html exposing (Html, div, text, input, Attribute)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
main =
Browser.sandbox { init = init, update = update, view = view }
type alias Model = String
init : Model
init = "Hello, World!"
type alias Msg = String
update : Msg -> Model -> Model
update msg model =
msg
view : Model -> Html Msg
view model =
div []
[ input [ placeholder "Input new string", value model, onInput update ] []
, div [] [ text model ]
]
You're passing the update function as an argument to onInput. Your probably meant to pass it a Msg, which the runtime will then pass to the update function.
Since your Msg type is an alias for String, you can use onInput identity
So I started learning Elm today. I use VSCode as my editor.
I followed the docs for the set up and installed elm as well as el-format via npm install -g elm elm-format. I also installed the VSCode Elm extension.
Next, in my settings.json I set:
"[elm]": {
"editor.formatOnSave": true
},
Then I went on with the tutorial. In it the code is formatted like this:
import Browser
import Html exposing (Html, Attribute, div, input, text)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
-- MAIN
main =
Browser.sandbox { init = init, update = update, view = view }
-- MODEL
type alias Model =
{ content : String
}
init : Model
init =
{ content = "" }
-- UPDATE
type Msg
= Change String
update : Msg -> Model -> Model
update msg model =
case msg of
Change newContent ->
{ model | content = newContent }
-- VIEW
view : Model -> Html Msg
view model =
div []
[ input [ placeholder "Text to reverse", value model.content, onInput Change ] []
, div [] [ text (String.reverse model.content) ]
]
But when I hit safe, it formats the code like this:
module Main exposing (Model, Msg(..), init, main, update, view)
import Browser
import Html exposing (Attribute, Html, div, input, text)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
-- MAIN
main =
Browser.sandbox { init = init, update = update, view = view }
-- MODEL
type alias Model =
{ content : String
}
init : Model
init =
{ content = "" }
-- UPDATE
type Msg
= Change String
update : Msg -> Model -> Model
update msg model =
case msg of
Change newContent ->
{ model | content = newContent }
-- VIEW
view : Model -> Html Msg
view model =
div []
[ input [ placeholder "Text to reverse", value model.content, onInput Change ] []
, div [] [ text (String.reverse model.content) ]
]
So it adds extra lines, and extra module Main exposing ... and doubles the number of spaces. I tried setting spaces to 2 again using the footer in VSCode, but that didn't help.
My questions are:
Is it okay that saving adds the extra module Main ...?
Is having 2 spaces best practice / community standard, or 4?
If it is 2 (like it is in the tutorial's default code) how can I get my formatter to respect that?
If it is not, why has the tutorial the non-standard indentation?
First of all, this question is both too broad and primarily opinion-based and will probably be closed because of that. It would have been more suited for the forums I think.
That said, I'm going to try to answer it as best as I can anyway, since it's here:
Yes? Most modules won't be very useful without exposing something and it's good practice to be explicit about what's being exposed.
elm-format is the community standard, so 4 it is.
You can't. This is by design. It has also been discussed to death in various fora. Here's one issue discussing it
You'd have to ask Evan about that. It might be related to formatting for the web, or just Evan being lazy.
The Elm getting started guide doesn't seem to use the standard Elm HTML events and I'm not sure how to use them. For example, onBlur:
view model =
div []
[ div []
input [ type_ "text", onInput MsgA ] []
, input [ type_ "text", onBlur MsgB ] []
]
This fails in the compiler because now I am returning type Html (String -> Msg) on line 3 but Html (Msg) in line two.
Why are these two events incompatible? Is there any way to use both at the same time? Additionally, the docs don't make it clear enough for someone like me to understand how to use onBlur.
onBlur has a different type signature than onInput.
onInput : (String -> msg) -> Attribute msg
onBlur : msg -> Attribute msg
That means whatever Msg you use for onInput has to take a single string parameter. Likewise, the Msg used in onBlur cannot take a parameter. If you redefined MsgA and MsgB to the following, it would compile:
type Msg
= MsgA String
| MsgB
Edit
If you wanted your blur handler to be able to accept the target value, you can roll your own like this:
import Html.Events exposing (on, targetValue)
import Json.Decode as Json
onBlurWithTargetValue : (String -> msg) -> Attribute msg
onBlurWithTargetValue tagger =
on "blur" (Json.map tagger targetValue)
I wrote a simple program based on time example, to test what data are in events. It decodes JSON to value then encodes it back to JSON, then show it in SVG text element. And the only thing I get is {"isTrusted":true}.
Why that happens? How do I get another data of event? I'm using Firefox 49 and online compiler:
import Html exposing (Html)
import Svg exposing (..)
import Svg.Attributes exposing (..)
import Svg.Events exposing(on)
import Json.Decode as Json
import Json.Encode exposing (encode)
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
-- MODEL
type alias Model = String
init : (Model, Cmd Msg)
init =
("No event", Cmd.none)
-- UPDATE
type Msg
= Event String
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Event event ->
(event, Cmd.none)
subscriptions model = Sub.none
stringifyEvent: Json.Encode.Value -> Msg
stringifyEvent x =
Event (encode 2 x)
-- VIEW
view : Model -> Svg Msg
view model =
svg [ viewBox "0 0 300 300", width "300px", on "mousedown" (Json.map stringifyEvent Json.value) ] [
text_ [x "0", y "30"] [text model]
]
When I try in console
svgElement.addEventListener('click', function(e) {console.log(e)})
It works with all the attributes.
I do not know a way to achieve your goal.
But I can give you an answer why it does the way you described.
If you look at the source code, you'll find that Elm runtime uses
JSON.stringify() for converting Value to String.
And guess what...
svgElement.addEventListener('click', function(e) {console.log(JSON.stringify(e))})
will give you {"isTrusted":true} when you click...
How do I use preventDefault in elm? Say on a keyboard event:
keyDown keyCode model =
case keyCode of
13 -> -- Enter key
model
if we don't want the default behaviour?
Html.Events has methods for it, but I don't understand how to use it in practice.
This answer is obsolete with Elm 0.19
Learn how to use Html.Events.on, then it becomes obvious.
myInput =
input
[ on "keydown" (Json.map KeyInput keyCode)]
[]
becomes...
myInputWithOptions =
input
[ onWithOptions "keydown" options (Json.map KeyInput keyCode)]
[]
options =
{ stopPropagation = True
, preventDefault = True
}
(Here, message constructor KeyInput
is defined as: type Msg = KeyInput Int)
#Tosh has shown how to stop event propagation and prevent default behaviour in his answer.
If your specific case is to prevent default behaviour only on Enter but not on any other keys, this is currently not possible in Elm—you'll have to resort to Ports and JS.
You can now use the library elm-community/html-extra for that, it provides you the onClickPreventDefault and onClickStopPropagation functions.