1
votes

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 }

1

1 Answers

0
votes

You can name elements with a RefLabel, see https://pursuit.purescript.org/packages/purescript-halogen/5.0.0/docs/Halogen.HTML.Properties#v:ref

And then you can query for those elements, see https://pursuit.purescript.org/packages/purescript-halogen/5.0.0/docs/Halogen.Query#v:getHTMLElementRef

Unlike element ids, a Halogen RefLabel is local to the component.