module Reflex.Dom.Widget.Input (module Reflex.Dom.Widget.Input, def, (&), (.~)) where
import Prelude
import Reflex.Dom.Class
import Reflex.Dom.Widget.Basic
import Reflex
import Reflex.Host.Class
import GHCJS.DOM.HTMLInputElement
import GHCJS.DOM.HTMLTextAreaElement
import GHCJS.DOM.Element
import GHCJS.DOM.HTMLSelectElement
import GHCJS.DOM.EventM
import GHCJS.DOM.UIEvent
import Data.Monoid
import Data.Map as Map
import Control.Lens
import Control.Monad hiding (forM_)
import Control.Monad.IO.Class
import Data.Default
import Data.Maybe
import Safe
import Data.Dependent.Sum (DSum (..))
data TextInput t
= TextInput { _textInput_value :: Dynamic t String
, _textInput_keypress :: Event t Int
, _textInput_keydown :: Event t Int
, _textInput_keyup :: Event t Int
, _textInput_hasFocus :: Dynamic t Bool
, _textInput_element :: HTMLInputElement
}
data TextInputConfig t
= TextInputConfig { _textInputConfig_inputType :: String
, _textInputConfig_initialValue :: String
, _textInputConfig_setValue :: Event t String
, _textInputConfig_attributes :: Dynamic t (Map String String)
}
instance Reflex t => Default (TextInputConfig t) where
def = TextInputConfig { _textInputConfig_inputType = "text"
, _textInputConfig_initialValue = ""
, _textInputConfig_setValue = never
, _textInputConfig_attributes = constDyn mempty
}
textInput :: MonadWidget t m => TextInputConfig t -> m (TextInput t)
textInput (TextInputConfig inputType initial eSetValue dAttrs) = do
e <- liftM castToHTMLInputElement $ buildEmptyElement "input" =<< mapDyn (Map.insert "type" inputType) dAttrs
liftIO $ htmlInputElementSetValue e initial
performEvent_ $ fmap (liftIO . htmlInputElementSetValue e) eSetValue
eChange <- wrapDomEvent e elementOninput $ liftIO $ htmlInputElementGetValue e
postGui <- askPostGui
runWithActions <- askRunWithActions
eChangeFocus <- newEventWithTrigger $ \eChangeFocusTrigger -> do
unsubscribeOnblur <- liftIO $ elementOnblur e $ liftIO $ do
postGui $ runWithActions [eChangeFocusTrigger :=> False]
unsubscribeOnfocus <- liftIO $ elementOnfocus e $ liftIO $ do
postGui $ runWithActions [eChangeFocusTrigger :=> True]
return $ liftIO $ unsubscribeOnblur >> unsubscribeOnfocus
dFocus <- holdDyn False eChangeFocus
eKeypress <- wrapDomEvent e elementOnkeypress getKeyEvent
eKeydown <- wrapDomEvent e elementOnkeydown getKeyEvent
eKeyup <- wrapDomEvent e elementOnkeyup getKeyEvent
dValue <- holdDyn initial $ leftmost [eSetValue, eChange]
return $ TextInput dValue eKeypress eKeydown eKeyup dFocus e
textInputGetEnter :: Reflex t => TextInput t -> Event t ()
textInputGetEnter i = fmapMaybe (\n -> if n == keycodeEnter then Just () else Nothing) $ _textInput_keypress i
data TextAreaConfig t
= TextAreaConfig { _textAreaConfig_initialValue :: String
, _textAreaConfig_setValue :: Event t String
, _textAreaConfig_attributes :: Dynamic t (Map String String)
}
instance Reflex t => Default (TextAreaConfig t) where
def = TextAreaConfig { _textAreaConfig_initialValue = ""
, _textAreaConfig_setValue = never
, _textAreaConfig_attributes = constDyn mempty
}
data TextArea t
= TextArea { _textArea_value :: Dynamic t String
, _textArea_element :: HTMLTextAreaElement
, _textArea_hasFocus :: Dynamic t Bool
, _textArea_keypress :: Event t Int
}
textArea :: MonadWidget t m => TextAreaConfig t -> m (TextArea t)
textArea (TextAreaConfig initial eSet attrs) = do
e <- liftM castToHTMLTextAreaElement $ buildEmptyElement "textarea" attrs
liftIO $ htmlTextAreaElementSetValue e initial
postGui <- askPostGui
runWithActions <- askRunWithActions
eChangeFocus <- newEventWithTrigger $ \eChangeFocusTrigger -> do
unsubscribeOnblur <- liftIO $ elementOnblur e $ liftIO $ do
postGui $ runWithActions [eChangeFocusTrigger :=> False]
unsubscribeOnfocus <- liftIO $ elementOnfocus e $ liftIO $ do
postGui $ runWithActions [eChangeFocusTrigger :=> True]
return $ liftIO $ unsubscribeOnblur >> unsubscribeOnfocus
performEvent_ $ fmap (liftIO . htmlTextAreaElementSetValue e) eSet
f <- holdDyn False eChangeFocus
ev <- wrapDomEvent e elementOninput $ liftIO $ htmlTextAreaElementGetValue e
v <- holdDyn initial $ leftmost [eSet, ev]
eKeypress <- wrapDomEvent e elementOnkeypress getKeyEvent
return $ TextArea v e f eKeypress
data CheckboxConfig t
= CheckboxConfig { _checkboxConfig_setValue :: Event t Bool
, _checkboxConfig_attributes :: Dynamic t (Map String String)
}
instance Reflex t => Default (CheckboxConfig t) where
def = CheckboxConfig { _checkboxConfig_setValue = never
, _checkboxConfig_attributes = constDyn mempty
}
data Checkbox t
= Checkbox { _checkbox_value :: Dynamic t Bool
}
checkbox :: MonadWidget t m => Bool -> CheckboxConfig t -> m (Checkbox t)
checkbox checked config = do
attrs <- mapDyn (\c -> Map.insert "type" "checkbox" $ (if checked then Map.insert "checked" "checked" else Map.delete "checked") c) (_checkboxConfig_attributes config)
e <- liftM castToHTMLInputElement $ buildEmptyElement "input" attrs
eClick <- wrapDomEvent e elementOnclick $ liftIO $ htmlInputElementGetChecked e
performEvent_ $ fmap (\v -> liftIO $ htmlInputElementSetChecked e $! v) $ _checkboxConfig_setValue config
dValue <- holdDyn checked $ leftmost [_checkboxConfig_setValue config, eClick]
return $ Checkbox dValue
checkboxView :: MonadWidget t m => Dynamic t (Map String String) -> Dynamic t Bool -> m (Event t Bool)
checkboxView dAttrs dValue = do
e <- liftM castToHTMLInputElement $ buildEmptyElement "input" =<< mapDyn (Map.insert "type" "checkbox") dAttrs
eClicked <- wrapDomEvent e elementOnclick $ do
preventDefault
liftIO $ htmlInputElementGetChecked e
schedulePostBuild $ do
v <- sample $ current dValue
when v $ liftIO $ htmlInputElementSetChecked e True
performEvent_ $ fmap (\v -> liftIO $ htmlInputElementSetChecked e $! v) $ updated dValue
return eClicked
data Dropdown t k
= Dropdown { _dropdown_value :: Dynamic t k
}
data DropdownConfig t k
= DropdownConfig { _dropdownConfig_setValue :: Event t k
, _dropdownConfig_attributes :: Dynamic t (Map String String)
}
instance (Reflex t, Ord k, Show k, Read k) => Default (DropdownConfig t k) where
def = DropdownConfig { _dropdownConfig_setValue = never
, _dropdownConfig_attributes = constDyn mempty
}
dropdown :: forall k t m. (MonadWidget t m, Ord k, Show k, Read k) => k -> Dynamic t (Map k String) -> DropdownConfig t k -> m (Dropdown t k)
dropdown k0 options (DropdownConfig setK attrs) = do
(eRaw, _) <- elDynAttr' "select" attrs $ do
optionsWithDefault <- mapDyn (`Map.union` (k0 =: "")) options
listWithKey optionsWithDefault $ \k v -> do
elAttr "option" ("value" =: show k <> if k == k0 then "selected" =: "selected" else mempty) $ dynText v
let e = castToHTMLSelectElement $ _el_element eRaw
performEvent_ $ fmap (liftIO . htmlSelectElementSetValue e . show) setK
eChange <- wrapDomEvent e elementOnchange $ do
kStr <- liftIO $ htmlSelectElementGetValue e
return $ readMay kStr
let readKey opts mk = fromMaybe k0 $ do
k <- mk
guard $ Map.member k opts
return k
dValue <- combineDyn readKey options =<< holdDyn (Just k0) (leftmost [eChange, fmap Just setK])
return $ Dropdown dValue
liftM concat $ mapM makeLenses
[ ''TextAreaConfig
, ''TextArea
, ''TextInputConfig
, ''TextInput
, ''DropdownConfig
, ''Dropdown
, ''CheckboxConfig
, ''Checkbox
]
class HasAttributes a where
type Attrs a :: *
attributes :: Lens' a (Attrs a)
instance HasAttributes (TextAreaConfig t) where
type Attrs (TextAreaConfig t) = Dynamic t (Map String String)
attributes = textAreaConfig_attributes
instance HasAttributes (TextInputConfig t) where
type Attrs (TextInputConfig t) = Dynamic t (Map String String)
attributes = textInputConfig_attributes
instance HasAttributes (DropdownConfig t k) where
type Attrs (DropdownConfig t k) = Dynamic t (Map String String)
attributes = dropdownConfig_attributes
instance HasAttributes (CheckboxConfig t) where
type Attrs (CheckboxConfig t) = Dynamic t (Map String String)
attributes = checkboxConfig_attributes
class HasSetValue a where
type SetValue a :: *
setValue :: Lens' a (SetValue a)
instance HasSetValue (TextAreaConfig t) where
type SetValue (TextAreaConfig t) = Event t String
setValue = textAreaConfig_setValue
instance HasSetValue (TextInputConfig t) where
type SetValue (TextInputConfig t) = Event t String
setValue = textInputConfig_setValue
instance HasSetValue (DropdownConfig t k) where
type SetValue (DropdownConfig t k) = Event t k
setValue = dropdownConfig_setValue
instance HasSetValue (CheckboxConfig t) where
type SetValue (CheckboxConfig t) = Event t Bool
setValue = checkboxConfig_setValue
class HasValue a where
type Value a :: *
value :: a -> Value a
instance HasValue (TextArea t) where
type Value (TextArea t) = Dynamic t String
value = _textArea_value
instance HasValue (TextInput t) where
type Value (TextInput t) = Dynamic t String
value = _textInput_value
instance HasValue (Dropdown t k) where
type Value (Dropdown t k) = Dynamic t k
value = _dropdown_value
instance HasValue (Checkbox t) where
type Value (Checkbox t) = Dynamic t Bool
value = _checkbox_value