How can i break line html text in Elm - elm

I have a text like this
Special menu:\nFrenchfire 1ea\nHamborger 2ea\nMeatball 1ea
and i want to breakline it in Elm with Html tag such as pre or span
and it should show like this
Special menu:
Frenchfire 1ea
Hamborger 2ea
Meatball 1ea
i have no idea to concat br[][] in string if it is an Elm
but if javascript i can replace string like replace(/\n/g,'<br/>') and it done.

You mentioned a <pre> tag, so let's try that first:
import Html exposing (pre, text)
pre []
[ text "Special menu:\nFrenchfire 1ea\nHamborger 2ea\nMeatball 1ea" ]
Renders as:
Special menu:
Frenchfire 1ea
Hamborger 2ea
Meatball 1ea
Or you could split the string into individual lines. String.split accomplishes this:
String.split "\n" "Special menu:\nFrenchfire 1ea\nHamborger 2ea\nMeatball 1ea"
Even better, as kaskelotti aptly suggested:
String.lines "Special menu:\nFrenchfire 1ea\nHamborger 2ea\nMeatball 1ea"
Use List.map to transform the Strings into Html and List.intersperse to insert <br /> tags between them:
import Html exposing (br, text)
List.intersperse (br [] [])
(List.map text
(String.lines "Special menu:\nFrenchfire 1ea\nHamborger 2ea\nMeatball 1ea")
)
You end up with a list of Html elements. Done!
See it rendered here.
Or, if you prefer, use an itemized list:
import Html exposing (Html, div, h3, li, text, ul)
listItem : String -> Html msg
listItem content =
li [] [ text content ]
main : Html msg
main =
let
split =
String.lines "Special menu:\nFrenchfire 1ea\nHamborger 2ea\nMeatball 1ea"
headline =
Maybe.withDefault "Items:" (List.head split)
items =
Maybe.withDefault [] (List.tail split)
in
div []
[ h3 [] [ text headline ]
, ul [] (List.map listItem items)
]
See it rendered here.

String.replace takes input of type String and returns a String. List.map requires an input as List of Strings i.e. List String as the error states.
You can solve this by using String.split or String.lines. Both will take a String as input and return a List of String, which is what List.map requires.
The difference is that String.lines is a built-in function meant for splitting the String with newline chars (\n) and String.split is a general purpose utility for splitting a String by any delimiter.
For this particular case, I'd go with String.lines, since the name documents its purpose already. Presence of String.split is good to know for future cases.

Related

elm-ui center elements in wrapped row

I'm using Elm with mdgriffiths/elm-ui, and I've really been enjoying it. Right now, I'm trying to create a centered, wrapped row of elements:
I can get it to this point:
using this code:
button : String -> String -> Element Msg
button label url =
link
[ height (px 150)
, width (px 150)
, Border.width 1
, Background.color (rgb255 255 255 255)
]
{ url = url
, label =
Element.paragraph
[ Font.center ]
[ textEl [] label ]
}
row : Element Msg
row =
Element.wrappedRow
[ Element.spacing 25
, Element.centerX
, Element.centerY
, width (fill |> Element.maximum 600)
, Font.center
]
[ button "A" "/a"
, button "B" "/b"
, button "C" "/c"
, button "D" "/d"
]
But when I try adding Element.centerX to my buttons like this
link
[ Element.centerX
, ...
]
I get this instead:
I've also tried Font.center without success, and I don't know what else I can try.
I'm not sure if I'm missing something I should be using, or if the whole thing needs re-arranging, or if I just need to use the built-in CSS stuff.
Update:
Link to an Ellie with the issues I'm seeing.
https://ellie-app.com/7NpM6SPfhLHa1
This Github issue is useful for this problem. I'm afraid you will have to use some CSS (unless I'm missing something). I've found this before with elm-ui; every now and then it can't quite do what you want and you need a bit of CSS.
This works for me (taken from the post by AlienKevin in the link above). You need to set "marginLeft" and "marginRight" to "auto".
module Main exposing (main)
import Element as E
import Element.Border
import Html.Attributes
box : String -> E.Element msg
box label =
E.paragraph
[ E.width <| E.px 200
, Element.Border.width 1
, E.htmlAttribute (Html.Attributes.style "marginLeft" "auto")
, E.htmlAttribute (Html.Attributes.style "marginRight" "auto")
]
[ E.text label ]
row : E.Element msg
row =
E.wrappedRow []
[ box "A"
, box "B"
, box "C"
]
main =
E.layout [] row
(See here for an Ellie.)
You can also do the following:
Define the following elm-ui classes. I usually setup a UI.elm module for this
centerWrap : Attribute msg
centerWrap =
Html.Attributes.class "centerWrap"
|> htmlAttribute
dontCenterWrap : Attribute msg
dontCenterWrap =
Html.Attributes.class "dontCenterWrap"
|> htmlAttribute
Add the following to your css. Basically says center elements if has centerWrap class but not dontCenterWrap class.
:not(.dontCenterWrap).centerWrap>div.wrp {
justify-content: center !important;
}
Apply the attribute
wrappedRow [ width fill, UI.centerWrap, spaceEvenly ] [...]
Assuming you created a custom element that centerWraps and wanted to disable that you could use UI.dontCenterWrap
centerWrappedRow attr children =
wrappedRow (UI.centerWrap :: attr) children
-- somewhere else
...
centerWrappedRow [UI.dontCenterWrap] [..]
...

How To Add Items To A Multiline Select And Show Them In The View

In my model I have a field that stores a List of syllables:
type alias Model =
{ freeSyllables : List FreeSyllable
...
}
The syllable looks like:
type alias FreeSyllable =
{ syllable : String
, usage : Usage
}
I want to bind the string in each syllable to a select in my view:
select [ size 20, style [ ("width", "70px") ] ] []
How can I accomplish this? Thanks.
In order to display each syllable as an option of a select, you'll need option function from Html module:
import Html exposing (Html, beginnerProgram, text, option, select)
import Html.Attributes exposing (style, value)
import Html.Events exposing (onInput)
Then just map freeSyllables to options with value and text equal to syllable or to something else (value is what you get as an input, text is what displayed on the screen to select from)
view : Model -> Html Msg
view model =
let
syllableToOption : FreeSyllable -> Html Msg
syllableToOption freeSyllable =
option [ value freeSyllable.syllable ] [ text freeSyllable.syllable ]
in
select [ onInput SetSyllable ] (List.map syllableToOption model.freeSyllables)
When an option is selected an event (for example SetSyllable) is triggered. Thus, you'll need something like this in the update function:
type Msg
= SetSyllable String
update : Msg -> Model -> Model
update msg model =
case msg of
SetSyllable syllable ->
model

how to properly parse paired html tags?

the question is about parsing an html stream obtained by load/markup in a way you can get html tags constituent parts, i.e. when you find
<div id="one">my text</div>
you should end with something like <div id="one">, {my text} and </div> in the same container, something like
[<div id="one"> {my text} </div>]
or even better
[<div> [id {one}] {my text} </div>]
the parsing problem is matching paired html tags, in html a tag may be an empty tag with maybe attributes but without content and thus without an ending tag or a normal tag maybe with attributes and content and so an ending tag, but both types of tag are just a tag
I mean when you find a sequence like <p>some words</p> you have a P tag just the same you get whit a sequence like <p /> just a P tag, in first case you have associated text and ending tag and in the latter you don't, that's all
In other words, html attributes and content are properties of tag element in html, so representing this in json you will get someting like:
tag: { name: "div" attributes: { id: "one } content: "my text" }
this means you have to identify content of a tag in order to assign it to properly tag, which in terms of rebol parse means identifing matching tags (opening tag and ending tag)
In rebol you can easy parse an html sequence like:
<div id="yo">yeah!</div><br/>
with the rule:
[ some [ tag! string! tag! | tag! ]]
but with this rule you will match the html
<div id="yo">yeah!</div><br/>
and also
<div id="yo">yeah!</p><br/>
as being the same
so you need a way to match the same opening tag when appearing in ending position
sadly rebol tags cannot (AFAIK) be parametrized with tag name, so you cannot say something like:
[ some [ set t1 tag! set s string! set t2 tag!#t1/1 | tag! ] ]
the t1/1 notation is due to a (bad) feature of rebol including all tag atributes at same level of tag name (another bad feature is not reckognizing matching tags as being the same tag)
Of course you can achieve the goal using code such as:
tags: copy []
html: {<div id="yo">yeah!</p><br/>}
parse html [ some [ set t1 tag! set s string! set t2 tag! (tag: first make block! t1 if none <> find t2 tag [append/only tags reduce [t1 s] ]) | tag! (append/only tags reduce [t1])]]
but the idea is to use a more elegant and naive approach using parse dialect only
There's a way to parse pairs of items in rebol parse dialect, simply using a word to store the expected pair:
parse ["a" "a"] [some [set s string! s ]]
parse ["a" "a" "b" "b"] [some [set s string! s ]]
But this doesn't work well with tags due to tags carry attributes and special ending marks (/) and thus it's not easy to find the ending pair from initial one:
parse [<p> "some text" </p>] [some [ set t tag! set s string! t ]
parse [<div id="d1"> "some text" </div>] [some [ set t tag! set s string! t ]
don't work cause </p> is not equal to <p> and neither </div> is equal to <div id="d1">
Again you can fix it with code:
parse load/markup "<p>preug</p>something<br />" [
some [
set t tag! (
b: copy t remove/part find b " " tail b
insert b "/"
)
set s string!
b (print [t s b])
|
tag!
|
string!
]
]
but this is not simple and zen code anymore, so question's still alive ;-)

Transform Html DOM

I am new to Elm and I really love it so far, but I've run into a problem that I cannot seem to wrap my head around.
I have an Html DOM, for example
div []
[ h1 [] [text "Headline 1"]
, p [] [text "Some text"]
, h2 [] [text "Headline 2"]
]
I would like to add a-links inside each h[1-6] element and so transform it to something like (keeping it simple)
div []
[ h1 [] [ text "Headline 1"
, [a [name "headline"] [text "#"]
]
, p [] [text "Some text"]
, h2 [] [text "Headline 2"
, [a [name "headline"] [text "#"]
]
]
This is conceptually not very hard. Look through the DOM, if element is h[1-6] add an a-link as child element. However my understanding of Elm is not well enough to get it to work.
Here is what I've been trying so far.
transform : Html a -> Html a
transform node =
-- check if the tag is h1-h6
case node.tag of
-- add a-link to h1 children
"h1" -> { node | children = (a [name "headline"] [text "#") :: node.children }
"h2" -> { node | children = (a [name "headline"] [text "#") :: node.children }
-- do this for all nodes in the tree
_ -> { node | children = List.map transform node.children }
This doesn't work.
The type annotation for `transform` does not match its definition.
40| transform : Html a -> Html a
^^^^^^^^^^^^^^^^
The type annotation is saying:
VirtualDom.Node a -> VirtualDom.Node a
But I am inferring that the definition has this type:
{ b | tag : String, children : List (Html a) }
-> { b | children : List (Html a), tag : String }
I understand that I can't do node.tag because the generic type a might not have that field. It wouldn't be type safe. For example the text node doesn't have a tag field, but is still an instance of Html.Html a.
> text "Hello World"
{ type = "text", text = "Hello World" } : Html.Html a
My question is, how can I do this? Can I do this? or shouldn't I be doing this?
It is not possible to modify existing values of Html msg type.
They are final internal structures, which are rendered by Virtual DOM in to actual HTML Nodes as an output of your program.
Html msg is an alias for VirtualDom.Node a
You are attempting to use them as Records, but that's just a JavaScript object.
Elm REPL outputs String presentation of an abstract data structure here:
> text "Hello World"
{ type = "text", text = "Hello World" } : Html.Html a -- not a record
Instead of attempting to transform Html msg -> Html msg, you should try something like:
-- Input example: [ "#", "http://google.com/", "http://package.elm-lang.org/" ]
linksView : List String -> Html msg
linksView links =
links
|> List.map (\link -> a [ href link ] [ text link ])
|> div [] -- Expected output: <div> with thre links
In Elm, Html a is really only useful as output. You're never going to use it as input in the way that your transform function is attempting.
You will be better served by creating a model to describe your domain, then passing that to a view function to render html.
type alias Article =
{ priority : Priority
, headline : String
, body : String
}
type alias Model =
List Article
type Priority = First | Second
Your view could then look something like this:
view : Model -> Html msg
view =
div [] << List.map viewArticle
viewArticle : Article -> Html msg
viewArticle article =
let
priorityTag =
case article.priority of
First -> h1
Second -> h2
in
div []
[ priorityTag []
[ text article.headline
, a [ name "headline" ] [ text "#" ]
]
, p [] [ text article.body ]
]

In a select element, how do I designate the initially selected option from my model in Elm?

Let's say I have a select element to choose a person, and I want to have a certain person, say with id = 3, to be initially selected. How do I pass this id down into my options, and then set the selected attribute to True in that options?
Some sample code:
personSelect : List Person -> String -> Html Msg
personSelect : personList selectedId =
div []
[ select [] (List.map personOption personList) ]
personOption : Person -> Html Msg
personOption : person =
option [ value (toString person.id) ] [ text person.name ]
Specifically, how do I get "selectedId" passed to "personOption"? Can I even do this using List.map?
Thanks very much!
Provide selectedId as an argument to personOption and exploit that you can partially apply functions in Elm. That is, when you give a function some but not all of the arguments that it needs, you get back a function waiting for the remaining arguments.
First, add selectedId to personOptions and render the option as selected if it matches.
personOption : String -> Person -> Html Msg
personOption selectedId person =
option
[ selected (selectedId == person.id)
, value (toString person.id)
]
[ text person.name ]
Then partially apply personOption by giving it its first argument before passing it on to map:
personSelect : List Person -> String -> Html Msg
personSelect personList selectedId =
div []
[ select []
(List.map (personOption selectedId) personList)
-- personOption selectedId : String -> Html Msg
]