Using preventDefault in Elm - elm

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.

Related

Elm and VSCode: Formatter messes up spacing

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.

Elm - How to use HTML events?

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)

Keyboard combinations in Elm 0.17 and later

How do I build keyboard combinations into my Elm app, eg. "shift + alt + enter"? You would do something like this to react to a single key pressed (for example the enter key):
import Keyboard
type Msg
= KeyDown Keyboard.KeyCode
type alias Model =
...
update msg model =
case msg of
KeyDown key ->
handleKeyDown key model
subscriptions model =
Sub.batch
[
Keyboard.downs KeyDown
]
handleKeyDown key model =
case key of
13 -> -- Enter key
Debug.log "Other key"
model
_ -> -- Any other key
Debug.log "Other key"
model
view model =
...
But how can you do the same for multiple keys pressed?
You could use Keyboard.downs as pdoherty926 mentioned as well as a Set to keep track of which keys are pressed. You'll need to look at Keyboard.ups as well to know when a key is released.
Here is a working example:
import Html exposing (..)
import Html.App exposing (program)
import Keyboard exposing (..)
import Set exposing (Set)
import Char
main =
program { init = (initialModel, Cmd.none), view = view, update = update, subscriptions = subscriptions }
initialModel =
{ keysDown = Set.empty
}
view : Model -> Html Msg
view model =
text <| toString <| Set.map Char.fromCode model.keysDown
type Msg
= KeyDown KeyCode
| KeyUp KeyCode
type alias Model =
{ keysDown : Set KeyCode
}
update msg model =
case msg of
KeyDown key ->
({ model | keysDown = Set.insert key model.keysDown }, Cmd.none)
KeyUp key ->
({ model | keysDown = Set.remove key model.keysDown }, Cmd.none)
subscriptions _ =
Sub.batch
[ Keyboard.downs KeyDown
, Keyboard.ups KeyUp
]
From the documentation:
Longer-term, it makes sense to help track key combinations and a couple other common scenarios. If you have a particular problem you think this library could address more directly, please describe it as a SSCCE in an issue. Please do not suggest the solution. Just describe the scenario. Once it becomes clearer what folks are up to, it will be time to add support for those cases in a coherent way.
So, at least it means that it is not supported right now. I'm not fully sure whether it also means that you should open an issue to encourage this later addition or not.
Now, Chad's answer looks like a good temporary solution to me. It looks even better with swelet's suggestion of tracking only a few modifier keys. Note how if you take this approach you don't need to rely on any List or Set but you can instead have a little record with 4 booleans.
Finally, if you want for some reasons to track all the keys (if for instance you would like the user being able to link [F1] to some action, then performance-wise it would make sense to use a more efficient representation of sets. Any subset of a finite set for instance can be defined as a single number, each bit being a boolean meaning absence or presence of one particular element. You can do such things with the Bitwise library.

How to use onWithOptions in Elm 0.17?

I have an application where I'm selecting some statuses. Initially, I had a code like this
div
[ classList
[ onClick (SelectStatus (Just status)) ]
But at some moment, I need to stop event propagation. I found that there is an onWithOptions function but I don't know how to use it. Especially what's the Decoder parameter for. I rewrite it to this form but I'm still getting some errors.
div
[ onWithOptions "click" { stopPropagation = True, preventDefault = False } keyCode (SelectStatus (Just status))
This is the error message
Function `onWithOptions` is expecting 3 arguments, but was given 4.
Maybe you forgot some parentheses? Or a comma?at line 171 col 11
Your link is pointing to an obsolete package as of Elm 0.17. Here is the correct version: http://package.elm-lang.org/packages/elm-lang/html/1.1.0/Html-Events#onWithOptions
I think this would give you the functionality you're after:
onWithOptions "click" { stopPropagation = True, preventDefault = False } (Json.succeed (SelectStatus (Just status)))

How to debounce elm signals?

Im using start-app to shape my application. Html.Events supports creating custom events with custom Signal.message. But how to send that message is abstracted behind the Html library. Also there is a library called which implements debouncing (http://package.elm-lang.org/packages/Apanatshka/elm-signal-extra/5.7.0/Signal-Time#settledAfter).
SearchBar.elm:
module PhotosApp.SearchBar (view) where
import Html exposing (input, div, button, text, Html)
import Html.Events exposing (on, onClick, targetValue)
import Html.Attributes exposing (value, type')
import Effects exposing (Effects, Never)
import PhotosApp.Actions as Actions
onTextChange : Signal.Address a -> (String -> a) -> Html.Attribute
onTextChange address contentToValue =
on "input" targetValue (\str -> Signal.message address (contentToValue str))
view : Signal.Address Actions.Action -> String -> Html
view address model =
div []
[ input [ type' "text", value model, onTextChange address Actions.Search] []
, button [ onClick address Actions.Clear ] [text "Clear"]
]
You can set up debouncing from an HTML attribute by going through a proxy mailbox before tying into #Apanatshka's settledAfter function.
(Note that since you're using StartApp, we don't have direct access to the main Address and therefore I've had to compromise a few of these functions by ignoring the address passed into view. You could get something more generalized by not using StartApp, but this should hopefully get you started)
Since event attributes cannot create tasks directly, we have to use an intermediate proxy mailbox. We can set that up like this:
debounceProxy : Signal.Mailbox Action
debounceProxy =
Signal.mailbox NoOp
Note that the above function expects a NoOp Action, common in Elm architecture, which just means the update function makes no changes to the model.
Now we need to set up another Signal which listens to the proxy mailbox, then passes the signal through the settledAfter function. We can define this signal like this:
debounce : Signal Action
debounce =
settledAfter (500 * Time.millisecond) debounceProxy.signal
We can now change the onTextChange function to point at the proxy mailbox. Notice that I've taken out the first Address parameter since it's being ignored (see my earlier comment about conforming to StartApp):
onTextChange : (String -> Action) -> Html.Attribute
onTextChange contentToValue =
on "input" targetValue (\str -> Signal.message debounceProxy.address (contentToValue str))
Lastly, you have to tie in the debounce signal into StartApp's inputs parameter, which means your call to start will look something like this:
app = StartApp.start
{ init = init
, update = update
, view = view
, inputs = [ debounce ]
}
I've pasted the full working example at a gist here.