New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add preventDefault/stopPropagation etc to docs #426

Open
boothead opened this Issue Mar 7, 2017 · 8 comments

Comments

4 participants
@boothead
Copy link

boothead commented Mar 7, 2017

I can't find the preventDefault function in halogen's API anymore, or see it mentioned in the docs. How do we do that now?

I'd also just like to say a massive thanks for halogen. The latest changes for version 1 make for a much nicer to use API and refactoring a couple of thousand line app was pretty painless. You guys rock!

@cryogenian

This comment has been minimized.

Copy link
Member

cryogenian commented Mar 7, 2017

It works this way now

import DOM.Classy.Event (toEvent, preventDefault)
data Query a 
  = DoSomething a 
  | PreventDefault Event (Query a) 

-- somewhere in render function 
onClick \e -> Just $ PreventDefault (toEvent e) $ action DoSomething

eval :: Query ~> DSL 
eval (PreventDefault ev q) = do
  liftEff $ preventDefault ev 
  eval q
eval (DoSomething next) = pure next
@boothead

This comment has been minimized.

Copy link

boothead commented Mar 7, 2017

Aaah! Perfect. thanks. Is this in the docs anywhere? Shall I leave this open as a reminder or close it?

@cryogenian

This comment has been minimized.

Copy link
Member

cryogenian commented Mar 7, 2017

I'd leave and change the header to something like `Add preventDefault/stopPropagation etc to docs" or something :)

@boothead boothead changed the title What happened to preventDefault and friends? Add preventDefault/stopPropagation etc to docs Mar 8, 2017

@boothead

This comment has been minimized.

Copy link

boothead commented Mar 8, 2017

Well then, I will see what I can do :-)

@thomashoneyman

This comment has been minimized.

Copy link
Contributor

thomashoneyman commented Aug 5, 2017

This issue was very helpful for me. I often have to prevent link clicks from causing the browser to load the page, but using preventDefault inside every query handler is tedious.

In my project, I created variants of the events from Halogen.HTML.Events that have prevention built in. For example, onClick has its parallel in preventClick, which makes using preventDefault as simple as using one of the prevent- functions in your render.

Thought this may be useful for others. Full excerpt:

import DOM.Classy.Event (preventDefault, toEvent)
import DOM.Event.Event (Event)
...

data Query a
  = NoOp a
  | PreventDefault Event (Query a)

eval = case _ of
  NoOp next -> pure next
  PreventDefault ev q -> do
    liftEff $ preventDefault ev
    eval q

preventClick q =
  HE.onClick \e -> Just $ PreventDefault (toEvent e) $ H.action q

render :: H.ComponentHTML Query
render =
  HH.a [ HP.href "", preventClick NoOp ] [ HH.text "Do something" ]

JordanMartinez added a commit to JordanMartinez/purescript-halogen that referenced this issue Jul 25, 2018

@JordanMartinez

This comment has been minimized.

Copy link

JordanMartinez commented Jul 26, 2018

Building off of @cryogenian and @thomashoneyman's work, I abstract things even more in my work on #563, so that I could write
preventDefault_ HH.onClick toEvent (\e -> DoSomethingElseWithMouseAfterPDCall e)

After working on it some more, I then abstracted the preventDefault Query out of this method and got this:
recursiveQuery onEvent eToRecQuery = onEvent (\e -> Just $ eToRecQuery e)

This allows me to write:

HH.div
  [ recursiveQuery HE.onClick (\e ->
    let 
      event = toEvent e
    in
      PreventDefault event $
      StopPropagation event $
      -- other recursive queries... until we get to a terminating one
      H.action $ TerminatingQuery -- like NoOp
  ]
  []

However, I then tried to use the function on a HH.button and now I'm getting a very crazy-looking error message. I'm guessing it's due to the function not having a type signature.

Here's the full code:

module Component where

import Prelude

import Data.Maybe (Maybe(..))
import Effect.Aff (Aff)
import Effect.Class (liftEffect)
import Effect.Console (log)
import Halogen as H
import Halogen.HTML as HH
import Halogen.HTML.Events as HE
import Halogen.HTML.Properties as HP
import Web.Event.Event (preventDefault, stopPropagation, stopImmediatePropagation)
import Web.Event.Internal.Types (Event)
import Web.UIEvent.MouseEvent (toEvent, clientX)

type State = Unit

type Input = Unit
type Message = Void

data Query a
  -- actual action...
  = LogToConsole String a

  -- Equivalent of "Halt" when there's nothing to log
  | NoOperation a

  -- Recursive queries that can be used to call these functions
  | PreventDefault Event (Query a)
  | StopPropagation Event (Query a)
  | StopImmediatePropagation Event (Query a)

component :: H.Component HH.HTML Query Input Message Aff
component =
  H.component
    { initialState: const unit
    , render
    , eval
    , receiver: const Nothing
    }
  where

  recursiveQuery onEvent eToRecQuery =
    onEvent (\e -> Just $ eToRecQuery e)

  render :: State -> H.ComponentHTML Query
  render _ =
    HH.div
      [ HP.id_ "div1"
      , recursiveQuery HE.onClick (\e ->
          PreventDefault (toEvent e) $
          H.action $ NoOperation
        )
      , HE.onMouseMove (HE.input_ $ LogToConsole "Div1: This will never appear")
      ]
      [ HH.div
        [ HP.id_ "div2"
        , recursiveQuery HE.onClick (\e ->
            let
              event = toEvent e
            in
              PreventDefault event $
              StopPropagation event $
              H.action $ LogToConsole "Either Button or Div 2 was clicked"
          )
        , HE.onMouseMove (HE.input_ $ LogToConsole "Div2: This will never appear")
        ]
        [ HH.button
          [
            -- I get a compilation error when any or all of these parts are uncommented

            -- recursiveQuery HE.onClick (\e -> H.action $ LogToConsole "message" )
          -- ,
            -- recursiveQuery HE.onClick (\e ->
            --   PreventDefault (toEvent e) $
            --   H.action $ LogToConsole $ "Button clicked - ClientX: x" <> (show $ clientX e)
            -- )
          -- ,
            -- recursiveQuery HE.onMouseMove (\mouseEvent ->
            --   let
            --     event = toEvent mouseEvent
            --   in
            --     PreventDefault event $
            --     StopPropagation event $
            --     StopImmediatePropagation event $
            --     H.action $ LogToConsole $
            --       "preventDefault and stopPropagation and stopImmediatePropagation" <>
            --       "were called on Button's mouse move event"
            -- )
          ]
          [ HH.text "Click me and check the console." ]
        ]
      ]


  eval :: Query ~> H.ComponentDSL State Query Message Aff
  eval = case _ of
    LogToConsole message next -> do
      liftEffect $ log message
      pure next
    NoOperation next -> pure next
    PreventDefault e query -> do
      liftEffect $ preventDefault e

      -- "eval query" will block until the query finishes its evaluation
      -- So, no need to worry about concurrency issues by doing this.
      -- However, beware of recursive
      eval query
    StopPropagation e query -> do
      liftEffect $ stopPropagation e
      eval query
    StopImmediatePropagation e query -> do
      liftEffect $ stopImmediatePropagation e
      eval query

Here's the resulting error message. It's going to be a long scroll:

Error found:
in module Component
at src/Component.purs line 71, column 11 - line 89, column 12

  Could not match type

    ( onScroll :: Event
    , accessKey :: String
    , "class" :: String
    , contentEditable :: Boolean
    , dir :: DirValue
    , draggable :: Boolean
    , gotPointerCapture :: Event
    , hidden :: Boolean
    , id :: String
    , lang :: String
    , lostPointerCapture :: Event
    , onBlur :: FocusEvent
    , onClick :: MouseEvent
    , onContextMenu :: Event
    , onCopy :: ClipboardEvent
    , onCut :: ClipboardEvent
    , onDoubleClick :: MouseEvent
    , onDrag :: DragEvent
    , onDragEnd :: DragEvent
    , onDragEnter :: DragEvent
    , onDragExit :: DragEvent
    , onDragLeave :: DragEvent
    , onDragOver :: DragEvent
    , onDragStart :: DragEvent
    , onDrop :: DragEvent
    , onFocus :: FocusEvent
    , onFocusIn :: FocusEvent
    , onFocusOut :: FocusEvent
    , onKeyDown :: KeyboardEvent
    , onKeyPress :: KeyboardEvent
    , onKeyUp :: KeyboardEvent
    , onMouseDown :: MouseEvent
    , onMouseEnter :: MouseEvent
    , onMouseLeave :: MouseEvent
    , onMouseMove :: MouseEvent
    , onMouseOut :: MouseEvent
    , onMouseOver :: MouseEvent
    , onMouseUp :: MouseEvent
    , onPaste :: ClipboardEvent
    , onPointerCancel :: Event
    , onPointerDown :: Event
    , onPointerEnter :: Event
    , onPointerLeave :: Event
    , onPointerMove :: Event
    , onPointerOut :: Event
    , onPointerOver :: Event
    , onPointerUp :: Event
    , onTouchCancel :: TouchEvent
    , onTouchEnd :: TouchEvent
    , onTouchEnter :: TouchEvent
    , onTouchLeave :: TouchEvent
    , onTouchMove :: TouchEvent
    , onTouchStart :: TouchEvent
    , onTransitionEnd :: Event
    , onWheel :: WheelEvent
    , spellcheck :: Boolean
    , style :: String
    , tabIndex :: Int
    , title :: String
    )

  with type

    ( autofocus :: Boolean
    , disabled :: Boolean
    , form :: String
    , formAction :: String
    , formEncType :: MediaType
    , formMethod :: FormMethod
    , formNoValidate :: Boolean
    , formTarget :: String
    , name :: String
    , "type" :: ButtonType
    , value :: String
    , accessKey :: String
    , "class" :: String
    , contentEditable :: Boolean
    , dir :: DirValue
    , draggable :: Boolean
    , gotPointerCapture :: Event
    , hidden :: Boolean
    , id :: String
    , lang :: String
    , lostPointerCapture :: Event
    , onBlur :: FocusEvent
    , onClick :: MouseEvent
    , onContextMenu :: Event
    , onCopy :: ClipboardEvent
    , onCut :: ClipboardEvent
    , onDoubleClick :: MouseEvent
    , onDrag :: DragEvent
    , onDragEnd :: DragEvent
    , onDragEnter :: DragEvent
    , onDragExit :: DragEvent
    , onDragLeave :: DragEvent
    , onDragOver :: DragEvent
    , onDragStart :: DragEvent
    , onDrop :: DragEvent
    , onFocus :: FocusEvent
    , onFocusIn :: FocusEvent
    , onFocusOut :: FocusEvent
    , onKeyDown :: KeyboardEvent
    , onKeyPress :: KeyboardEvent
    , onKeyUp :: KeyboardEvent
    , onMouseDown :: MouseEvent
    , onMouseEnter :: MouseEvent
    , onMouseLeave :: MouseEvent
    , onMouseMove :: MouseEvent
    , onMouseOut :: MouseEvent
    , onMouseOver :: MouseEvent
    , onMouseUp :: MouseEvent
    , onPaste :: ClipboardEvent
    , onPointerCancel :: Event
    , onPointerDown :: Event
    , onPointerEnter :: Event
    , onPointerLeave :: Event
    , onPointerMove :: Event
    , onPointerOut :: Event
    , onPointerOver :: Event
    , onPointerUp :: Event
    , onTouchCancel :: TouchEvent
    , onTouchEnd :: TouchEvent
    , onTouchEnter :: TouchEvent
    , onTouchLeave :: TouchEvent
    , onTouchMove :: TouchEvent
    , onTouchStart :: TouchEvent
    , onTransitionEnd :: Event
    , onWheel :: WheelEvent
    , spellcheck :: Boolean
    , style :: String
    , tabIndex :: Int
    , title :: String
    )


while trying to match type IProp
                             ( onClick :: MouseEvent
                             , accessKey :: String
                             , "class" :: String
                             , contentEditable :: Boolean
                             , dir :: DirValue
                             , draggable :: Boolean
                             , gotPointerCapture :: Event
                             , hidden :: Boolean
                             , id :: String
                             , lang :: String
                             , lostPointerCapture :: Event
                             , onBlur :: FocusEvent
                             , onContextMenu :: Event
                             , onCopy :: ClipboardEvent
                             , onCut :: ClipboardEvent
                             , onDoubleClick :: MouseEvent
                             , onDrag :: DragEvent
                             , onDragEnd :: DragEvent
                             , onDragEnter :: DragEvent
                             , onDragExit :: DragEvent
                             , onDragLeave :: DragEvent
                             , onDragOver :: DragEvent
                             , onDragStart :: DragEvent
                             , onDrop :: DragEvent
                             , onFocus :: FocusEvent
                             , onFocusIn :: FocusEvent
                             , onFocusOut :: FocusEvent
                             , onKeyDown :: KeyboardEvent
                             , onKeyPress :: KeyboardEvent
                             , onKeyUp :: KeyboardEvent
                             , onMouseDown :: MouseEvent
                             , onMouseEnter :: MouseEvent
                             , onMouseLeave :: MouseEvent
                             , onMouseMove :: MouseEvent
                             , onMouseOut :: MouseEvent
                             , onMouseOver :: MouseEvent
                             , onMouseUp :: MouseEvent
                             , onPaste :: ClipboardEvent
                             , onPointerCancel :: Event
                             , onPointerDown :: Event
                             , onPointerEnter :: Event
                             , onPointerLeave :: Event
                             , onPointerMove :: Event
                             , onPointerOut :: Event
                             , onPointerOver :: Event
                             , onPointerUp :: Event
                             , onScroll :: Event
                             , onTouchCancel :: TouchEvent
                             , onTouchEnd :: TouchEvent
                             , onTouchEnter :: TouchEvent
                             , onTouchLeave :: TouchEvent
                             , onTouchMove :: TouchEvent
                             , onTouchStart :: TouchEvent
                             , onTransitionEnd :: Event
                             , onWheel :: WheelEvent
                             , spellcheck :: Boolean
                             , style :: String
                             , tabIndex :: Int
                             , title :: String
                             )
  with type IProp
              ( onCopy :: ClipboardEvent
              , onCut :: ClipboardEvent
              , onPaste :: ClipboardEvent
              , onBlur :: FocusEvent
              , onFocus :: FocusEvent
              , onFocusIn :: FocusEvent
              , onFocusOut :: FocusEvent
              , onTransitionEnd :: Event
              , onKeyDown :: KeyboardEvent
              , onKeyUp :: KeyboardEvent
              , onKeyPress :: KeyboardEvent
              , onPointerOver :: Event
              , onPointerEnter :: Event
              , onPointerDown :: Event
              , onPointerMove :: Event
              , onPointerUp :: Event
              , onPointerCancel :: Event
              , onPointerOut :: Event
              , onPointerLeave :: Event
              , gotPointerCapture :: Event
              , lostPointerCapture :: Event
              , onTouchCancel :: TouchEvent
              , onTouchEnd :: TouchEvent
              , onTouchEnter :: TouchEvent
              , onTouchLeave :: TouchEvent
              , onTouchMove :: TouchEvent
              , onTouchStart :: TouchEvent
              , onDrag :: DragEvent
              , onDragEnd :: DragEvent
              , onDragExit :: DragEvent
              , onDragEnter :: DragEvent
              , onDragLeave :: DragEvent
              , onDragOver :: DragEvent
              , onDragStart :: DragEvent
              , onDrop :: DragEvent
              , onDoubleClick :: MouseEvent
              , onClick :: MouseEvent
              , onMouseDown :: MouseEvent
              , onMouseEnter :: MouseEvent
              , onMouseLeave :: MouseEvent
              , onMouseMove :: MouseEvent
              , onMouseOver :: MouseEvent
              , onMouseOut :: MouseEvent
              , onMouseUp :: MouseEvent
              , onWheel :: WheelEvent
              , id :: String
              , title :: String
              , "class" :: String
              , style :: String
              , spellcheck :: Boolean
              , draggable :: Boolean
              , lang :: String
              , dir :: DirValue
              , hidden :: Boolean
              , tabIndex :: Int
              , accessKey :: String
              , contentEditable :: Boolean
              , onContextMenu :: Event
              , autofocus :: Boolean
              , disabled :: Boolean
              , form :: String
              , formAction :: String
              , formEncType :: MediaType
              , formMethod :: FormMethod
              , formNoValidate :: Boolean
              , formTarget :: String
              , name :: String
              , "type" :: ButtonType
              , value :: String
              )
while checking that expression (recursiveQuery onClick) (\e ->
                                                           (apply action) (LogToConsole "message")
                                                        )
  has type IProp
             ( onCopy :: ClipboardEvent
             , onCut :: ClipboardEvent
             , onPaste :: ClipboardEvent
             , onBlur :: FocusEvent
             , onFocus :: FocusEvent
             , onFocusIn :: FocusEvent
             , onFocusOut :: FocusEvent
             , onTransitionEnd :: Event
             , onKeyDown :: KeyboardEvent
             , onKeyUp :: KeyboardEvent
             , onKeyPress :: KeyboardEvent
             , onPointerOver :: Event
             , onPointerEnter :: Event
             , onPointerDown :: Event
             , onPointerMove :: Event
             , onPointerUp :: Event
             , onPointerCancel :: Event
             , onPointerOut :: Event
             , onPointerLeave :: Event
             , gotPointerCapture :: Event
             , lostPointerCapture :: Event
             , onTouchCancel :: TouchEvent
             , onTouchEnd :: TouchEvent
             , onTouchEnter :: TouchEvent
             , onTouchLeave :: TouchEvent
             , onTouchMove :: TouchEvent
             , onTouchStart :: TouchEvent
             , onDrag :: DragEvent
             , onDragEnd :: DragEvent
             , onDragExit :: DragEvent
             , onDragEnter :: DragEvent
             , onDragLeave :: DragEvent
             , onDragOver :: DragEvent
             , onDragStart :: DragEvent
             , onDrop :: DragEvent
             , onDoubleClick :: MouseEvent
             , onClick :: MouseEvent
             , onMouseDown :: MouseEvent
             , onMouseEnter :: MouseEvent
             , onMouseLeave :: MouseEvent
             , onMouseMove :: MouseEvent
             , onMouseOver :: MouseEvent
             , onMouseOut :: MouseEvent
             , onMouseUp :: MouseEvent
             , onWheel :: WheelEvent
             , id :: String
             , title :: String
             , "class" :: String
             , style :: String
             , spellcheck :: Boolean
             , draggable :: Boolean
             , lang :: String
             , dir :: DirValue
             , hidden :: Boolean
             , tabIndex :: Int
             , accessKey :: String
             , contentEditable :: Boolean
             , onContextMenu :: Event
             , autofocus :: Boolean
             , disabled :: Boolean
             , form :: String
             , formAction :: String
             , formEncType :: MediaType
             , formMethod :: FormMethod
             , formNoValidate :: Boolean
             , formTarget :: String
             , name :: String
             , "type" :: ButtonType
             , value :: String
             )
             t0
in value declaration component

where t0 is an unknown type

See https://github.com/purescript/documentation/blob/master/errors/TypesDoNotUnify.md for more information,
or to contribute content related to this error.


* ERROR: Subcommand terminated with exit code 1
@JordanMartinez

This comment has been minimized.

Copy link

JordanMartinez commented Jul 27, 2018

I realized that it'd be easier to just change input to take the result of H.action. This now works:

inputR :: forall f a. (a -> f Unit) -> a -> Maybe (f Unit)
inputR f x = Just $ (f x)

Usage: recursively evaluate queries until a final terminating query:

HH.button
  [  HE.onClick (inputR \e ->
      PreventDefault (toEvent e) $
      H.action $ LogToConsole $ "Button clicked - ClientX: x" <> (show $ clientX e)
     )
  ,  HE.onMouseMove (inputR \mouseEvent ->
       let
         event = toEvent mouseEvent
       in
         PreventDefault event $
         StopPropagation event $
         StopImmediatePropagation event $
         H.action $ LogToConsole $
           "preventDefault and stopPropagation and stopImmediatePropagation" <>
           "were called on Button's mouse move event"
     )
  ]
  [ HH.text "Click me and check the console." ]
@JordanMartinez

This comment has been minimized.

Copy link

JordanMartinez commented Jul 27, 2018

As a whole, however, I wonder if it would be better to have a query LiftEffect (function) (recordOfArguments) a. However, I think that would make Query have kind * -> * -> *.
Maybe this feature already exists in Halogen and I just don't know about it yet.

Is it possible to define a query that gets inherited by all component types? Then, we wouldn't need to get boilerplatey and define the PreventDefault and StopPropagation for all components that use it.

What if the Query type in components was changed to something like Either (UsualQueries a) (ComponentQueries a)? When one wants to call preventDefault, they just use Left $ PreventDefault (toEvent e) (or a function that handles the type correctly for them) and when they use their own Query, they write Right $ MyOwnQuery.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment