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 as Input
import GHCJS.DOM.HTMLTextAreaElement as TextArea
import GHCJS.DOM.Element hiding (error)
import GHCJS.DOM.HTMLSelectElement as Select
import GHCJS.DOM.EventM
import GHCJS.DOM.File
import qualified GHCJS.DOM.FileList as FileList
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_input :: Event 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
Input.setValue e $ Just initial
performEvent_ $ fmap (Input.setValue e . Just) eSetValue
eChange <- wrapDomEvent e (`on` input) $ fromMaybe "" <$> Input.getValue e
postGui <- askPostGui
runWithActions <- askRunWithActions
eChangeFocus <- newEventWithTrigger $ \eChangeFocusTrigger -> do
unsubscribeOnblur <- on e blurEvent $ liftIO $ do
postGui $ runWithActions [eChangeFocusTrigger :=> Identity False]
unsubscribeOnfocus <- on e focusEvent $ liftIO $ do
postGui $ runWithActions [eChangeFocusTrigger :=> Identity True]
return $ liftIO $ unsubscribeOnblur >> unsubscribeOnfocus
dFocus <- holdDyn False eChangeFocus
eKeypress <- wrapDomEvent e (`on` keyPress) getKeyEvent
eKeydown <- wrapDomEvent e (`on` keyDown) getKeyEvent
eKeyup <- wrapDomEvent e (`on` keyUp) getKeyEvent
dValue <- holdDyn initial $ leftmost [eSetValue, eChange]
return $ TextInput dValue eChange 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_input :: Event 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
TextArea.setValue e $ Just initial
postGui <- askPostGui
runWithActions <- askRunWithActions
eChangeFocus <- newEventWithTrigger $ \eChangeFocusTrigger -> do
unsubscribeOnblur <- on e blurEvent $ liftIO $ do
postGui $ runWithActions [eChangeFocusTrigger :=> Identity False]
unsubscribeOnfocus <- on e focusEvent $ liftIO $ do
postGui $ runWithActions [eChangeFocusTrigger :=> Identity True]
return $ liftIO $ unsubscribeOnblur >> unsubscribeOnfocus
performEvent_ $ fmap (TextArea.setValue e . Just) eSet
f <- holdDyn False eChangeFocus
ev <- wrapDomEvent e (`on` input) $ fromMaybe "" <$> TextArea.getValue e
v <- holdDyn initial $ leftmost [eSet, ev]
eKeypress <- wrapDomEvent e (`on` keyPress) getKeyEvent
return $ TextArea v ev 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_change :: Event 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 (`on` click) $ Input.getChecked e
performEvent_ $ fmap (\v -> Input.setChecked e $! v) $ _checkboxConfig_setValue config
dValue <- holdDyn checked $ leftmost [_checkboxConfig_setValue config, eClick]
return $ Checkbox dValue eClick
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 (`on` click) $ do
preventDefault
Input.getChecked e
schedulePostBuild $ do
v <- sample $ current dValue
when v $ Input.setChecked e True
performEvent_ $ fmap (\v -> Input.setChecked e $! v) $ updated dValue
return eClicked
data FileInput t
= FileInput { _fileInput_value :: Dynamic t [File]
, _fileInput_element :: HTMLInputElement
}
data FileInputConfig t
= FileInputConfig { _fileInputConfig_attributes :: Dynamic t (Map String String)
}
instance Reflex t => Default (FileInputConfig t) where
def = FileInputConfig { _fileInputConfig_attributes = constDyn mempty
}
fileInput :: MonadWidget t m => FileInputConfig t -> m (FileInput t)
fileInput (FileInputConfig dAttrs) = do
e <- liftM castToHTMLInputElement $ buildEmptyElement "input" =<< mapDyn (Map.insert "type" "file") dAttrs
eChange <- wrapDomEvent e (flip on change) $ do
Just files <- getFiles e
len <- FileList.getLength files
mapM (liftM (fromMaybe (error "fileInput: fileListItem returned null")) . FileList.item files) $ init [0..len]
dValue <- holdDyn [] eChange
return $ FileInput dValue e
data Dropdown t k
= Dropdown { _dropdown_value :: Dynamic t k
, _dropdown_change :: Event 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 (Select.setValue e . Just . show) setK
eChange <- wrapDomEvent e (`on` change) $ do
kStr <- fromMaybe "" <$> Select.getValue 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 (attachDynWith readKey options eChange)
liftM concat $ mapM makeLenses
[ ''TextAreaConfig
, ''TextArea
, ''TextInputConfig
, ''TextInput
, ''DropdownConfig
, ''Dropdown
, ''CheckboxConfig
, ''Checkbox
]
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 (FileInput t) where
type Value (FileInput t) = Dynamic t [File]
value = _fileInput_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