简体   繁体   中英

Halogen keyboard-input example and unsubsribing to the events?

How to unsubscribe to keyboard events from other actions than HandleKey in the keyboard-input example ? (The question is related to Halogen version 2.0.1 and purescript 0.11.4.)

In the example the enter/return works. I have a group of elements that can be collapsed with mouse by pressing close-button and Close-action takes care of it. Also, the enter/return can be used to collapse the elements and it works as it should.

Open next -> do
  st <- H.get
  if not st.open
    then do
      H.modify (_ { open = true })
      eval (H.action Init)
    else pure unit
  pure next

Close next -> do
  H.modify (_ { open = false })
  st <- H.get
  case st.unsubscribe of
    Nothing -> pure unit
    -- Just smth -> H.liftAff smth
    Just _ -> H.modify (_ { unsubscribe = Nothing}) -- H.liftAff us
  pure next

Init next -> do
  H.modify (_ { open = true})
  document <- H.liftEff $ DOM.window >>= DOM.document <#> DOM.htmlDocumentToDocument
  H.subscribe $ ES.eventSource'
    (K.onKeyUp document)
    (Just <<< H.request <<< HandleKey)
  -- H.modify (_ { unsubscribe = ??? })
  pure next

The problem is that when I close (collapse) the elements with mouse, the event listener is not removed (not Done) and it will be still there. When I re-open the elements, the above code will establish a second (a third, fourth, etc) listener and then every keypress will be handled many times.

I was considering to create a keyboard event but it doesn't sound a correct way here.

So the questions are:

  • How to check if there is already a keyboard listener?
  • And then how to stop it at the Close -action?
  • Or is it possible to make the Done SubscribeStatus and send it to the keyboard listener at the Close -action?

Further questions:

  • The example has unsubscribe in the state. How to use it?
  • (I tried to put something into it at the Init -action and at the Close -action but it was pure guessing.)

Late edit: This seems to be related to questions about dynamically attached event listeners and see also this , this and this .

One of the answers said that it is only possible to know about dynamic handlers through the use of states and that did the trick for my problem. Anyhow, I think that the questions stated here remain (how to remove handler with Halogen and especially about the unsubscribe field in the example - is it somehow useable?)

It took me a while to understand the eventSource' signature, but here's a breakdown that might help clarify what's going on.

This is the actual signature...

eventSource' :: forall f m a eff. MonadAff (avar :: AVAR | eff) m =>
  ((a -> Eff (avar :: AVAR | eff) Unit) -> Eff (avar :: AVAR | eff) (Eff (avar :: AVAR | eff) Unit)) ->
  (a -> Maybe (f SubscribeStatus)) ->
  EventSource f m

If we use a sort of pseudocode to name the components of the signature...

type Callback a = (a -> Eff (avar :: AVAR | eff) Unit)
type MaybeQuery a = (a -> Maybe (f SubscribeStatus))
type RemoveEventListener = (Eff (avar :: AVAR | eff) Unit)

We get...

eventSource' :: forall f m a eff.
  (Callback a -> Eff (avar :: AVAR | eff) RemoveEventListener) ->
  MaybeQuery a ->
  EventSource f m

I think the part you're missing is the RemoveEventListener part. Here's an example EventSource for onMouseUp on an HTMLDocument that doesn't use the foreign function interface (FFI) like the example you referenced.

import DOM.Event.Types as DET
import DOM.Event.EventTarget as DEET
import DOM.HTML.Event.EventTypes as EventTypes

onMouseUp :: forall e
   . DHT.HTMLDocument
  -> (DET.Event -> Eff (CompEffects e) Unit)
  -> Eff (CompEffects e) (Eff (CompEffects e) Unit)
onMouseUp doc callback = do
  let eventTarget = (DHT.htmlDocumentToEventTarget doc)

  -- Create an EventListener that will log a message
  -- and pass the event to the callback
  let listener =
        DEET.eventListener (\event -> do
          log "mouseUp"
          callback event
        )

  -- Add the EventListener to the
  -- document so it will fire on mouseup
  DEET.addEventListener
    EventTypes.mouseup
    listener true eventTarget

  -- Return the function that will later 
  -- remove the event listener
  pure $ DEET.removeEventListener
    EventTypes.mouseup
    listener true eventTarget

So the Callback will get the a event (In this case a generic Event ). Note that we have to create a reference to the listener, so we can pass the same reference to addEventListener as removeEventListener (the function reference acts like an id to determines which listener function is removed).

The MaybeQuery function will also be passed the a event, so Halogen can execute a query and you can decide if you should keep listening for events or not. That query will need to be structured like this...

data YourQuery =
  | HandleEvent Event (SubscribeStatus -> a)

eval (HandleEvent event reply) =
  -- Decided if you want to keep listening
  -- or now.

  -- Keep listening...
  -- pure $ reply (H.Listening)

  -- Stop listening.  Halogen will call the removeEventListener
  -- function you returned from onMouseUp earlier
  -- pure $ reply (H.Done)

And just show how to subscribe to the EventSource...

import DOM.HTML.Window as Win
import Halogen.Query.EventSource as HES

document <- H.liftEff $ (window >>= Win.document)
H.subscribe $ HES.eventSource'
  (onMouseUp document)
  (Just <<< H.request <<< HandleEvent)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM