Working with multiple ReflexFRP text boxesFiled under: programming
Last week during my flight to West Coast visit my parents for Chinese New Year, I spent sometime with my getting-started-with-haskell project.
Last Fall I started working on a web app version of the image ratio calculator, struggling to understand how the number inputs work with the calculations. Last spring/summer I was working calculating image rations (img-ratio.hs) to understand how Haskell functions could be defined and interacted with through ghci. Now I wanted a GUI interface.
Reading through the calculator tutorial showed new functions I’ve never seen before. Luckily my brother gave me his wifi access code so I could look function definitions on hackage up during the flight.
Tip: Reading documentation is the first step to understanding new code; to find out how it is defined then you can better understand how it is used.
Here are some of the types that come from the Reflex-Dynamic module.
mapDyn :: (Reflex t, MonadHold t m) => (a -> b) -> Dynamic t a -> m (Dynamic t b) combineDyn :: forall t m a b c. (Reflex t, MonadHold t m) => (a -> b -> c) -> Dynamic t a -> Dynamic t b -> m (Dynamic t c) constDyn :: Reflex t => a -> Dynamic t a count :: (Reflex t, MonadHold t m, MonadFix m, Num b) => Event t a -> m (Dynamic t b) dynText :: MonadWidget t m => Dynamic t String -> m ()
I borrowed the numberInput function from the tutorial, added a string for plugging in the initial value.
numberInput :: (MonadWidget t m) => String -> m (Dynamic t (Maybe Double)) numberInput i = do let errorState = Map.singleton "style" "border-color: red" validState = Map.singleton "style" "border-color: green" rec n <- textInput def & textInputConfig_inputType .~ "number" & textInputConfig_initialValue .~ i & textInputConfig_attributes .~ attrs result <- mapDyn readMay _textInput_value n attrs <- mapDyn (\r -> case r of Just _ -> validState Nothing -> errorState) result return result
I also created a function to create the label + text input combo:
box t i = elClass "div" "box height" do el "label" text t y <- numberInput i return y
With both functions defined I could go ahead and write the first version of a working app. I hardcoded the initial values to get the 4:3 ratio as I wanted to focus on getting the reflex dom working. I’ll be going back in to improve the usability of the interface.
main = mainWidgetWithCss (embedFile "style.css") do elClass "div" "top-line" return () elClass "div" "header" do elClass "h1" "logo" text "Image Ratio" elClass "div" "main" do elClass "div" "left" do x1 <- box "width" "1024" y1 <- box "height" "768" x2 <- box "new width" "640" y2 <- box "new height" "480" step1 <- combineDyn (\x y -> (*) <> x <*> y) x2 y1 step2 <- combineDyn (\x y -> (/) <> x <*> y) step1 x1 resultString <- mapDyn show step2 --(Dynamic Spider (Maybe Double)) text "new height = " s <- dynText resultString z <- box "new height" (show s) -- (Dynamic Spider String) return () elClass "div" "right" do elAttr' "div" ("style" =: "width:400px; height:300px; border: 1px solid red; position: fixed;") return () elAttr' "img" ("src" =: "http://placehold.it/640x480" <> "width" =: "400") return () return ()
This is how it turns out with the applied stylesheet:
Files for this example:
Next I’m trying to work on displaying dynamic text in text boxes as soon as the user updates values. I think I’m going to have to take the value out of the monad. Here are some I found that work with monad types.
(=<<) :: Monad m => (a -> m b) -> m a -> m b liftM :: Monad m => (a1 -> r) -> m a1 -> m r fmap :: (a -> b) -> f a -> f b