How do I retrieve text from an html element? - elm

How do I get the caption from a button?
decorateOn : String -> Html Msg -> Html Msg
decorateOn selectedCaption button =
if button.text == selectedCaption then
button [ class "selectedNavigationButton" ] []
else
button [ class "navigationButton" ] []
button does not have a field named text. - The type of button
is:
Html Home.Msg
Which does not contain a field named text.
Note, I realize that the "button" is really of type Html Msg.

You need to turn your thinking on its head. Rather than seeing what is in the button text, you need to set the text at the same stage as setting the class. So that gives you something like
decorateOn : String -> Html Msg -> Html Msg
decorateOn selectedCaption button =
if selectedCaption == "the selected value" then
button [ class "selectedNavigationButton" ] [text selectedCaption ]
else
button [ class "navigationButton" ] [text selectedCaption]

You can't get the text from a button without resorting to hacks involving ports and JavaScript. Moreover, you can't really inspect anything about the Elm Virtual DOM from within Elm.
Instead, try to refactor your app so that you can get the information from your model.

Related

Access attribute name with on "click" event handler

I am trying to access an elements HTML properties when clicked. I figured out that Events.onClick will only return event.target.value and I need to use Events.on to handle custom behaviours.
What I need is: when click on a div I should be able to access its HTML properties, for now id and name and send this value to update with some message.
I tried like this:
onClickData : msg -> Attribute msg
onClickData handler =
on "click" (Decode.succeed handler )
---
view: ...
....
div[onClickData HandleClick] []
This way i am able to trigger HandleClick action when the div is clicked but cannot access its HTML properties.
As #glennsl has noted, you normally would want to do something more like this:
view identification =
button [ id identification, onClick (MyMsg identification) ]
[ text "Click me"
]
i.e. you can pass data straight into your Msg type.
That said, there are some unusual inter-op situations where you might want to do this. The general principle is that you can get the element that you bound your event handler to from event.currentTarget, so you can use the following decoder:
decodeProperty : String -> Decoder a -> Decoder a
decodeProperty property decoder =
Decode.at ["currentTarget", property] decoder
--- use
onClickId : (String -> msg) -> Attribute msg
onClickId tagger =
on "click" (Decode.map tagger (decodeProperty "id"))

A way to detect a click anywhere but a specific control?

In my app the user can click on a div to highlight it. Is there a way to detect that they've clicked somewhere away from the div? I'd like to unhighlight selected things when this happens. I tried adding an onclick to the big div that contains everything else, but that doesn't really work as desired. IIRC the big div onclick always happens after the contained div onclick, so you can't ever select anything.
You can do this by adding a on "click" event to both the divs and then in the inner div, use onWithOptions to stop the propagation of the event. Here's some (untested) code:
onClickStop msg =
onWithOptions "click"
{ stopPropagation = True, preventDefault = False }
(Json.succeed msg)
view _ =
div [ onClick Clicked ]
[ div [ onClickStop NoOp ] []
]
When the user clicks inside the nested div, the event will be ignored and its propagation to its parent will be cancelled. Only when the user clicks outside the inner div will the Clicked message will be sent to update.

View with multiple textareas, how to identify which was modified?

I'm making a simple page with multiple textarea elements in Elm. I'm struggling to get the data saved and especially identifying which textarea was updated. Maybe an example illustrates the point better
I have multiple elements made in view from a list
type alias Model = { List Comment, ... }
type alias Comment = {id: Int, content: String, draftContent: String, ...}
type Event = Save Comment | SaveDraft String
-- view
model.comments
|> List.map( --iterate the list of comments and render html
div [attrubute "name" "comment"] [
textarea [onInput SaveDraft] [text comment.content],
button [onClick (Save comment)] [text "Post comment"]
]
-- update
case event of
Save comment ->
-- Replace the comment content with draft data and clear draft
SaveDraft draftText ->
-- Update the draft content with text from event
-- Which Comment is it?
Based on examples here I came up with the idea of sending each textarea input as an event to update function and saving the draft data.
Now the problem here is that onInput only accepts types with a String parameter and I have no means of identifying which one of the comments was modified.
Change the Event union type to include the comment (SaveDraft String --> SaveDraft Comment String)
type Event = Save Comment | SaveDraft Comment String
-- view
model.comments
|> List.map( --iterate the list of comments and render html
div [attrubute "name" "comment"] [
textarea [onInput (SaveDraft comment.content)] [text comment.content],
button [onClick (Save comment)] [text "Post comment"]
]
Currying causes (SaveDraft comment.content) to have the same return value as before

HTML textarea in Elm where pressing tab adds \t and doesn't change focus

I know how to listen for tab key presses in Elm. And I know how to stop the focus from being changed using onWithOptions:
textarea
[ onWithOptions "keydown" (Options False True) <| Decode.map KeyDown keyCode ] []
I can then check, in my update function, if the keyCode pressed was a 9, representing a tab. The problem is now the default behavior of a textarea doesn't work. Anything I type doesn't appear in the textarea. Easy enough, I simply add whatever I type to the model and make the value of the textarea the model. Now I have issues with the cursor and, more importantly, clipboard pasting doesn't work...
How do I get tabs to work properly with textareas in Elm? Normally, it would seem to make sense to only call preventDefault() if the tab key was pressed. How can I conditionally call preventDefault() in Elm?
Elm does support conditional event propagation through a Decoder that either succeeds or fails. Simply map the message type you want to react to in your update function:
succeededIfTabKey : Int -> Decode.Decoder Int
succeededIfTabKey key =
if key == 9 then
Decode.succeed key
else
Decode.fail "non-tab"
tabPressed : Decode.Decoder Msg
tabPressed =
Decode.andThen succeededIfTabKey keyCode
|> Decode.map (always TabPressed)
And then use this as your attribute for your input element:
onWithOptions "keydown" { defaultOptions | preventDefault = True } tabPressed
This isn't ideal for all situations. If you want some keydown events to not preventDefault(), and other keydown events to preventDefault(), then you're out of luck.

HTML startapp view inside Graphics.element.container

I am trying to place my html view in the middle of a container.
Unfortunately, container gives output of type Graphics.Element.Element whereas html view must be of the type, Html.Html.
And also, the html element constructors like div, checkbox, button, body return type is Html whereas, container needs a Graphics.Element parameter.
This is an erroneous html view, which i would like to make it work,
view : Signal.Address Action -> Model -> Element
view address model =
container 1000 1000 middle <|
toForm <|
div []
[ button [ onClick address Decrement ] [text "-"]
, div [ countStyle ] [ text (toString model ) ]
, button [ onClick address Increment ] [ text "+" ]
]
It has two buttons, one div all placed in the middle of a 1000 * 1000 container.
The elm-html package has a fromElement that can be used to create an Html from an Element.
I will say though, the idea with elm-html is to allow for a more "normal" flow of rendering HTML and using CSS to style it. While it would work to take your Html, convert it to an to either a Form or Element, use a collage or container to move it to the center of some area, and then convert back to Html, I'd probably recommend against it. If you're using HTML, just render that and style it (either with an external stylesheet or withHtml.Attributes).