react-flux 1.2.2 → 1.2.3
raw patch · 9 files changed
+110/−32 lines, 9 files
Files
- ChangeLog.md +10/−0
- react-flux.cabal +1/−1
- src/React/Flux.hs +5/−2
- src/React/Flux/Ajax.hs +4/−0
- src/React/Flux/Internal.hs +41/−8
- src/React/Flux/Views.hs +32/−17
- test/client/TestClient.hs +12/−3
- test/spec/TestClientSpec.hs +4/−0
- test/spec/stack.yaml +1/−1
ChangeLog.md view
@@ -1,3 +1,13 @@+# 1.2.3++* Add `rawJsRendering` function which allows you to inject arbitrary javascript code+ into the rendering function of a view. This is quite low-level and should be a last+ resort, but it can be used if you have especially tricky 3rd-party React classes. Having+ said that, I suggest you try other approaches first.++* Add `transHandler` and `liftViewToStateHandler` utility functions which can be used to transform+ the handler that a `ReactElementM` lives in.+ # 1.2.2 * Update the test suite to use hspec-webdriver-1.2. There was no change to any code
react-flux.cabal view
@@ -1,5 +1,5 @@ name: react-flux-version: 1.2.2+version: 1.2.3 synopsis: A binding to React based on the Flux application architecture for GHCJS category: Web homepage: https://bitbucket.org/wuzzeb/react-flux
src/React/Flux.hs view
@@ -92,6 +92,9 @@ , viewWithIKey , childrenPassedToView , foreignClass+ , rawJsRendering+ , transHandler+ , liftViewToStateHandler , module React.Flux.DOM , module React.Flux.PropertiesAndEvents , module React.Flux.Combinators@@ -143,7 +146,7 @@ #ifdef __GHCJS__ reactRender htmlId rc props = do- (e, _) <- mkReactElement id (return nullRef) (return []) $ view rc props mempty+ (e, _) <- mkReactElement id (ReactThis nullRef) $ view rc props mempty js_ReactRender e (toJSString htmlId) foreign import javascript unsafe@@ -175,7 +178,7 @@ #ifdef __GHCJS__ reactRenderToString includeStatic rc props = do- (e, _) <- mkReactElement id (return nullRef) (return []) $ view rc props mempty+ (e, _) <- mkReactElement id (ReactThis nullRef) $ view rc props mempty sRef <- (if includeStatic then js_ReactRenderStaticMarkup else js_ReactRenderToString) e --return sRef --return $ JSS.unpack sRef
src/React/Flux/Ajax.hs view
@@ -1,3 +1,7 @@+-- | Make calls to the backend from within your stores. This module is low-level+-- in that it mostly directly exposes the XMLHttpRequest access. If you are using+-- servant, <http://hackage.haskell.org/package/react-flux-servant react-flux-servant>+-- for a higher-level interface. module React.Flux.Ajax ( initAjax , RequestTimeout(..)
src/React/Flux/Internal.hs view
@@ -6,12 +6,14 @@ module React.Flux.Internal( ReactViewRef(..) , ReactElementRef(..)+ , ReactThis(..) , HandlerArg(..) , PropertyOrHandler(..) , property , (&=) , ReactElement(..) , ReactElementM(..)+ , transHandler , elemString , elemText , elemJSString@@ -73,6 +75,10 @@ newtype HandlerArg = HandlerArg JSVal instance IsJSVal HandlerArg +-- | The this value during the rendering function+newtype ReactThis state props = ReactThis {reactThisRef :: JSVal }+instance IsJSVal (ReactThis state props)+ instance Show HandlerArg where show _ = "HandlerArg" @@ -85,7 +91,7 @@ { propertyName :: JSString , propertyVal :: ref }- | forall ref. ToJSVal ref => PropertyFromContext + | forall ref. ToJSVal ref => PropertyFromContext { propFromThisName :: JSString , propFromThisVal :: JSVal -> ref -- ^ will be passed this.context }@@ -147,6 +153,11 @@ , ceProps :: props , ceChild :: ReactElement eventHandler }+ | RawJsElement+ { rawTransform :: JSVal -> [ReactElementRef] -> IO ReactElementRef+ -- ^ first arg is this from render method, second argument is the rendering of 'rawChild'+ , rawChild :: ReactElement eventHandler+ } | ChildrenPassedToView | Content JSString | Append (ReactElement eventHandler) (ReactElement eventHandler)@@ -159,6 +170,7 @@ instance Functor ReactElement where fmap f (ForeignElement n p c) = ForeignElement n (map (fmap f) p) (fmap f c) fmap f (ViewElement n k p c) = ViewElement n k p (fmap f c)+ fmap f (RawJsElement t c) = RawJsElement t (fmap f c) fmap _ ChildrenPassedToView = ChildrenPassedToView fmap f (Append a b) = Append (fmap f a) (fmap f b) fmap _ (Content s) = Content s@@ -204,6 +216,12 @@ instance (a ~ ()) => IsString (ReactElementM eventHandler a) where fromString s = elementToM () $ Content $ toJSString s +-- | Transform the event handler for a 'ReactElementM'.+transHandler :: (handler1 -> handler2) -> ReactElementM handler1 a -> ReactElementM handler2 a+transHandler f (ReactElementM writer) = ReactElementM $ mapWriter f' writer+ where+ f' (a, x) = (a, fmap f x)+ -- | Create a text element from a string. The text content is escaped to be HTML safe. -- If you need to insert HTML, instead use the -- <https://facebook.github.io/react/tips/dangerously-set-inner-html.html dangerouslySetInnerHTML>@@ -253,16 +271,15 @@ -- | Execute a ReactElementM to create a javascript React element and a list of callbacks attached -- to nodes within the element. These callbacks will need to be released with 'releaseCallback' -- once the class is re-rendered.-mkReactElement :: forall eventHandler.+mkReactElement :: forall eventHandler state props. (eventHandler -> IO ())- -> IO JSVal -- ^ this.context- -> IO [ReactElementRef] -- ^ this.props.children+ -> ReactThis state props -- ^ this -> ReactElementM eventHandler () -> IO (ReactElementRef, [CallbackToRelease]) #ifdef __GHCJS__ -mkReactElement runHandler getContext getPropsChildren = runWriterT . mToElem+mkReactElement runHandler this = runWriterT . mToElem where -- Run the ReactElementM monad to create a ReactElementRef. mToElem :: ReactElementM eventHandler () -> MkReactElementM ReactElementRef@@ -286,7 +303,7 @@ vRef <- toJSVal val JSO.setProp n vRef obj addPropOrHandlerToObj obj (PropertyFromContext n f) = lift $ do- ctx <- getContext+ ctx <- js_ReactGetContext this vRef <- toJSVal $ f ctx JSO.setProp n vRef obj addPropOrHandlerToObj obj (NestedProperty n vals) = do@@ -321,7 +338,9 @@ createElement EmptyElement = return [] createElement (Append x y) = (++) <$> createElement x <*> createElement y createElement (Content s) = return [js_ReactCreateContent s]- createElement ChildrenPassedToView = lift getPropsChildren+ createElement ChildrenPassedToView = lift $ do+ childRef <- js_ReactGetChildren this+ return $ map ReactElementRef $ JSA.toList childRef createElement (f@(ForeignElement{})) = do obj <- lift $ JSO.create mapM_ (addPropOrHandlerToObj obj) $ fProps f@@ -348,6 +367,11 @@ Nothing -> js_ReactCreateClass rc propsE children return [e] + createElement (RawJsElement trans child) = do+ childNodes <- createElement child+ e <- liftIO $ trans (reactThisRef this) childNodes+ return [e]+ type MkReactElementM a = WriterT [CallbackToRelease] IO a foreign import javascript unsafe@@ -390,6 +414,15 @@ "$r = hsreact$textWrapper" js_textWrapper :: JSVal +foreign import javascript unsafe+ "$1['context']"+ js_ReactGetContext :: ReactThis state props -> IO JSVal++foreign import javascript unsafe+ "hsreact$children_to_array($1['props']['children'])"+ js_ReactGetChildren :: ReactThis state props -> IO JSArray++ js_ReactCreateContent :: JSString -> ReactElementRef js_ReactCreateContent = ReactElementRef . unsafeCoerce @@ -408,7 +441,7 @@ return (jsval cb, wrappedCb) #else-mkReactElement _ _ _ _ = return (ReactElementRef (), [])+mkReactElement _ _ _ = return (ReactElementRef (), []) toJSString :: String -> String toJSString = id
src/React/Flux/Views.hs view
@@ -196,6 +196,12 @@ -- instead use the state passed directly to the handler. type StatefulViewEventHandler state = state -> ([SomeStoreAction], Maybe state) +-- | Change the event handler from 'ViewEventHandler' to 'StatefulViewEventHandler' to allow you to embed+-- combinators with 'ViewEventHandler's into a stateful view. Each such lifted handler makes no change to+-- the state.+liftViewToStateHandler :: ReactElementM ViewEventHandler a -> ReactElementM (StatefulViewEventHandler st) a+liftViewToStateHandler = transHandler (\h _ -> (h, Nothing))+ -- | A stateful view is a re-usable component of the page which keeps track of internal state. -- Try to keep as many views as possible stateless. The React documentation on -- <https://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html interactivity and dynamic UIs>@@ -286,9 +292,6 @@ #ifdef __GHCJS__ -newtype ReactThis state props = ReactThis JSVal-instance IsJSVal (ReactThis state props)- foreign import javascript unsafe "$1['state'].hs" js_ReactGetState :: ReactThis state props -> IO (Export state)@@ -298,14 +301,6 @@ js_ReactGetProps :: ReactThis state props -> IO (Export props) foreign import javascript unsafe- "$1['context']"- js_ReactGetContext :: ReactThis state props -> IO JSVal--foreign import javascript unsafe- "hsreact$children_to_array($1['props']['children'])"- js_ReactGetChildren :: ReactThis state props -> IO JSArray--foreign import javascript unsafe "$1._updateAndReleaseState($2)" js_ReactUpdateAndReleaseState :: ReactThis state props -> Export state -> IO () @@ -366,12 +361,7 @@ state <- parseState this props <- js_ReactGetProps this >>= parseExport node <- render state props-- let getPropsChildren = do childRef <- js_ReactGetChildren this- return $ map ReactElementRef $ toList childRef- getContext = js_ReactGetContext this-- (element, evtCallbacks) <- mkReactElement (runHandler this) getContext getPropsChildren node+ (element, evtCallbacks) <- mkReactElement (runHandler this) this node evtCallbacksRef <- toJSVal evtCallbacks js_RenderCbSetResults arg evtCallbacksRef element@@ -469,6 +459,31 @@ #else foreignClass _ _ x = x+#endif++-- | Inject arbitrary javascript code into the rendering function. This is very low level and should only+-- be used as a last resort when interacting with complex third-party react classes. For the most part,+-- third-party react classes can be interacted with using 'foreignClass' and the various ways of creating+-- properties.+rawJsRendering :: (JSVal -> JSArray -> IO JSVal)+ -- ^ The raw code to inject into the rendering function. The first argument is the 'this' value+ -- from the rendering function so points to the react class. The second argument is the result of+ -- rendering the children so is an array of react elements. The return value must be a React element.+ -> ReactElementM handler () -- ^ the children+ -> ReactElementM handler ()++#ifdef __GHCJS__++rawJsRendering trans (ReactElementM child) =+ let (a, childEl) = runWriter child+ trans' thisVal childLst =+ ReactElementRef <$> trans thisVal (JSA.fromList $ map reactElementRef childLst)+ in elementToM a $ RawJsElement trans' childEl++#else++rawJsRendering _ x = x+ #endif -- | A class which is used to implement <https://wiki.haskell.org/Varargs variable argument functions>.
test/client/TestClient.hs view
@@ -18,6 +18,7 @@ import GHCJS.Types (JSVal, JSString) import GHCJS.Marshal (fromJSVal)+import JavaScript.Array (JSArray) import qualified Data.JSString.Text as JSS foreign import javascript unsafe@@ -46,6 +47,10 @@ outputIO :: [T.Text] -> IO () outputIO ss = void $ transform ss OutputStoreData +foreign import javascript unsafe+ "React['createElement']('p', {'id': 'test-raw-js-para'}, $2)"+ js_testRawJs :: JSVal -> JSArray -> IO JSVal+ -------------------------------------------------------------------------------- --- Events --------------------------------------------------------------------------------@@ -87,7 +92,7 @@ , onClick $ \e m -> output [ "click" , tshow e- , tshow m + , tshow m , logM (mouseGetModifierState m) --, logT (mouseRelatedTarget m) ]@@ -117,7 +122,7 @@ , 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"]@@ -406,7 +411,7 @@ let step = UTCTime moon (2*60*60 + 56*60) -- 1969-7-20 02:56 UTC fullT = (fullDayF, TimeFormat { hourF = Just "numeric", minuteF = Just "2-digit", secondF = Just "numeric", timeZoneNameF = Just "long" })- + li_ ["id" $= "f-shorttime"] $ utcTime_ shortDateTime step li_ ["id" $= "f-fulltime"] $ utcTime_ fullT step li_ ["id" $= "f-time"] $ formattedDate_ (Right step)@@ -499,6 +504,10 @@ view callbackViewWrapper () mempty view intlSpec () mempty++ rawJsRendering js_testRawJs $+ span_ ["id" $= "test-raw-js-body"]+ "Raw Javascript Render Body" } main :: IO ()
test/spec/TestClientSpec.hs view
@@ -164,6 +164,10 @@ (findElem (ById "raw-show-view") >>= getText) `shouldReturn` "42" + it "has rendered the raw javascript rendering" $ runWD $+ (findElem (ByCSS "p#test-raw-js-para > span") >>= getText)+ `shouldReturn` "Raw Javascript Render Body"+ describe "lifecycle events" $ do it "properly updates the state" $ runWD $ do
test/spec/stack.yaml view
@@ -2,4 +2,4 @@ packages: - '.' extra-deps:-- hspec-webdriver-1.2+- hspec-webdriver-1.2.0