How can I mix html and svg in an elm view? - elm

I'm attempting to add a control to turn the subscription on and off in the elm time example. The problem is that I can't get html and svg to coexist in the view.
So far, I have this for the view:
import Html exposing (Html)
import Html.App as Html
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Svg exposing (..)
import Svg.Attributes exposing (..)
import Time exposing (Time, second)
-- MODEL, UPDATE and SUBSCRIPTION removed for brevity
-- VIEW
view : Model -> Html Msg
view model =
Html.div []
[ Html.p [] [ text "hello world" ]
, clock
]
clock : Model -> Html msg
clock model =
let
angle =
turns (Time.inMinutes model.time)
handX =
toString (50 + 40 * cos angle)
handY =
toString (50 + 40 * sin angle)
in
svg [ viewBox "0 0 100 100", Svg.Attributes.width "300px" ]
[ circle [ cx "50", cy "50", r "45", fill "#0B79CE" ] []
, line [ x1 "50", y1 "50", x2 handX, y2 handY, stroke "#023963" ] []
]
However, I get the following message when I try to transpile:
Detected errors in 1 module.
==================================== ERRORS ====================================
-- TYPE MISMATCH ------------------------------------------ examples/6-clock.elm
The 1st and 2nd elements are different types of values.
70| [ Html.p [] [ text "hello world" ]
71|> , clock
72| ]
The 1st element has this type:
VirtualDom.Node a
But the 2nd is:
Model -> Html a
Hint: All elements should be the same type of value so that we can iterate
through the list without running into unexpected values.
In addition to fixing the error, is there a way to fix the problem of not having to prepend Html to all the html elements? e.g. Html.div as opposed to the usual div.

clock is a function that receives a Model and returns some Html.
As the compiler is telling you, you'll need to have an Html element inside your view. It should be enough to pass the model to the clock function, like
view model =
Html.div []
[ Html.p [] [ text "hello world" ]
, clock model
]
To be able to use directly div instead of Html.div you could import their definition using
import Html exposing (Html, div, p)
and so on, or, if you want to import all the Html package
import Html exposing (..)

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

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.

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

Insert raw HTML into Elm component

How do I insert text - that I know to be 'safe' - directly into an Elm page.
i.e. what is text' in the following.
let str = "<h1>Hello World</h1>"
in div [] [ text' str ]
In practise I could get any valid HTML, so I don't want to build a parser, and I'd rather avoid a port as playing around wit the DOM can break the virtual dom diffing
There are a few solutions discussed in this post. Seems the poster had success with using Markdown.toHtml.
To elaborate on the accepted answer, this is what worked for me:
Install the markdown library
elm-package install evancz/elm-markdown
To render your HTML in a div
import Markdown
div [] [ Markdown.toHtml [] str ]
Or simply this as per:
import Html exposing (div)
import Html.Attributes exposing (property)
import Json.Encode exposing (string)
main =
div [ property "innerHTML" <| string "<div>hello</div>" ] []
As of Elm 0.19, it appears that the hole in elm-markdown has been plugged and properly escapes HTML.
A solution that works with Elm 0.19 is to parse the HTML using hecrj/html-parser:
import Html.Parser
import Html.Parser.Util
let
rawHtml = "<h1>Hello World</h1>"
innerHtml = case Html.Parser.run rawHtml of
Ok nodes -> Html.Parser.Util.toVirtualDom nodes
Err _ -> []
in
div [] innerHtml