Playing with a simple demo app in Halogen with a dynamic list filled with values from an input field, I realized, that returning focus back to input after button click, is pretty hard. There is just 1 way: define FFI method which discovers element by DOM id and calls focus. Halogen doesn't expose DOM reference up on creation.
Targeting DOM by id is limited approach and not scalable on its own. DOM ids are page global. They break abstraction and isolation. Components are not COMPOSABLE!
Lack of DOM reference hurts not only focus, but selection too.
I guess Halogen would not solve the issue soon so I am looking for a workaround. How to make components, relaying on focus, composable?
I think some sort of hierarchical id generator would help. It generates and assigns unique ids to DOM elements and meanwhile exposes them to component logic.
module Main where
import Prelude (Unit, bind, discard, map, unit, ($), (<>), show)
import Data.Array (snoc, length)
import Effect (Effect)
import Effect.Console (log)
import Effect.Aff (Aff)
import Data.Maybe (Maybe(..))
import Halogen (liftEffect)
import Halogen as H
import Halogen.Aff as HA
import Halogen.HTML as HH
import Halogen.VDom.Driver (runUI)
import Halogen.HTML.Properties as P
import Halogen.HTML.Events as E
main :: Effect Unit
main = HA.runHalogenAff do
body <- HA.awaitBody
runUI component unit body
type AppState = { names :: Array String
, newName :: String
}
data Action = Append | NewName String
component :: forall query input output. H.Component HH.HTML query input output Aff
component =
H.mkComponent
{ initialState
, render
, eval: H.mkEval $ H.defaultEval { handleAction = handleAction }
}
where
initialState _ = { names: ["Bill"]
, newName: ""
}
render state =
HH.div_
[ HH.div_
[ HH.input
[ P.value state.newName
, P.id_ "newNameField"
, P.type_ P.InputText
, P.autofocus true
, E.onValueChange \s -> Just $ NewName s
]
, HH.button [E.onClick \_ -> Just Append] [ HH.text "add" ]
]
, HH.div_ (map (\n -> HH.div_ [HH.text n]) state.names)
]
handleAction ev = do
st <- H.get
case ev of
Append -> do
case st.newName of
"" -> liftEffect $ log "Empty new name"
_ -> H.put st { newName = "", names = snoc st.names st.newName }
NewName s -> do
liftEffect $ log ("Update new name: [" <> s <> "]")
H.put st { newName = s }