InputWithAutocomplete.purs 3.89 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
module Gargantext.Components.InputWithAutocomplete where

import Prelude
import Data.Maybe (Maybe(..))
import Data.Nullable (Nullable, null, toMaybe)
import Data.Tuple.Nested ((/\))
import DOM.Simple as DOM
import DOM.Simple.Event as DE
import Effect (Effect)
import Effect.Timer (setTimeout)
import Reactix as R
import Reactix.DOM.HTML as H
13
import Toestand as T
14 15 16

import Gargantext.Utils.Reactix as R2

17
here :: R2.Here
18
here = R2.here "Gargantext.Components.InputWithAutocomplete"
19

20 21 22 23 24 25 26 27

type Completions = Array String

type Props =
  (
    autocompleteSearch :: String -> Completions
  , onAutocompleteClick :: String -> Effect Unit
  , onEnterPress :: String -> Effect Unit
28
  , state :: T.Box String
29 30
  )

31 32
inputWithAutocomplete :: R2.Component Props
inputWithAutocomplete = R.createElement inputWithAutocompleteCpt
33 34

inputWithAutocompleteCpt :: R.Component Props
35
inputWithAutocompleteCpt = here.component "inputWithAutocomplete" cpt
36
  where
37 38 39 40 41
    cpt props@{ autocompleteSearch
              , onAutocompleteClick
              , onEnterPress
              , state } _ = do
      state' <- T.useLive T.unequal state
42
      inputRef <- R.useRef null
43 44 45
      completions <- T.useBox $ autocompleteSearch state'

      let onFocus completions e = T.write_ (autocompleteSearch state') completions
46 47 48 49

      pure $
        H.span { className: "input-with-autocomplete" }
        [
50
          completionsCpt { completions, onAutocompleteClick, state } []
51 52 53 54 55 56 57 58
        , H.input { type: "text"
                  , ref: inputRef
                  , className: "form-control"
                  , value: state'
                  , on: { blur: onBlur completions
                        , focus: onFocus completions
                        , input: onInput completions
                        , change: onInput completions
59
                        , keyUp: onInputKeyUp inputRef } }
60 61 62 63 64 65 66 67 68 69
        ]

      where

        -- setTimeout is a bit of a hack here -- clicking on autocomplete
        -- element will clear out the blur first, so the autocomplete click
        -- won't fire without a timeout here.  However, blur is very handy and
        -- handles automatic autocomplete search, otherwise I'd have to hide it
        -- in various different places (i.e. carefully handle all possible
        -- events where blur happens and autocomplete should hide).
70 71
        onBlur completions e = setTimeout 100 $ do
          T.write_ [] completions
72

73
        onInput completions e = do
74
          let val = R.unsafeEventValue e
75 76
          T.write_ val state
          T.write_ (autocompleteSearch val) completions
77

78 79
        onInputKeyUp :: R.Ref (Nullable DOM.Element) -> DE.KeyboardEvent -> Effect Unit
        onInputKeyUp inputRef e = do
80
          if DE.key e == "Enter" then do
81
            let val = R.unsafeEventValue e
82
            let mInput = toMaybe $ R.readRef inputRef
83
            T.write_ val state
84 85 86 87 88 89 90
            onEnterPress val
            case mInput of
              Nothing -> pure unit
              Just input -> R2.blur input
          else
            pure $ unit

91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
type CompletionsProps =
  ( completions :: T.Box Completions
  , onAutocompleteClick :: String -> Effect Unit
  , state :: T.Box String
  )

completionsCpt :: R2.Component CompletionsProps
completionsCpt = R.createElement completionsCptCpt

completionsCptCpt :: R.Component CompletionsProps
completionsCptCpt = here.component "completionsCpt" cpt
  where
    cpt { completions, onAutocompleteClick, state } _ = do
      completions' <- T.useLive T.unequal completions

      let className = "completions " <> (if completions' == [] then "d-none" else "")

      pure $ H.div { className }
        [
          H.div { className: "list-group" } (cCpt <$> completions')
        ]
      where
        cCpt c =
          H.button { type: "button"
                    , className: "list-group-item"
                    , on: { click: onClick c } } [ H.text c ]
        onClick c _ = do
          T.write_ c state
          onAutocompleteClick c