In Elm, how can I detect if focus will be lost from a group of elements? - json

Suppose I have a form with a number of components. I'd like to detect a los of focus from the group. So, focus from 1 input to another on the same form should be ignored. How can I achieve this?

First, we want to be able to tag each focusable element within the group with some attribute, so when we switch elements we'll know if we're in the same group or not. This can be achieved with data attributes.
groupIdAttribute groupId =
Html.Attributes.attribute "data-group-id" groupId
Next, we need to decode the event payload on an onBlur event to see if the target is different from the relatedTarget (that which will get the focus). And report the change. (note that here we refer to data-group-id via the path "dataset", "groupId")
decodeGroupIdChanged msg =
Json.Decode.oneOf
[ Json.Decode.map2
(\a b ->
if a /= b then
Just a
else
Nothing
)
(Json.Decode.at [ "target", "dataset", "groupId" ] Json.Decode.string)
(Json.Decode.at [ "relatedTarget", "dataset", "groupId" ] Json.Decode.string)
, Json.Decode.at [ "target", "dataset", "groupId" ] Json.Decode.string
|> Json.Decode.andThen (\a -> Json.Decode.succeed (Just a))
]
|> Json.Decode.andThen
(\maybeChanged ->
case maybeChanged of
Just a ->
Json.Decode.succeed (msg a)
Nothing ->
Json.Decode.fail "no change"
)
Now we can create an onGroupLoss listener:
onGroupFocusLoss msg =
Html.Events.on "blur" (decodeGroupIdChanged msg)
And rig it up like so:
input [onGroupFocusLoss GroupFocusLoss, groupIdAttribute "a"]
Here is an example (note that it is built with elm-ui so there's a little extra code.)
https://ellie-app.com/3nkBCXJqjQTa1

Related

Elm onInput event handler not working on contenteditable element

I want to update my model everytime someone modifies an editable table cell.
The onInput function seems to be working well on input elements, but on a td element with contenteditable="true", the handler function never gets called.
Why?
How should I detect content change in an editable table cell then?
Please see lines 74 and 78 in the following piece of code
(I just took the "buttons" example of the Elm guide and added my stuff to it to have something minimal but runnable):
module Main exposing (..)
-- Press buttons to increment and decrement a counter.
--
-- Read how it works:
-- https://guide.elm-lang.org/architecture/buttons.html
--
import Browser
import Html exposing (Html, button, div, text, table, tbody, tr, td, input)
import Html.Events exposing (onClick, onInput)
import Html.Attributes exposing (contenteditable)
-- MAIN
main =
Browser.sandbox { init = init, update = update, view = view }
-- MODEL
type alias Model = Int
init : Model
init =
0
-- UPDATE
type Msg
= Increment
| Decrement
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
model + 1
Decrement ->
model - 1
testOnInputHandler : String -> Msg
testOnInputHandler str =
let
log = Debug.log "in testOnInputHandler" "here"
in
Increment
-- VIEW
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (String.fromInt model) ]
, button [ onClick Increment ] [ text "+" ]
, table []
[ tbody []
[ tr []
[ td [ contenteditable True, onInput testOnInputHandler] [ text "editable!" ]
]
]
]
, input [ onInput testOnInputHandler ] []
]
onInput will get the string from target.value which does not exist for contenteditable input events.
You may want to grab innerText for example instead:
onContentEditableInput : (String -> msg) -> Attribute msg
onContentEditableInput tagger =
Html.Events.stopPropagationOn "input"
(Json.map (\x -> ( x, True )) (Json.map tagger innerText))
innerText : Json.Decoder String
innerText =
Json.at ["target", "innerText"] Json.string

Disable Elm form submission without no-op message

I have an Elm app with an HTML form (containing an input field and a submit button) that I would like to disable while the input is not valid. I've managed to do that by binding form submission to a no-op message when the input is invalid:
type Msg
= Input String
| Submit
| Noop
viewForm : Model -> Html Msg
viewForm = form [ onSubmit (if model.valid then Submit else Noop) ]
[ input [ onInput Input ] []
, button [ disabled (not model.valid) ] [ "Submit" ]
]
update then does nothing on a Noop message. The button is also disabled, but this is secondary, since I also care about form submission by hitting Enter from the text input. Note that it does not work to skip the onSubmit handler, because then hitting Enter will reload the page.
This feels a bit messy and inefficient, so my questions are:
What is the idiomatic way to achieve this in Elm?
How much more expensive is this use of a no-op message to a no-op Javascript event handler.
First of all, I have to say that your implementation is alright, there's no harm in sending Noop message, that's why it exists.
The best scenario would be to remove the listener entirely(if possible) and disable the button visually. This actually might have a performance drawback if model.valid changes super-frequently.
I'm using a helper for conditionally adding stuff to a list:
appendIf : Bool -> a -> List a -> List a
appendIf flag value list =
if flag == True then
list ++ [ value ]
else
list
So you could use it like that:
view model =
button
([ type_ "" submit ] |> appendIf model.valid (onClick Submit))
[]

Subsetting JSON data

Please consider this dataset:
type Deck = JsonProvider<"...">
let dt = Deck.GetSamples()
dt
[{"collectible":true,"health":4,"artist":"Zoltan Boros","type":"MINION","cost":1,"attack":2},
{"collectible":true,"health":8,"artist":"James Ryman","type":"MINION","cost":8,"attack":8},
{"collectible":true,"health":3,"artist":"Warren Mahy", "type":"LAND","cost":2,"attack":2}]
I am trying to build a function capable of extracting certain info from it and, eventually, store them in a smaller dataset. It should, given a list-like dataset deck, consider only the cards that for the keys equal to given values.
let rec filter deck key value =
let rec aux l1 l2 l3 =
match l1 with
[] -> []
| x::xs when x.l2 = l3 -> x::(aux xs key value)
aux deck key value
For example,
filter dt type minion
should subset the deck in a smaller one with only the first and second card. I think I did few steps forward in getting the concept, but still it does not work, throwing an error of kind
FS0072: Lookup on object of indeterminate type based on information prior to
this program point. A type annotation may be needed prior to this program point to
constrain the type of the object. This may allow the lookup to be resolved.
How should I define the type of key? I tried with key : string and key : string list, without succeed.
Are you trying to re-implement filter?
#if INTERACTIVE
#r #"..\packages\FSharp.Data\lib\net40\FSharp.Data.dll"
#endif
open FSharp.Data
[<Literal>]
let jsonFile = #"C:\tmp\test.json"
type Json = JsonProvider<jsonFile>
let deck = Json.Load(jsonFile)
deck |> Seq.filter (fun c -> c.Type = "MINION")
Gives me:
val it : seq.Root> = seq
[{ "collectible": true, "health": 4, "artist": "Zoltan Boros", "type": "MINION", "cost": 1, "attack": 2 };
{ "collectible": true, "health": 8, "artist": "James Ryman", "type": "MINION", "cost": 8, "attack": 8 }]
You actually need to annotate the type of l1.
setting l1: something list should be what you want.
Key doesn't help as type inference is top to bottom and x.l2 is before aux is called with key as an argument

scala only if condition without else (not even default)

It may be simple question, but I am new to Scala and not able to find the proper solution
I am trying to create a JSON object from the Option values. Will check if the value is not empty then create the Json obj, if the value is None I don't want to create the json object. With out else, default else is Unit which will fail to create Json obj
Json.obj(if(position.nonEmpty) ("position" -> position.get),
if(place.nonEmpty) ("place" -> place.get),
if(country.nonEmpty) ("country" -> country.get))
Need to put the If condition so that the final json string to look like
{
"position": "M2",
"place": "place",
"country": "country"
}
val obj = for {
p <- position
o <- otherOption
...
} yield Json.obj(
"position" -> p,
"other" -> o)
Will only yield a Some of Json Object if all options are defined. Otherwise None
Option is a monad and there are few convenient ways for using it.
First, if you want to extract value you should use map or flatMap and getOrElse methods:
val res = position.map(value => Json.obj("position" -> value)).getOrElse(null)
Another way is to keep Option of another type and use it latter:
val jsonOption = position.map(value => Json.obj("position" -> value))
After you can use it in for comprehension with another options or perform another mutations without extracting:
for (positionJson <- jsonOption; xJson <- xJsonOption) yield positionJson.toString + xJson.toString
jsonOption.map(_.toString).foreach(print(_))
And always try to avoid pattern matching on monads.

Retrieve the actual value from json

I have code that parses json:
(Aeson.Object jsonObj) -> case (HashMap.lookup "one" jsonObj, HashMap.lookup "two" jsonObj, , HashMap.lookup "three" jsonObj) of
(Just one, Just two, Just three) -> -- what's next?
_ -> error "All three keys don't exist
"
How do I retrieve the actual values from "one", "two" and "three"? All the three are Data.Aeson.Value and its data type is defined as following but yet I can't figure out what to do next:
data Value Source
A JSON value represented as a Haskell value.
Constructors
Object !Object
Array !Array
String !Text
Number !Number
Bool !Bool
Null
You can check if the values are of the expected type right in the pattern matching like this:
case HashMap.lookup "one" jsonObj of
(Just (Aeson.String t)) -> -- do with string t what pleases you
_ -> error "key not present or not a string"
You could adapt this to the triple in your code or leave it as a separate function, whatever you see fit. But be aware that this style would really mean that you're not using the Parser monad Aeson offers, which is really nice to dissect arbitrary JSON. Assume in your example above, what you really want to achieve is something like this:
parseTriple :: ByteString -> Maybe (Text, Integer, Bool)
then, using parseEither, this can be expressed as simple as
parseTriple bs = decode bs >>= parseMaybe $ \o -> do
one <- o .: "one"
two <- o .: "two"
three <- o .: "three"
return (one, two, three)
I think this nicely expresses what you're doing with the JSON and is easy to follow and maintain. Also, if you don't use parseMaybe but parseEither, you even get kind-of-useful error messages for free.