InputWithAutocomplete.purs 3.98 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

type Completions = Array String

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

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

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

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

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

      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).
72 73
        onBlur completions e = setTimeout 100 $ do
          T.write_ [] completions
74

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

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

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 120 121
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