Elm and VSCode: Formatter messes up spacing - elm

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.

Related

Incorrect View Functon Return Type in elm

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

How to integrate multiple msg types from different elm features into a top-level app

I have a feature called Editor that I'm trying to plug into my app. Its view returns a the type Html EditorMsg. I plug it in here:
edit : Editor.Types.Model -> Html EditorMsg
edit editorModel =
div [ class "edit" ] [ Editor.view editorModel ]
In the app, I have routing and so edit is called by way of my main view function, plus a couple of functions that manage which route to show:
-- VIEW
view : Model -> Html Msg
view model =
div []
[ RoutingMsg navBar
, EditorMsg (render model)
]
render : Model -> Html EditorMsg
render model =
case model.route of
Nothing ->
li [] [ text "Invalid URL" ]
Just route ->
showRoute route model
showRoute : Route -> Model -> Html EditorMsg
showRoute route model =
case route of
Home ->
home
Editor ->
edit model.editor
My view also contains a navBar as you can see, and that returns Html possibly containing a different type of message, RoutingMsg:
navBar : Html RoutingMsg
navBar =
NavBarState.config
|> NavBarState.items
[ navItem "/" "Home" False
, navItem "/editor" "Edit" False
]
|> NavBar.view
This setup didn't compile because in view I have a list of two different types, one returning Html RoutingMsg and the other Html EditorMsg, so I set up a union type to try to contain that difference:
type Msg
= RoutingMsg (Html RoutingMsg)
| EditorMsg (Html EditorMsg)
This seems to work, but then I run into trouble in my update method, where the matching doesn't quite work. Long and short of it is that it feels as though I've just got the wrong pattern. My goal was to make my Editor somewhat independent, like a module, that can be plugged in different places. But it's hard for me to understand in Elm, how to integrate things.
To illustrate the problem more simply, I created this example on ellie-app that approximates what I tried to do but can't get working: https://ellie-app.com/PKmzsV3PC7a1/1
Is my approach here just incorrect, or if not, how can I get this code to work?
You should use Html.map to map children messages to the top-level Msg
Here's what you've been missing:
view : Model -> Html Msg
view model =
div []
[ Html.map ButtonDecMsg buttonDec
, div [] [ text (toString model) ]
, Html.map ButtonIncMsg buttonInc
]
Also the type annotation definition of child update functions should include the message type:
buttonDecUpdate : ButtonDecMsg -> Model -> Int
buttonDecUpdate msg model =
model - 1
Here is an example of working app: https://ellie-app.com/PM4H2dpFsfa1/0

Elm no data except isTrusted in JSON events

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 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.

Simplifying button that when clicked changes boolean value

I've created a button that holds a boolean value and when you click it, it changes this value and text inside the button. The code below works but I have a feeling I'm doing an overkill. How can I write this button as simple as possible but still following the ELM architecture?
module BtnPin where
import Html exposing ( Html )
import Html.Events as E
import StartApp.Simple as StartApp
-- MAIN
main =
StartApp.start { model = emptyModel, view = view, update = update }
-- MODEL
type alias Model =
{pinned : Bool}
init : Model
init = Model False
emptyModel : Model
emptyModel =
{ pinned = False
}
pin : Model -> Model
pin model =
if model.pinned then
Model False
else
Model True
viewPin : Signal.Address Action -> Model -> Html
viewPin address model =
if model.pinned == True then
Html.button
[ E.onClick address Pin ]
[ Html.text <| "Unpin" ]
else
Html.button
[ E.onClick address Pin ]
[ Html.text <| "Pin" ]
-- UPDATE
type Action = Pin
update : Action -> Model -> Model
update action model =
pin model
-- VIEW
view : Signal.Address Action -> Model -> Html
view address model =
Html.div []
[ viewPin address model ]
The more explicit you are, and the closer you stay to the Elm Architecture, the easier it is to read for someone who knows the pattern. It's also easy to extend. But of course you can simplify a component that you don't expect to change.
Just remember that with the code you have in your question, the only documentation you need is the one-sentence description, because the code is Elm Architecture, everything has type annotations and is super simple. When you condense your code, you may need more documentation.
A simplified button:
module BtnPin where
import Html exposing ( Html )
import Html.Events as E
import StartApp.Simple as StartApp
main =
StartApp.start { model = False, view = view, update = always not }
-- VIEW
view : Signal.Address () -> Bool -> Html
view address model =
Html.div [] [ viewPin address model ]
viewPin : Signal.Address () -> Bool -> Html
viewPin address model =
let
text = if model then "Unpin" else "Pin"
in
Html.button
[ E.onClick address () ]
[ Html.text text ]
This may be a bit extreme, so you can compromise by defining an update function, a Model type and an Action type. But then you're pretty close to where you started...