packages feed

react-flux-1.0.2: test/client/TestClient.hs

{-# LANGUAGE OverloadedStrings, TypeFamilies, ScopedTypeVariables, DeriveAnyClass, FlexibleInstances, DeriveGeneric #-}
module TestClient (testClient) where

import Control.DeepSeq (NFData)
import Control.Monad
import Data.Monoid ((<>))
import Data.Typeable (Typeable)
import Data.Maybe
import Debug.Trace
import GHC.Generics (Generic)
import React.Flux
import React.Flux.Lifecycle
import React.Flux.Internal (toJSString)
import React.Flux.Addons.React
import React.Flux.Addons.Bootstrap

import GHCJS.Types (JSVal, JSString)
import GHCJS.Marshal (fromJSVal)

foreign import javascript unsafe
    "(function(x) { \
    \    if (!window.test_client_output) window.test_client_output = []; \
    \    window.test_client_output.push(x); \
    \})($1)"
    js_output :: JSString -> IO ()

data OutputStoreData = OutputStoreData
    deriving (Show, Typeable)

instance StoreData OutputStoreData where
    type StoreAction OutputStoreData = [String]
    -- log both to the console and to js_output
    transform ss OutputStoreData = do
        mapM_ (js_output . toJSString) ss
        trace (unlines ss) $ return OutputStoreData

outputStore :: ReactStore OutputStoreData
outputStore = mkStore OutputStoreData

output :: [String] -> [SomeStoreAction]
output s = [SomeStoreAction outputStore s]

outputIO :: [String] -> IO ()
outputIO ss = void $ transform ss OutputStoreData

--------------------------------------------------------------------------------
--- Events
--------------------------------------------------------------------------------

logM :: (String -> Bool) -> String
logM f = "alt modifier: " ++ show (f "Alt")

logT :: EventTarget -> String
logT t = eventTargetProp t "id"

rawShowView :: ReactView Int
rawShowView = defineView "raw show view" elemShow

eventsView :: ReactView ()
eventsView = defineView "events" $ \() ->
    div_ $ do
        p_ $ input_ [ "type" $= "text"
                    , "id" $= "keyinput"
                    , "placeholder" $= "onKeyDown"
                    , onKeyDown $ \e k -> output
                        [ "keydown"
                        , show e
                        , show k
                        , logM (keyGetModifierState k)
                        , logT (evtTarget e)
                        , logT (evtCurrentTarget e)
                        ]
                    , onFocus $ \e _ -> output
                        [ "focus"
                        , show e
                        --, logT $ focusRelatedTarget f
                        ]
                    ]

        p_ $ label_ [ "id" $= "clickinput"
                    , onClick $ \e m -> output
                        [ "click"
                        , show e
                        , show m 
                        , logM (mouseGetModifierState m)
                        --, logT (mouseRelatedTarget m)
                        ]
                    ]
           "onClick"

        p_ $ label_ [ "id" $= "touchinput"
                    , onTouchStart $ \e t -> output
                        [ "touchstart"
                        , show e
                        , show t
                        , logM (touchGetModifierState t)
                        , logT (touchTarget $ head $ touchTargets t)
                        , "endtouch"
                        ]
                    ]
           "onTouchStart"

        p_ $ a_ [ "id" $= "some-link"
                , "href" $= "http://www.haskell.org"
                , onClick $ \e _ -> output ["Click some-link"] ++ [preventDefault e]
                ]
                "Testing preventDefault"

        div_ $
            div_ [ "id" $= "outer-div"
                 , onClick $ \_ _ -> output ["Click on outer div"]
                 , capturePhase $ onDoubleClick $ \e _ -> output ["Double click outer div"] ++ [stopPropagation e]
                 ] $ do
                
                span_ [ "id" $= "inner-span"
                      , onClick $ \e _ -> output ["Click inner span"] ++ [stopPropagation e]
                      , onDoubleClick $ \_ _ -> output ["Double click inner span"]
                      ]
                      "Testing stopPropagation"

        p_ [ "id" $= "raw-show-view"] $ view rawShowView 42 mempty

eventsView_ :: ReactElementM eventHandler ()
eventsView_ = view eventsView () mempty

--------------------------------------------------------------------------------
--- Lifecycle
--------------------------------------------------------------------------------

logPandS :: LPropsAndState String Int -> IO ()
logPandS ps = do
    p <- lGetProps ps
    st <- lGetState ps
    outputIO ["Current props and state: " ++ p ++ ", " ++ show st]

foreign import javascript unsafe
    "$1.id"
    js_domGetId :: JSVal -> IO JSVal

logDOM :: LDOM -> IO ()
logDOM dom = do
    this <- lThis dom >>= js_domGetId >>= fromJSVal
    x <- lRef dom "refSt" >>= js_domGetId >>= fromJSVal
    y <- lRef dom "refProps" >>= js_domGetId >>= fromJSVal
    outputIO [ "this id = " ++ fromMaybe "Nothing" this
             , "refStr id = " ++ fromMaybe "Nothing" x
             , "refProps id = " ++ fromMaybe "Nothing" y
             ]


testLifecycle :: ReactView String
testLifecycle = defineLifecycleView "testlifecycle" (12 :: Int) lifecycleConfig
    { lRender = \s p -> p_ ["id" $= "lifecycle-p"] $ do
        span_ "Current state: "
        span_ ["ref" $= "refSt", "id" $= "hello"] (elemShow s)
        span_ ["ref" $= "refProps", "id" $= "world"] $ elemText $ "Current props: " ++ p
        button_ [ "id" $= "increment-state"
                , onClick $ \_ _ st -> ([], Just $ st + 1)
                ] "Incr"

    , lComponentWillMount = Just $ \pAndS setStateFn -> do
        outputIO ["will mount"]
        logPandS pAndS
        setStateFn 100

    , lComponentDidMount = Just $ \pAndS dom _setStateFn -> do
        outputIO ["did mount"]
        logPandS pAndS
        logDOM dom

    , lComponentWillReceiveProps = Just $ \pAndS _dom _setStateFn newProps -> do
        outputIO ["will recv props"]
        logPandS pAndS
        outputIO ["New props: " ++ newProps]

    , lComponentWillUpdate = Just $ \pAndS _dom newProps newState -> do
        outputIO ["will update"]
        logPandS pAndS
        outputIO ["New props: " ++ newProps, "New state: " ++ show newState]

    , lComponentDidUpdate = Just $ \pAndS _dom _setStateFn oldProps oldState -> do
        outputIO ["did update"]
        logPandS pAndS
        outputIO ["Old props: " ++ oldProps, "Old state: " ++ show oldState]

    , lComponentWillUnmount = Just $ \pAndS _dom -> do
        outputIO ["will unmount"]
        logPandS pAndS
    }

testLifecycle_ :: String -> ReactElementM eventHandler ()
testLifecycle_ s = view testLifecycle s $ span_ ["id" $= "child-passed-to-view"] "I am a child!!!"

--------------------------------------------------------------------------------
--- Children passed to view
--------------------------------------------------------------------------------

displayChildren :: ReactView String
displayChildren = defineView "display children" $ \ident ->
    span_ [classNames [("display-children", True), ("missing-name", False)], "id" @= ident]
        childrenPassedToView

displayChildren_ :: String -> ReactElementM handler () -> ReactElementM handler ()
displayChildren_ = view displayChildren

displayChildrenSpec :: ReactElementM handler ()
displayChildrenSpec = ul_ $ do
    li_ $ displayChildren_ "empty-children" mempty
    li_ $ displayChildren_ "single-child-wrapper" $ span_ ["id" $= "single-child"] "Single Child!!"
    li_ $ displayChildren_ "multi-child" $ span_ ["id" $= "child1"] "Child 1" <> span_ ["id" $= "child2"] "Child 2"

--------------------------------------------------------------------------------
--- CSS Transitions
--------------------------------------------------------------------------------

cssTransitions :: ReactView [String]
cssTransitions = defineView "css transitions" $ \items ->
    div_ ["id" $= "css-transitions"] $
        cssTransitionGroup ["transitionName" $= "example"] $
            forM_ (zip items [(0 :: Int)..]) $ \(txt, key) ->
                div_ ["key" @= key] $ span_ ["className" $= "css-transition-entry"] $ elemText txt

--------------------------------------------------------------------------------
--- Bootstrap
--------------------------------------------------------------------------------

bootstrapSpec :: ReactView ()
bootstrapSpec = defineView "bootstrap" $ \() -> div_ ["id" $= "bootstrap"] $ do
    bootstrap_ "Alert" [ "bsStyle" $= "danger"
                       , callback "onDismiss" $ output ["Closing alert"]
                       ] $
        p_ "Hello, World!"

    bootstrap_ "Nav" [ "activeKey" @= (1 :: Int)
                     , callback "onSelect" $ \(i :: Int) -> output ["Switched to " ++ show i]
                     ] $ do
        bootstrap_ "NavItem" ["eventKey" @= (1 :: Int)] "Item 1"
        bootstrap_ "NavItem" ["eventKey" @= (2 :: Int)] "Item 2"
        bootstrap_ "NavItem" ["eventKey" @= (3 :: Int)] "Item 3"

--------------------------------------------------------------------------------
--- shouldComponentUpdate
--------------------------------------------------------------------------------

data ShouldComponentUpdateData = ShouldComponentUpdateData Int String
    deriving (Typeable, Show)

data ShouldComponentUpdateAction = IncrementAllSCUData
                                 | IncrementFirstSCUData
                                 | NoChangeToSCUData
    deriving (Show, Typeable, Generic, NFData)

instance StoreData [ShouldComponentUpdateData] where
    type StoreAction [ShouldComponentUpdateData] = ShouldComponentUpdateAction
    transform NoChangeToSCUData s = return s
    transform IncrementAllSCUData d = return [ShouldComponentUpdateData (i+1) s | ShouldComponentUpdateData i s <- d]
    transform IncrementFirstSCUData (ShouldComponentUpdateData i s:xs) = return $ (ShouldComponentUpdateData (i+1) s) : xs
    transform _ [] = error "Should never happen"

shouldComponentUpdateStore :: ReactStore [ShouldComponentUpdateData]
shouldComponentUpdateStore = mkStore [ ShouldComponentUpdateData 1 "Hello"
                                     , ShouldComponentUpdateData 2 "World"
                                     , ShouldComponentUpdateData 3 "!!!"
                                     ]

-- | This will log wheenver componentWillUpdate lifecycle event occurs
logComponentWillUpdate :: ReactView ShouldComponentUpdateData
logComponentWillUpdate = defineLifecycleView "shouldComponentUpdate spec" () lifecycleConfig
    { lRender = \() (ShouldComponentUpdateData i s) ->
                    span_ (elemShow i) <> span_ (elemText s)
    , lComponentWillUpdate = Just $ \curProps _ (ShouldComponentUpdateData newI newS) () -> do
          ShouldComponentUpdateData curI curS <- lGetProps curProps
          outputIO [ "Component will update"
                   , "current props: " ++ show curI ++ " " ++ curS
                   , "new props: " ++ show newI ++ " " ++ newS
                   ]
    }

shouldComponentUpdateSpec :: ReactView ()
shouldComponentUpdateSpec = defineControllerView "should component update" shouldComponentUpdateStore $ \ds () -> 
    div_ ["id" $= "should-component-update"] $ do
        ul_ $ forM_ (zip ds [(0 :: Int)..]) $ \(su, i)  ->
            li_ $ viewWithKey logComponentWillUpdate i su mempty

        button_ ["id" $= "no-change-scu", onClick $ \_ _ -> [SomeStoreAction shouldComponentUpdateStore NoChangeToSCUData]]
            "No change to data"

        button_ ["id" $= "change-all-scu", onClick $ \_ _ -> [SomeStoreAction shouldComponentUpdateStore IncrementAllSCUData]]
            "Increment all"

        button_ ["id" $= "increment-first-scu", onClick $ \_ _ -> [SomeStoreAction shouldComponentUpdateStore IncrementFirstSCUData]]
            "Increment first entry's integer"

--------------------------------------------------------------------------------
--- Main
--------------------------------------------------------------------------------

-- | Test a lifecycle view with all lifecycle methods nothing
testClient :: ReactView ()
testClient = defineLifecycleView "app" "Hello" lifecycleConfig
    { lRender = \s () -> do
        eventsView_

        when (s /= "") $
            testLifecycle_ s
        button_ [ "id" $= "add-app-str"
                , onClick $ \_ _ s' -> ([], Just $ s' ++ "o")
                ]
                "Add o"
        button_ [ "id" $= "clear-app-str"
                , onClick $ \_ _ _ -> ([], Just "")
                ] "Clear"

        displayChildrenSpec

        view cssTransitions ["A", "B"] mempty

        view bootstrapSpec () mempty

        view shouldComponentUpdateSpec () mempty
    }