Lightweight frontend library for GHCJS
Go to file
2023-09-17 22:15:01 +04:00
benchmarks Rename newStep -> dynStep 2023-02-02 22:45:09 +04:00
examples Migrate most things to javascript backend 2023-09-17 22:15:01 +04:00
src Migrate most things to javascript backend 2023-09-17 22:15:01 +04:00
.gitignore Some breaking changes 2021-12-25 10:16:45 +04:00
8.6.0.nix Revokable finalizers 2023-05-19 22:06:51 +04:00
8.10.7.nix Change 8.10.7.nix 2023-01-28 20:27:50 +04:00
9.7.2.nix WIP: Build with js-backend 2023-08-11 18:57:19 +04:00
htmlt.cabal Migrate most things to javascript backend 2023-09-17 22:15:01 +04:00
README.md Minor changes 2023-05-19 05:03:13 +04:00
shell.nix Add benchmarks 2021-10-24 05:38:01 +04:00

Lightweight frontend library for GHCJS with focus on minimalism and simplicity

Getting started

First you have to install nix package manager to download and install the dependecies. By default nix-shell gets GHCJS compiler from reflex-platform project. You have to add their binary caches to your nix.conf to avoid building everything from sources

# ~/.config/nix.conf
substituters = https://nixcache.reflex-frp.org
trusted-substituters = https://nixcache.reflex-frp.org
trusted-public-keys = ryantrinkle.com-1:JJiAKaRv9mWgpVAz8dwewnZe0AzzEAzPkagE9SP5NWI=

Build the examples:

# Clone the repository
git clone https://github.com/lagunoff/htmlt.git
cd htmlt
# Enter the nix-shell
nix-shell
# Build examples with cabal
cabal build -fexamples --ghcjs --ghcjs-options="-j"

Once cabal build is successful you can find the js executables in dist-newstyle/build/x86_64-linux/ghcjs-8.6.0.1/htmlt-0.1.0.0/x/ and run them opening index.html in browser

Simple example

-- Example featuring <input> element and two buttons. The input value
-- is synchronized with 'DynRef's state and can be modified by either entering a
-- number into the input or by clicking one of the two buttons
main :: IO ()
main = void $ attachToBody do
  -- First create a 'DynRef
  counterRef <- newRef @Int 0
  div_ do
    input_ do
      -- Show the value inside <input>
      dynProp "value" $ T.pack . show <$> fromRef counterRef
      -- Parse and update the value on each InputEvent
      on "input" $ decodeEvent intDecoder $ writeRef counterRef
    br_
    -- Decrease the value on each click
    button_ do
      on_ "click" $ modifyRef counterRef pred
      text "Decrease"
    -- Increase the value on each click
    button_ do
      on_ "click" $ modifyRef counterRef succ
      text "Increase"
  where
    intDecoder =
      valueDecoder >=> MaybeT . pure . readMaybe . T.unpack

Open the demo

Quick API summary

-- Constructing DOM
el :: Text -> Html a -> Html a
elns :: Text -> Text -> Html a -> Html a
text :: Text -> Html ()
dynText :: Dynamic Text -> Html ()

-- Applying attributes and properties
prop :: Text -> v -> Html ()
dynProp :: Text -> Dynamic v -> Html ()
attr :: Text -> Text -> Html ()
dynAttr :: Text -> Text -> Html ()
toggleClass :: Text -> Dynamic Bool -> Html ()
toggleAttr :: Text -> Dynamic Bool -> Html ()
dynStyle :: Text -> Dynamic Text -> Html ()
dynStyles :: Dynamic Text -> Html ()
dynValue :: Dynamic Text -> Html ()
dynClass :: Dynamic Text -> Html ()
dynChecked :: Dynamic Bool -> Html ()
dynDisabled :: Dynamic Bool -> Html ()

-- Handling DOM events
on :: EventName -> (DOMEvent -> Step ()) -> Html ()
on_ :: EventName -> Step () -> Html ()
onOptions :: EventName -> ListenerOpts -> (DOMEvent -> Step ()) -> Html ()
onDecoder :: EventName -> Decoder a -> (a -> Step ()) -> Html ()
onGlobalEvent :: ListenerOpts -> DOMNode -> EventName -> (DOMEvent -> Step ()) -> Html ()

-- Decoding data from DOM Events
mouseDeltaDecoder :: JSVal -> MaybeT m MouseDelta
clientXYDecoder :: JSVal -> MaybeT m (Point Int)
offsetXYDecoder :: JSVal -> MaybeT m (Point Int)
pageXYDecoder :: JSVal -> MaybeT m (Point Int)
keyModifiersDecoder :: JSVal -> MaybeT m KeyModifiers
keyCodeDecoder :: JSVal -> MaybeT m Int
keyboardEventDecoder :: JSVal -> MaybeT m KeyboardEvent
valueDecoder :: JSVal -> MaybeT m Text
checkedDecoder :: JSVal -> MaybeT m Bool

-- DOM extras, useful helpers
unsafeHtml :: MonadIO m => Text -> HtmlT m ()
portal :: Monad m => DOMElement -> HtmlT m a -> HtmlT m a
addFinalizer :: MonadReactive m => IO () -> m ()

-- Dynamic collections
simpleList :: Dynamic [a] -> (Int -> DynRef a -> Html ()) -> Html ()

-- Arbitrary dynamic content
dyn :: Dynamic (Html ()) -> Html ()

-- Contructing Events
newEvent :: MonadReactive m => m (Event a, Trigger a)
fmap :: (a -> b) -> Event a -> Event a
never :: Event a
updates :: Dynamic a -> Event a
mapMaybeE :: (a -> Maybe b) -> Event a -> Event b

-- Constructing Dynamics
constDyn :: a -> Dynamic a
fromRef :: DynRef a -> Dynamic a
fmap :: (a -> b) -> Dynamic a -> Dynamic b
(<*>) :: Dynamic (a -> b) -> Dynamic a -> Dynamic b
mapDyn :: MonadReactive m => Dynamic a -> (a -> b)-> m (Dynamic b)
mapDyn2 :: MonadReactive m => Dynamic a -> Dynamic b -> (a -> b -> c) -> m (Dynamic c)
holdUniqDyn :: Eq a => Dynamic a -> Dynamic a
holdUniqDynBy :: (a -> a -> Bool) -> Dynamic a -> Dynamic a

-- Constructing DynRefs
newRef :: MonadReactive m => a -> m (DynRef a)
lensMap :: Lens' s a -> DynRef s -> DynRef a

-- Read and write DynRefs, Dynamics
readDyn :: MonadIO m => Dynamic a -> m a
readRef :: MonadIO m => DynRef a -> m a
writeRef :: DynRef a -> a -> Step ()
modifyRef :: DynRef a -> (a -> a) -> Step ()

-- Starting and shutting down the application
atatchOptions :: StartOpts -> Html a -> IO (a, RunningApp)
attachTo :: DOMElement -> Html a -> IO (a, RunningApp)
attachToBody :: Html a -> IO (a, RunningApp)
detach :: RunningApp -> IO ()

Other examples

Counter source demo
TodoMVC source demo
Simple Routing source demo

Todos

  • API to display sum types
  • Reduce compile time by getting rid of ghcjs-dom and jsaddle-dom from dependency list
  • Add benchmarks
  • More examples and documentation
  • Similar library for ReactNative