packages feed

react-flux 1.0.7 → 1.1.0

raw patch · 37 files changed

+1318/−701 lines, 37 filesdep ~basedep ~textdep ~timenew-component:exe:test-clientPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: base, text, time

API changes (from Hackage documentation)

- React.Flux.Addons.React: instance GHC.Generics.Constructor React.Flux.Addons.React.C1_0PerfAction
- React.Flux.Addons.React: instance GHC.Generics.Constructor React.Flux.Addons.React.C1_0PerfPrint
- React.Flux.Addons.React: instance GHC.Generics.Constructor React.Flux.Addons.React.C1_1PerfAction
- React.Flux.Addons.React: instance GHC.Generics.Constructor React.Flux.Addons.React.C1_1PerfPrint
- React.Flux.Addons.React: instance GHC.Generics.Constructor React.Flux.Addons.React.C1_2PerfPrint
- React.Flux.Addons.React: instance GHC.Generics.Constructor React.Flux.Addons.React.C1_3PerfPrint
- React.Flux.Addons.React: instance GHC.Generics.Datatype React.Flux.Addons.React.D1PerfAction
- React.Flux.Addons.React: instance GHC.Generics.Datatype React.Flux.Addons.React.D1PerfPrint
- React.Flux.Combinators: instance GHC.Generics.Constructor React.Flux.Combinators.C1_0AjaxRequest
- React.Flux.Combinators: instance GHC.Generics.Constructor React.Flux.Combinators.C1_0AjaxResponse
- React.Flux.Combinators: instance GHC.Generics.Datatype React.Flux.Combinators.D1AjaxRequest
- React.Flux.Combinators: instance GHC.Generics.Datatype React.Flux.Combinators.D1AjaxResponse
- React.Flux.Combinators: instance GHC.Generics.Selector React.Flux.Combinators.S1_0_0AjaxRequest
- React.Flux.Combinators: instance GHC.Generics.Selector React.Flux.Combinators.S1_0_0AjaxResponse
- React.Flux.Combinators: instance GHC.Generics.Selector React.Flux.Combinators.S1_0_1AjaxRequest
- React.Flux.Combinators: instance GHC.Generics.Selector React.Flux.Combinators.S1_0_1AjaxResponse
- React.Flux.Combinators: instance GHC.Generics.Selector React.Flux.Combinators.S1_0_2AjaxRequest
- React.Flux.Combinators: instance GHC.Generics.Selector React.Flux.Combinators.S1_0_2AjaxResponse
- React.Flux.Combinators: instance GHC.Generics.Selector React.Flux.Combinators.S1_0_3AjaxRequest
- React.Flux.DOM: instance (child ~ React.Flux.Internal.ReactElementM eventHandler a) => React.Flux.DOM.Term eventHandler [React.Flux.Internal.PropertyOrHandler eventHandler] (child -> React.Flux.Internal.ReactElementM eventHandler a)
- React.Flux.Internal: class ReactViewKey key
- React.Flux.Internal: instance (a ~ ()) => Data.String.IsString (React.Flux.Internal.ReactElementM eventHandler a)
- React.Flux.Internal: instance (a ~ ()) => GHC.Base.Monoid (React.Flux.Internal.ReactElementM eventHandler a)
- React.Flux.Internal: instance React.Flux.Internal.ReactViewKey GHC.Base.String
- React.Flux.Internal: instance React.Flux.Internal.ReactViewKey GHC.Types.Int
- React.Flux.Internal: toKeyRef :: ReactViewKey key => key -> IO JSVal
+ React.Flux: ReturnProps :: props -> ReturnProps props
+ React.Flux: class ArgumentsToProps props a | a -> props
+ React.Flux: elemJSString :: JSString -> ReactElementM eventHandler ()
+ React.Flux: elemString :: String -> ReactElementM eventHandler ()
+ React.Flux: exportViewToJavaScript :: (Typeable props, ArgumentsToProps props func) => ReactView props -> func -> IO JSVal
+ React.Flux: newtype ReturnProps props
+ React.Flux: type family StoreAction storeData;
+ React.Flux: viewWithIKey :: Typeable props => ReactView props -> Int -> props -> ReactElementM eventHandler a -> ReactElementM eventHandler a
+ React.Flux: viewWithSKey :: Typeable props => ReactView props -> JSString -> props -> ReactElementM eventHandler a -> ReactElementM eventHandler a
+ React.Flux: }
+ React.Flux.Addons.Intl: data IntlProperty
+ React.Flux.Addons.Intl: dayProp :: JSString -> Day -> IntlProperty
+ React.Flux.Addons.Intl: formattedHtmlMessage_ :: [PropertyOrHandler eventHandler] -> ReactElementM eventHandler ()
+ React.Flux.Addons.Intl: formattedMessage_ :: [PropertyOrHandler eventHandler] -> ReactElementM eventHandler ()
+ React.Flux.Addons.Intl: instance React.Flux.Addons.Intl.ToJSVal React.Flux.Addons.Intl.JSVal
+ React.Flux.Addons.Intl: iprop :: ToJSVal v => JSString -> v -> IntlProperty
+ React.Flux.Addons.Intl: timeProp :: JSString -> UTCTime -> IntlProperty
+ React.Flux.Combinators: style :: [(JSString, JSString)] -> PropertyOrHandler handler
+ React.Flux.DOM: instance child ~ React.Flux.Internal.ReactElementM eventHandler a => React.Flux.DOM.Term eventHandler [React.Flux.Internal.PropertyOrHandler eventHandler] (child -> React.Flux.Internal.ReactElementM eventHandler a)
+ React.Flux.Internal: (&=) :: ToJSVal a => JSString -> a -> PropertyOrHandler handler
+ React.Flux.Internal: CallbackPropertyReturningView :: JSString -> (JSArray -> IO props) -> ReactViewRef props -> PropertyOrHandler handler
+ React.Flux.Internal: [cretArgToProp] :: PropertyOrHandler handler -> JSArray -> IO props
+ React.Flux.Internal: [cretPropertyName] :: PropertyOrHandler handler -> JSString
+ React.Flux.Internal: [cretView] :: PropertyOrHandler handler -> ReactViewRef props
+ React.Flux.Internal: elemJSString :: JSString -> ReactElementM eventHandler ()
+ React.Flux.Internal: elemString :: String -> ReactElementM eventHandler ()
+ React.Flux.Internal: exportViewToJs :: Typeable props => ReactViewRef props -> (JSArray -> IO props) -> IO (CallbackToRelease, JSVal)
+ React.Flux.Internal: instance React.Flux.Internal.IsJSVal React.Flux.Internal.JSString
+ React.Flux.Internal: instance React.Flux.Internal.IsJSVal React.Flux.Internal.JSVal
+ React.Flux.Internal: instance React.Flux.Internal.ToJSVal React.Flux.Internal.JSString
+ React.Flux.Internal: instance a ~ () => Data.String.IsString (React.Flux.Internal.ReactElementM eventHandler a)
+ React.Flux.Internal: instance a ~ () => GHC.Base.Monoid (React.Flux.Internal.ReactElementM eventHandler a)
+ React.Flux.Internal: type JSString = String
+ React.Flux.PropertiesAndEvents: (&=) :: ToJSVal a => JSString -> a -> PropertyOrHandler handler
+ React.Flux.PropertiesAndEvents: ReturnProps :: props -> ReturnProps props
+ React.Flux.PropertiesAndEvents: callbackView :: JSString -> ReactView () -> PropertyOrHandler handler
+ React.Flux.PropertiesAndEvents: callbackViewWithProps :: (Typeable props, ArgumentsToProps props func) => JSString -> ReactView props -> func -> PropertyOrHandler handler
+ React.Flux.PropertiesAndEvents: class ArgumentsToProps props a | a -> props
+ React.Flux.PropertiesAndEvents: newtype ReturnProps props
- React.Flux: class Typeable storeData => StoreData storeData where type family StoreAction storeData
+ React.Flux: class Typeable storeData => StoreData storeData where type StoreAction storeData where {
- React.Flux: defineControllerView :: (StoreData storeData, Typeable props) => String -> ReactStore storeData -> (storeData -> props -> ReactElementM ViewEventHandler ()) -> ReactView props
+ React.Flux: defineControllerView :: (StoreData storeData, Typeable props) => JSString -> ReactStore storeData -> (storeData -> props -> ReactElementM ViewEventHandler ()) -> ReactView props
- React.Flux: defineStatefulView :: (Typeable state, NFData state, Typeable props) => String -> state -> (state -> props -> ReactElementM (StatefulViewEventHandler state) ()) -> ReactView props
+ React.Flux: defineStatefulView :: (Typeable state, NFData state, Typeable props) => JSString -> state -> (state -> props -> ReactElementM (StatefulViewEventHandler state) ()) -> ReactView props
- React.Flux: defineView :: Typeable props => String -> (props -> ReactElementM ViewEventHandler ()) -> ReactView props
+ React.Flux: defineView :: Typeable props => JSString -> (props -> ReactElementM ViewEventHandler ()) -> ReactView props
- React.Flux: elemText :: String -> ReactElementM eventHandler ()
+ React.Flux: elemText :: Text -> ReactElementM eventHandler ()
- React.Flux.Addons.Intl: DayFormat :: Maybe String -> Maybe String -> Maybe String -> Maybe String -> Maybe String -> DayFormat
+ React.Flux.Addons.Intl: DayFormat :: Maybe JSString -> Maybe JSString -> Maybe JSString -> Maybe JSString -> Maybe JSString -> DayFormat
- React.Flux.Addons.Intl: TimeFormat :: Maybe String -> Maybe String -> Maybe String -> Maybe String -> TimeFormat
+ React.Flux.Addons.Intl: TimeFormat :: Maybe JSString -> Maybe JSString -> Maybe JSString -> Maybe JSString -> TimeFormat
- React.Flux.Addons.Intl: [dayF] :: DayFormat -> Maybe String
+ React.Flux.Addons.Intl: [dayF] :: DayFormat -> Maybe JSString
- React.Flux.Addons.Intl: [eraF] :: DayFormat -> Maybe String
+ React.Flux.Addons.Intl: [eraF] :: DayFormat -> Maybe JSString
- React.Flux.Addons.Intl: [hourF] :: TimeFormat -> Maybe String
+ React.Flux.Addons.Intl: [hourF] :: TimeFormat -> Maybe JSString
- React.Flux.Addons.Intl: [minuteF] :: TimeFormat -> Maybe String
+ React.Flux.Addons.Intl: [minuteF] :: TimeFormat -> Maybe JSString
- React.Flux.Addons.Intl: [monthF] :: DayFormat -> Maybe String
+ React.Flux.Addons.Intl: [monthF] :: DayFormat -> Maybe JSString
- React.Flux.Addons.Intl: [secondF] :: TimeFormat -> Maybe String
+ React.Flux.Addons.Intl: [secondF] :: TimeFormat -> Maybe JSString
- React.Flux.Addons.Intl: [timeZoneNameF] :: TimeFormat -> Maybe String
+ React.Flux.Addons.Intl: [timeZoneNameF] :: TimeFormat -> Maybe JSString
- React.Flux.Addons.Intl: [weekdayF] :: DayFormat -> Maybe String
+ React.Flux.Addons.Intl: [weekdayF] :: DayFormat -> Maybe JSString
- React.Flux.Addons.Intl: [yearF] :: DayFormat -> Maybe String
+ React.Flux.Addons.Intl: [yearF] :: DayFormat -> Maybe JSString
- React.Flux.Addons.Intl: formattedDateProp :: String -> Either Day UTCTime -> [Pair] -> PropertyOrHandler eventHandler
+ React.Flux.Addons.Intl: formattedDateProp :: JSString -> Either Day UTCTime -> [IntlProperty] -> PropertyOrHandler eventHandler
- React.Flux.Addons.Intl: formattedNumberProp :: ToJSVal num => String -> num -> [Pair] -> PropertyOrHandler handler
+ React.Flux.Addons.Intl: formattedNumberProp :: ToJSVal num => JSString -> num -> [IntlProperty] -> PropertyOrHandler handler
- React.Flux.Addons.Intl: formattedRelativeProp :: String -> UTCTime -> [Pair] -> PropertyOrHandler eventHandler
+ React.Flux.Addons.Intl: formattedRelativeProp :: JSString -> UTCTime -> [IntlProperty] -> PropertyOrHandler eventHandler
- React.Flux.Addons.Intl: intlProvider_ :: String -> Maybe JSVal -> Maybe Object -> ReactElementM eventHandler a -> ReactElementM eventHandler a
+ React.Flux.Addons.Intl: intlProvider_ :: JSString -> Maybe JSVal -> Maybe Object -> ReactElementM eventHandler a -> ReactElementM eventHandler a
- React.Flux.Addons.Intl: messageProp :: String -> MessageId -> Text -> ExpQ
+ React.Flux.Addons.Intl: messageProp :: Text -> MessageId -> Text -> ExpQ
- React.Flux.Addons.Intl: messageProp' :: String -> MessageId -> Text -> Text -> ExpQ
+ React.Flux.Addons.Intl: messageProp' :: Text -> MessageId -> Text -> Text -> ExpQ
- React.Flux.Addons.Intl: pluralProp :: ToJSVal val => String -> val -> [Pair] -> PropertyOrHandler eventHandler
+ React.Flux.Addons.Intl: pluralProp :: ToJSVal val => JSString -> val -> [IntlProperty] -> PropertyOrHandler eventHandler
- React.Flux.Combinators: clbutton_ :: String -> handler -> ReactElementM handler a -> ReactElementM handler a
+ React.Flux.Combinators: clbutton_ :: JSString -> handler -> ReactElementM handler a -> ReactElementM handler a
- React.Flux.Combinators: cldiv_ :: String -> ReactElementM handler a -> ReactElementM handler a
+ React.Flux.Combinators: cldiv_ :: JSString -> ReactElementM handler a -> ReactElementM handler a
- React.Flux.Combinators: faIcon_ :: String -> ReactElementM handler ()
+ React.Flux.Combinators: faIcon_ :: JSString -> ReactElementM handler ()
- React.Flux.Combinators: foreign_ :: String -> [PropertyOrHandler handler] -> ReactElementM handler a -> ReactElementM handler a
+ React.Flux.Combinators: foreign_ :: JSString -> [PropertyOrHandler handler] -> ReactElementM handler a -> ReactElementM handler a
- React.Flux.Combinators: labeledInput_ :: String -> ReactElementM handler () -> [PropertyOrHandler handler] -> ReactElementM handler ()
+ React.Flux.Combinators: labeledInput_ :: JSString -> ReactElementM handler () -> [PropertyOrHandler handler] -> ReactElementM handler ()
- React.Flux.DOM: term :: Term eventHandler arg result => String -> arg -> result
+ React.Flux.DOM: term :: Term eventHandler arg result => JSString -> arg -> result
- React.Flux.Internal: CallbackPropertyWithArgumentArray :: String -> (JSArray -> IO handler) -> PropertyOrHandler handler
+ React.Flux.Internal: CallbackPropertyWithArgumentArray :: JSString -> (JSArray -> IO handler) -> PropertyOrHandler handler
- React.Flux.Internal: CallbackPropertyWithSingleArgument :: String -> (HandlerArg -> handler) -> PropertyOrHandler handler
+ React.Flux.Internal: CallbackPropertyWithSingleArgument :: JSString -> (HandlerArg -> handler) -> PropertyOrHandler handler
- React.Flux.Internal: Content :: String -> ReactElement eventHandler
+ React.Flux.Internal: Content :: JSString -> ReactElement eventHandler
- React.Flux.Internal: ElementProperty :: String -> ReactElementM handler () -> PropertyOrHandler handler
+ React.Flux.Internal: ElementProperty :: JSString -> ReactElementM handler () -> PropertyOrHandler handler
- React.Flux.Internal: ForeignElement :: Either String (ReactViewRef Object) -> [PropertyOrHandler eventHandler] -> ReactElement eventHandler -> ReactElement eventHandler
+ React.Flux.Internal: ForeignElement :: Either JSString (ReactViewRef Object) -> [PropertyOrHandler eventHandler] -> ReactElement eventHandler -> ReactElement eventHandler
- React.Flux.Internal: NestedProperty :: String -> [PropertyOrHandler handler] -> PropertyOrHandler handler
+ React.Flux.Internal: NestedProperty :: JSString -> [PropertyOrHandler handler] -> PropertyOrHandler handler
- React.Flux.Internal: Property :: String -> ref -> PropertyOrHandler handler
+ React.Flux.Internal: Property :: JSString -> ref -> PropertyOrHandler handler
- React.Flux.Internal: PropertyFromContext :: String -> (JSVal -> ref) -> PropertyOrHandler handler
+ React.Flux.Internal: PropertyFromContext :: JSString -> (JSVal -> ref) -> PropertyOrHandler handler
- React.Flux.Internal: ViewElement :: ReactViewRef props -> Maybe key -> props -> ReactElement eventHandler -> ReactElement eventHandler
+ React.Flux.Internal: ViewElement :: ReactViewRef props -> Maybe JSVal -> props -> ReactElement eventHandler -> ReactElement eventHandler
- React.Flux.Internal: [caPropertyName] :: PropertyOrHandler handler -> String
+ React.Flux.Internal: [caPropertyName] :: PropertyOrHandler handler -> JSString
- React.Flux.Internal: [ceKey] :: ReactElement eventHandler -> Maybe key
+ React.Flux.Internal: [ceKey] :: ReactElement eventHandler -> Maybe JSVal
- React.Flux.Internal: [csPropertyName] :: PropertyOrHandler handler -> String
+ React.Flux.Internal: [csPropertyName] :: PropertyOrHandler handler -> JSString
- React.Flux.Internal: [elementPropertyName] :: PropertyOrHandler handler -> String
+ React.Flux.Internal: [elementPropertyName] :: PropertyOrHandler handler -> JSString
- React.Flux.Internal: [fName] :: ReactElement eventHandler -> Either String (ReactViewRef Object)
+ React.Flux.Internal: [fName] :: ReactElement eventHandler -> Either JSString (ReactViewRef Object)
- React.Flux.Internal: [nestedPropertyName] :: PropertyOrHandler handler -> String
+ React.Flux.Internal: [nestedPropertyName] :: PropertyOrHandler handler -> JSString
- React.Flux.Internal: [propFromThisName] :: PropertyOrHandler handler -> String
+ React.Flux.Internal: [propFromThisName] :: PropertyOrHandler handler -> JSString
- React.Flux.Internal: [propertyName] :: PropertyOrHandler handler -> String
+ React.Flux.Internal: [propertyName] :: PropertyOrHandler handler -> JSString
- React.Flux.Internal: el :: String -> [PropertyOrHandler eventHandler] -> ReactElementM eventHandler a -> ReactElementM eventHandler a
+ React.Flux.Internal: el :: JSString -> [PropertyOrHandler eventHandler] -> ReactElementM eventHandler a -> ReactElementM eventHandler a
- React.Flux.Internal: elemText :: String -> ReactElementM eventHandler ()
+ React.Flux.Internal: elemText :: Text -> ReactElementM eventHandler ()
- React.Flux.Internal: mkReactElement :: (eventHandler -> IO ()) -> IO JSVal -> IO [ReactElementRef] -> ReactElementM eventHandler () -> IO (ReactElementRef, [Callback (JSVal -> IO ())])
+ React.Flux.Internal: mkReactElement :: forall eventHandler. (eventHandler -> IO ()) -> IO JSVal -> IO [ReactElementRef] -> ReactElementM eventHandler () -> IO (ReactElementRef, [CallbackToRelease])
- React.Flux.Internal: property :: ToJSVal val => String -> val -> PropertyOrHandler handler
+ React.Flux.Internal: property :: ToJSVal val => JSString -> val -> PropertyOrHandler handler
- React.Flux.Lifecycle: LDOM :: IO HTMLElement -> (String -> IO HTMLElement) -> LDOM
+ React.Flux.Lifecycle: LDOM :: IO HTMLElement -> (JSString -> IO HTMLElement) -> LDOM
- React.Flux.Lifecycle: [lRef] :: LDOM -> String -> IO HTMLElement
+ React.Flux.Lifecycle: [lRef] :: LDOM -> JSString -> IO HTMLElement
- React.Flux.PropertiesAndEvents: ($=) :: Text -> Text -> PropertyOrHandler handler
+ React.Flux.PropertiesAndEvents: ($=) :: JSString -> JSString -> PropertyOrHandler handler
- React.Flux.PropertiesAndEvents: (@=) :: ToJSON a => Text -> a -> PropertyOrHandler handler
+ React.Flux.PropertiesAndEvents: (@=) :: ToJSON a => JSString -> a -> PropertyOrHandler handler
- React.Flux.PropertiesAndEvents: Event :: String -> Bool -> Bool -> EventTarget -> Bool -> Int -> Bool -> EventTarget -> Int -> HandlerArg -> Event
+ React.Flux.PropertiesAndEvents: Event :: Text -> Bool -> Bool -> EventTarget -> Bool -> Int -> Bool -> EventTarget -> Int -> HandlerArg -> Event
- React.Flux.PropertiesAndEvents: KeyboardEvent :: Bool -> Int -> Bool -> (String -> Bool) -> String -> Int -> String -> Int -> Bool -> Bool -> Bool -> Int -> KeyboardEvent
+ React.Flux.PropertiesAndEvents: KeyboardEvent :: Bool -> Int -> Bool -> (Text -> Bool) -> Text -> Int -> Maybe Text -> Int -> Bool -> Bool -> Bool -> Int -> KeyboardEvent
- React.Flux.PropertiesAndEvents: MouseEvent :: Bool -> Int -> Int -> Int -> Int -> Bool -> (String -> Bool) -> Bool -> Int -> Int -> EventTarget -> Int -> Int -> Bool -> MouseEvent
+ React.Flux.PropertiesAndEvents: MouseEvent :: Bool -> Int -> Int -> Int -> Int -> Bool -> (Text -> Bool) -> Bool -> Int -> Int -> EventTarget -> Int -> Int -> Bool -> MouseEvent
- React.Flux.PropertiesAndEvents: TouchEvent :: Bool -> [Touch] -> Bool -> (String -> Bool) -> Bool -> Bool -> [Touch] -> [Touch] -> TouchEvent
+ React.Flux.PropertiesAndEvents: TouchEvent :: Bool -> [Touch] -> Bool -> (Text -> Bool) -> Bool -> Bool -> [Touch] -> [Touch] -> TouchEvent
- React.Flux.PropertiesAndEvents: [evtType] :: Event -> String
+ React.Flux.PropertiesAndEvents: [evtType] :: Event -> Text
- React.Flux.PropertiesAndEvents: [keyGetModifierState] :: KeyboardEvent -> String -> Bool
+ React.Flux.PropertiesAndEvents: [keyGetModifierState] :: KeyboardEvent -> Text -> Bool
- React.Flux.PropertiesAndEvents: [keyKey] :: KeyboardEvent -> String
+ React.Flux.PropertiesAndEvents: [keyKey] :: KeyboardEvent -> Text
- React.Flux.PropertiesAndEvents: [keyLocale] :: KeyboardEvent -> String
+ React.Flux.PropertiesAndEvents: [keyLocale] :: KeyboardEvent -> Maybe Text
- React.Flux.PropertiesAndEvents: [mouseGetModifierState] :: MouseEvent -> String -> Bool
+ React.Flux.PropertiesAndEvents: [mouseGetModifierState] :: MouseEvent -> Text -> Bool
- React.Flux.PropertiesAndEvents: [touchGetModifierState] :: TouchEvent -> String -> Bool
+ React.Flux.PropertiesAndEvents: [touchGetModifierState] :: TouchEvent -> Text -> Bool
- React.Flux.PropertiesAndEvents: callback :: CallbackFunction handler func => String -> func -> PropertyOrHandler handler
+ React.Flux.PropertiesAndEvents: callback :: CallbackFunction handler func => JSString -> func -> PropertyOrHandler handler
- React.Flux.PropertiesAndEvents: elementProperty :: String -> ReactElementM handler () -> PropertyOrHandler handler
+ React.Flux.PropertiesAndEvents: elementProperty :: JSString -> ReactElementM handler () -> PropertyOrHandler handler
- React.Flux.PropertiesAndEvents: eventTargetProp :: FromJSVal val => EventTarget -> String -> val
+ React.Flux.PropertiesAndEvents: eventTargetProp :: FromJSVal val => EventTarget -> JSString -> val
- React.Flux.PropertiesAndEvents: nestedProperty :: String -> [PropertyOrHandler handler] -> PropertyOrHandler handler
+ React.Flux.PropertiesAndEvents: nestedProperty :: JSString -> [PropertyOrHandler handler] -> PropertyOrHandler handler
- React.Flux.PropertiesAndEvents: on :: String -> (Event -> handler) -> PropertyOrHandler handler
+ React.Flux.PropertiesAndEvents: on :: JSString -> (Event -> handler) -> PropertyOrHandler handler
- React.Flux.PropertiesAndEvents: property :: ToJSVal val => String -> val -> PropertyOrHandler handler
+ React.Flux.PropertiesAndEvents: property :: ToJSVal val => JSString -> val -> PropertyOrHandler handler
- React.Flux.PropertiesAndEvents: target :: FromJSVal val => Event -> String -> val
+ React.Flux.PropertiesAndEvents: target :: FromJSVal val => Event -> JSString -> val

Files

ChangeLog.md view
@@ -1,3 +1,68 @@+# 1.1.0++* Breaking Change - I removed the use of `String` and replaced it with either `Text` or `JSString`+  (from the `Data.JSString` module in `ghcjs-base`).  If a value was just going to be passed straight into+  react, I made it a JSString and if the value was intended to be manipulated in Haskell I used Text.+  The main changes are:+      +      * `elemText` used to (confusingly) take a `String`.  Now there are three functions: `elemString`,+        `elemText`, and `elemJSString` that take a `String`, `Text`, and `JSString` value respectively.++      * The type of `$=` now uses JSString.  `$=` is intended only to avoid ambiguity when using+        overloaded strings and string constants and `JSString` has an `IsString` instance.++      * There is a new property creator `&=` which instead of converting to Aeson and then to a javascript+        value like `@=` does, `&=` uses the `ToJSVal` class from `GHCJS.Foreign.Marshal` to convert values+        directly to javascript.++      * Several functions had their type changed to instead of taking a String as a param take a Text or+        JSString as a parameter++   Despite being quite a large change, it didn't take much effort for me to convert my application to this+   new API.  You can see the conversion of the todo example application here:+   https://bitbucket.org/wuzzeb/react-flux/commits/a630da5f9032745ab92da6e3d9f9038915b9b319+   The main things that required changing are:++      * Converting between `String` and `JSString` is done via `pack` and `unpack` in `Data.JSString`.+        Converting between `Text` and `JSString` is done via `textToJSString` and `textFromJSString`+        in the `Data.JSString.Text` module.  In fact, this conversion needed to happen in only a+        few places for me because for the most part the `IsString` instance and `OverloadedStrings`+        extension properly converted the string literals to the proper type.  A few places I needed to+        use `<>` from `Data.Monoid` to concatenate `JSString`s.++      * A few places I was accidentally using `$=` for non-literal strings.  Switching these to `&=`+        was enough, since `&=` is polymorphic over the type of the property value.++      * Most places using `elemText` had an error, but here it was straightforward to switch to+        either `elemString` or keep `elemText` and use `Text` values inside my store.++* React-flux has used the shouldComponentUpdate lifetime method to prevent re-rendering in some cases,+  but it was finicky and sometimes wouldn't work (not a correctness bug, just a missed performance+  improvement).  I now better understand when it works and does not work, and edited the documentation+  to explain this and what you can do in your own code (primarily add bang patterns to force thunks).+  The new documentation is at the end of the main module page.++* The shouldComponentUpdate lifetime method now knows about view props which are tuples of size two or+  three, and will be able to skip re-rendering as long as each component of the tuple is unchanged+  (and is not a thunk).++* `React.Flux.Addons.Intl` now uses a new type `IntlProperty` defined in the module instead of using+  the pair type from aeson.  This allows passing arbitrary JSVals to i18n functions instead of just+  aeson values.  This causes API breakage in `formattedNumberProp`, `formattedDateProp`, `pluralProp`,+  `messageProp`, and `messageProp'`.++* Ability to export react views to JavaScript.  `callbackViewWithProps` inside `React.Flux.PropertiesAndEvents`+  allows you to create a javascript function which returns a renderable and then pass that JavaScript+  function to a foreign JavaScript class.  This is useful for example for fixed-data-table and ReactNative's+  ListView among others.  Secondly, `exportViewToJavaScript` from `React.Flux` allows just exporting+  a view to a JavaScript function, which is useful to embed a Haskell application into a larger JavaScript+  React application.++* Add a `style` combinator to easily write inline styles on elements.++* Add `viewWithIKey` and `viewWithSKey` for views with integer and string keys.  The old `viewWithKey`+  was akward to use when OverloadedStrings was enabled.+ # 1.0.7  * Fix the build when building with GHC instead of GHCJS (an import was incorrectly protected by CPP)
Makefile view
@@ -5,7 +5,7 @@  js-build/install-root: $(INSTALL_ROOT) 	mkdir -p js-build-	ln -s $(INSTALL_ROOT) js-build/install-root+	ln -sf $(INSTALL_ROOT) js-build/install-root  js-build/todo.min.js: js-build/todo.js 	closure --compilation_level=ADVANCED_OPTIMIZATIONS js-build/todo.js > js-build/todo.min.js
README.md view
@@ -12,29 +12,27 @@  I use stack to build my frontend which uses react-flux.  I set up stack and ghcjs using [these-instructions](http://docs.haskellstack.org/en/stable/ghcjs.html).  Note that-react-flux requires GHCJS master (a.k.a. improved base).  At the moment I want-to use GHC 7.10.3 and no ghcjs snapshot uses lts-4.1 and GHC 7.10.3, so I am-building ghcjs manually.  So what I do is [install ghcjs from-git](https://github.com/ghcjs/ghcjs) using the following.  (Once the ghcjs-snapshots have caught up I will transition to using them and have stack install-ghcjs instead of installing ghcjs manually.)--~~~-$ git clone https://github.com/ghcjs/ghcjs.git-$ cabal install ./ghcjs-$ ghcjs-boot --dev-~~~--After this, in my application which depends on react-flux, I use the following `stack.yaml`:+instructions](http://docs.haskellstack.org/en/stable/ghcjs/).  Note that+react-flux requires GHCJS master (a.k.a. improved base).  I keep updated with+the latest version of GHCJS and lts.  As I write this, I use the following `stack.yaml`,+but as new LTS versions or new GHCJS versions come out I keep updating to them (and+probably forget to update this README :)  ~~~-resolver: lts-4.1-compiler: ghcjs-0.2.0_ghc-7.10.3+resolver: lts-6.1 packages:     - . extra-deps:-    - react-flux-1.0.3+    - react-flux-(version)++compiler: ghcjs-0.2.0.20160414_ghc-7.10.3+compiler-check: match-exact+setup-info:+  ghcjs:+    source:+      ghcjs-0.2.0.20160414_ghc-7.10.3:+        url: https://s3.amazonaws.com/ghcjs/ghcjs-0.2.0.20160414_ghc-7.10.3.tar.gz+        sha1: 6d6f307503be9e94e0c96ef1308c7cf224d06be3 ~~~  @@ -62,23 +60,18 @@ haskell application using [hspec-webdriver](https://hackage.haskell.org/package/hspec-webdriver).  This must be built using GHC (not GHCJS), so there is a separate `stack.yaml` file-in the `test/spec` directory.  In summary, run the following commands:+in the `test/spec` directory.  Also, `react-intl` must be installed in the test client+directory.  In summary, run the following commands:  ~~~ {.bash}-stack build-make+cd test/client+npm install react-intl cd test/spec stack build ~~~  Next, install [selenium-server-standalone](http://www.seleniumhq.org/download/) (also from-[npm](https://www.npmjs.com/package/selenium-server-standalone-jar)). Also, at the moment, the-release candiate of the react-intl library must be installed from npm.--~~~-cd test/client-npm install react-intl@v2.0.0-rc-1-~~~+[npm](https://www.npmjs.com/package/selenium-server-standalone-jar)).  Finally, start selenium-server-standalone and execute the test suite.  Make sure you also have closure installed, since the test suite will compress the todo app before testing it.  It must be
example/purecss-side-menu/PageViews.hs view
@@ -17,7 +17,7 @@             clbutton_ "pure-button" (changePageTo Page2) "Change to page 2"             "Also, try reducing the browser width to see the responsive menu."         p_ $ do-            "You must load this file from a server.  If you did not, no page changes will work.  Use for example "+            "You must load this file from a server.  If you did not, page changes might not work depending on your browser security settings (some features don't always work for file URLs).  Use for example "             code_ "python3 -m http.server 8000"             " or "             code_ "python2 -m SimpleHTTPServer 8000"
example/purecss-side-menu/index.html view
@@ -9,8 +9,8 @@     </head>     <body>         <div id="side-menu-app"/>-        <script src="https://fb.me/react-0.14.6.min.js"></script>-        <script src="https://fb.me/react-dom-0.14.6.min.js"></script>+        <script src="https://fb.me/react-15.1.0.min.js"></script>+        <script src="https://fb.me/react-dom-15.1.0.min.js"></script>         <script src="purecss-side-menu.js"></script>     </body> </html>
example/routing/EventTest.hs view
@@ -20,6 +20,10 @@ import           Data.Typeable                (Typeable) import           GHC.Generics                 (Generic) +#ifdef __GHCJS__+import Data.JSString (JSString)+#endif+ data ETState = ETState     { etActions :: [ETAction]     , etText    :: !T.Text@@ -69,13 +73,13 @@ view :: ReactView ETState view = defineView "event-tests" $ \(ETState actions text bool) ->   section_ $ do-    let textVal = "value" $= text+    let textVal = "value" &= text      div_ ["className" $= "et-actions"] $ do       a_ [onClick $ \_ _ -> dispatchEventTest ClearActionsA] $         elemText "Clear event history"       pre_ $-        elemText $ unlines $ map show actions+        elemString $ unlines $ map show actions      h2_ $ elemText "Testing keyboard events"     p_ $ elemText "Type <enter> to test onKeyUp/onKeyDown"@@ -187,13 +191,13 @@  #ifdef __GHCJS__ -targetInt :: Event -> String -> Int+targetInt :: Event -> JSString -> Int targetInt = target -targetText :: Event -> String -> T.Text+targetText :: Event -> JSString -> T.Text targetText = target -targetBool :: Event -> String -> Bool+targetBool :: Event -> JSString -> Bool targetBool = target  #else
example/routing/Main.hs view
@@ -1,5 +1,6 @@ -- | +{-# LANGUAGE OverloadedStrings #-} module Main where  import           React.Flux@@ -9,6 +10,7 @@ import           Router import qualified TabbedApps as TabbedApps import           Types+import qualified Data.Text as T  main :: IO () main = do@@ -33,15 +35,15 @@                  TabbedApps.Tab (appName a) (\pr -> view v pr mempty) (appRouter a))       apps appViews -clockApp :: String -> App TabbedApps.ParentRouter+clockApp :: T.Text -> App TabbedApps.ParentRouter clockApp name = App name Clock.store (\st _ -> Clock.view_ st) Clock.ClockInit Nothing -etApp :: String -> App TabbedApps.ParentRouter+etApp :: T.Text -> App TabbedApps.ParentRouter etApp name = App name rst (\st _ -> EventTest.view_ st) EventTest.ETInit Nothing   where     rst = EventTest.store -tabApp :: String -> [TabbedApps.Tab] -> App TabbedApps.ParentRouter+tabApp :: T.Text -> [TabbedApps.Tab] -> App TabbedApps.ParentRouter tabApp name tabs =   let rst = TabbedApps.newStore tabs   in
example/routing/TabbedApps.hs view
@@ -112,7 +112,7 @@   span_ ["style" @= A.object ["color" A..= ("#eee"::String)] | cur == aidx] $   if cur == aidx   then elemText aname-  else a_ ["href" $= actionRoute prouter (SwitchApp aidx Nothing)] $ elemText aname+  else a_ ["href" &= actionRoute prouter (SwitchApp aidx Nothing)] $ elemText aname  tabItem_ :: ((ParentRouter, Int), (Int, AppName)) -> ReactElementM eventHandler () tabItem_ tab =
example/routing/Types.hs view
@@ -14,8 +14,9 @@  import qualified Data.Text     as T import           Data.Typeable (Typeable)+import qualified Data.JSString.Text as JSS -type AppName = String+type AppName = T.Text type AppView = ReactElementM ViewEventHandler type AppRouter = [T.Text] -> IO () @@ -30,6 +31,6 @@  initApp :: Typeable props => App props -> IO (ReactView props) initApp App{..} = do-  let view' = defineControllerView appName appState (\st props -> appView st props)+  let view' = defineControllerView (JSS.textToJSString appName) appState (\st props -> appView st props)   alterStore appState appInitAction   return view'
example/todo/TodoComponents.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings, BangPatterns #-}  -- | The division between a view and a component is arbitrary, but for me components are pieces that -- are re-used many times for different purposes.  In the TODO app, there is one component for the@@ -7,15 +7,17 @@  import Data.Typeable (Typeable) import React.Flux+import Data.JSString (JSString)+import qualified Data.Text as T  -- | The properties for the text input component.  Note how we can pass anything, including -- functions, as the properties; the only requirement is an instance of Typeable. data TextInputArgs = TextInputArgs {-      tiaId :: Maybe String-    , tiaClass :: String-    , tiaPlaceholder :: String-    , tiaOnSave :: String -> [SomeStoreAction]-    , tiaValue :: Maybe String+      tiaId :: Maybe JSString+    , tiaClass :: JSString+    , tiaPlaceholder :: JSString+    , tiaOnSave :: T.Text -> [SomeStoreAction]+    , tiaValue :: Maybe T.Text } deriving (Typeable)  -- | The text input stateful view.  The state is the text that has been typed into the textbox@@ -24,27 +26,27 @@ todoTextInput :: ReactView TextInputArgs todoTextInput = defineStatefulView "todo text input" "" $ \curText args ->     input_ $-        maybe [] (\i -> ["id" @= i]) (tiaId args)+        maybe [] (\i -> ["id" &= i]) (tiaId args)         ++-        [ "className" @= tiaClass args-        , "placeholder" @= tiaPlaceholder args-        , "value" @= curText -- using value here creates a controlled component: https://facebook.github.io/react/docs/forms.html-        , "autoFocus" @= True+        [ "className" &= tiaClass args+        , "placeholder" &= tiaPlaceholder args+        , "value" &= curText -- using value here creates a controlled component: https://facebook.github.io/react/docs/forms.html+        , "autoFocus" &= True          -- Update the current state with the current text in the textbox, sending no actions         , onChange $ \evt _ -> ([], Just $ target evt "value")          -- Produce the save action and reset the current state to the empty string         , onBlur $ \_ _ curState ->-            if not (null curState)+            if not (T.null curState)                 then (tiaOnSave args curState, Just "")                 else ([], Nothing)         , onKeyDown $ \_ evt curState ->-             if keyCode evt == 13 && not (null curState) -- 13 is enter+             if keyCode evt == 13 && not (T.null curState) -- 13 is enter                  then (tiaOnSave args curState, Just "")                  else ([], Nothing)         ]  -- | A combinator suitible for use inside rendering functions. todoTextInput_ :: TextInputArgs -> ReactElementM eventHandler ()-todoTextInput_ args = view todoTextInput args mempty+todoTextInput_ !args = view todoTextInput args mempty
example/todo/TodoStore.hs view
@@ -1,13 +1,14 @@-{-# LANGUAGE TypeFamilies, DeriveGeneric, DeriveAnyClass #-}+{-# LANGUAGE TypeFamilies, DeriveGeneric, DeriveAnyClass, OverloadedStrings #-} module TodoStore where  import React.Flux import Control.DeepSeq import GHC.Generics (Generic) import Data.Typeable (Typeable)+import qualified Data.Text as T  data Todo = Todo {-    todoText :: String+    todoText :: T.Text   , todoComplete :: Bool   , todoIsEditing :: Bool } deriving (Show, Typeable)@@ -16,10 +17,10 @@     todoList :: [(Int, Todo)] } deriving (Show, Typeable) -data TodoAction = TodoCreate String+data TodoAction = TodoCreate T.Text                 | TodoDelete Int                 | TodoEdit Int-                | UpdateText Int String+                | UpdateText Int T.Text                 | ToggleAllComplete                 | TodoSetComplete Int Bool                 | ClearCompletedTodos
example/todo/TodoViews.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings, BangPatterns #-} -- | The views for the TODO app module TodoViews where @@ -82,7 +82,7 @@  -- | A combinator for a todo item to use inside rendering functions todoItem_ :: (Int, Todo) -> ReactElementM eventHandler ()-todoItem_ todo = viewWithKey todoItem (fst todo) todo mempty+todoItem_ !todo = viewWithIKey todoItem (fst todo) todo mempty  -- | A view for the footer, taking the entire state as the properties.  This could alternatively -- been modeled as a controller-view, attaching directly to the store.@@ -100,8 +100,8 @@                 button_ [ "id" $= "clear-completed"                         , onClick $ \_ _ -> dispatchTodo ClearCompletedTodos                         ] $-                    elemText $ "Clear completed (" ++ show completed ++ ")"+                    elemString $ "Clear completed (" ++ show completed ++ ")"  -- | A render combinator for the footer todoFooter_ :: TodoState -> ReactElementM eventHandler ()-todoFooter_ s = view todoFooter s mempty+todoFooter_ !s = view todoFooter s mempty
example/todo/run-in-node.js view
@@ -1,2 +1,3 @@ React = require("react");+ReactDOMServer = require("react-dom/server"); require("../../js-build/install-root/bin/todo-node.jsexe/all.js");
example/todo/todo-dev.html view
@@ -7,8 +7,8 @@     </head>     <body>         <section id="todoapp"/>-        <script src="https://fb.me/react-0.14.6.js"></script>-        <script src=" https://fb.me/react-dom-0.14.6.js"></script>+        <script src="https://fb.me/react-0.15.1.js"></script>+        <script src=" https://fb.me/react-dom-0.15.1.js"></script>         <script src="../../js-build/install-root/bin/todo.jsexe/all.js"></script>     </body> </html>
example/todo/todo.html view
@@ -7,8 +7,8 @@     </head>     <body>         <section id="todoapp"/>-        <script src="https://fb.me/react-15.0.0-rc.1.js"></script>-        <script src="https://fb.me/react-dom-15.0.0-rc.1.js"></script>+        <script src="https://fb.me/react-15.0.1.js"></script>+        <script src="https://fb.me/react-dom-15.0.1.js"></script>         <script src="../../js-build/todo.min.js"></script>     </body> </html>
jsbits/callback.js view
@@ -7,3 +7,15 @@         f(args);     }; }++function hsreact$wrap_callback_returning_element(f) {+    return function() {+        var args = new Array(arguments.length);+        for (var i = 0; i < arguments.length; i++) {+            args[i] = arguments[i];+        }+        var ret = {};+        f(ret, args);+        return ret.elem;+    };+}
+ jsbits/native.js view
@@ -0,0 +1,3 @@+//Tests for ReactNative+var hsreact$divLikeElement = (typeof window === "object" && window['navigator']['product'] === "ReactNative") ? window["View"] : "div";+var hsreact$textWrapper = (typeof window === "object" && window['navigator']['product'] === "ReactNative") ? window["Text"] : "span";
jsbits/views.js view
@@ -32,13 +32,55 @@         },         _currentCallbacks: []     };++    //Checks if the javascript representations of two haskell values are the same.+    //This can't check equality but just checks if the javascript object has not been+    //changed.  We have special support for tuples of size 2 and 3, where we do check if the individual components of+    //the tuple are equal.+    var areValuesSame = function(obj1, obj2) {+        if (obj1 == obj2) { //use two equal signs to test if the objects are the same+            return true;++        } else {++            //check tuples of size two and 3.+            //+            //Tuples of size 2 are stored as+            //  { d1: first value+            //  , d2: second value+            //  , f: constructor function+            //  }+            //+            //Tuples of size 3 are stored as+            //  { d1: first value+            //  , d2: { d1: second value+            //        , d2: third value+            //        }+            //  , f: constructor function+            //  }+            var obj1_f = (obj1['f'] || {})['name'];+            var obj2_f = (obj2['f'] || {})['name'];++            if (obj1_f === "h$ghczmprimZCGHCziTupleziZLz2cUZR_con_e" && obj1_f === obj2_f) {+                //pair+                return obj1['d1'] == obj2['d1'] && obj1['d2'] == obj2['d2']; //use two equal signs to test if the objects are the same++            } else if (obj1_f === "h$ghczmprimZCGHCziTupleziZLz2cUz2cUZR_con_e" && obj1_f === obj2_f) {+                var obj1_d2 = obj1['d2'] || {};+                var obj2_d2 = obj2['d2'] || {};+                return obj1['d1'] == obj2['d1'] && obj1_d2['d1'] == obj2_d2['d1'] && obj1_d2['d2'] == obj2_d2['d2'];+            } else {+                return false;+            }+        }+    };     if (checkState) {         cl['shouldComponentUpdate'] = function(newProps, newState) {-            return this['props'].hs.root != newProps.hs.root || this['state'].hs.root != newState.hs.root;+            return !areValuesSame(this['props'].hs.root, newProps.hs.root) || !areValuesSame(this['state'].hs.root, newState.hs.root);         };     } else {         cl['shouldComponentUpdate'] = function(newProps, newState) {-            return this['props'].hs.root != newProps.hs.root;+            return !areValuesSame(this['props'].hs.root, newProps.hs.root);         };     } 
react-flux.cabal view
@@ -1,5 +1,5 @@ name:                react-flux-version:             1.0.7+version:             1.1.0 synopsis:            A binding to React based on the Flux application architecture for GHCJS category:            Web homepage:            https://bitbucket.org/wuzzeb/react-flux@@ -53,6 +53,7 @@               jsbits/export.js               jsbits/callback.js               jsbits/ajax.js+              jsbits/native.js    exposed-modules: React.Flux                    React.Flux.DOM@@ -106,6 +107,9 @@    build-depends: base                 , react-flux                 , deepseq+                , text+   if impl(ghcjs)+      build-depends: ghcjs-base  executable todo-node    if !flag(example)@@ -119,6 +123,8 @@                 , react-flux                 , deepseq                 , text+   if impl(ghcjs)+      build-depends: ghcjs-base  executable purecss-side-menu    if !flag(example)@@ -135,24 +141,7 @@    if impl(ghcjs)       build-depends: ghcjs-base -executable test-client-13-   if !flag(test-client)-        Buildable: False-   ghc-options: -Wall-   cpp-options: -DGHCJS_BROWSER--   default-language: Haskell2010-   hs-source-dirs: test/client-   other-modules: TestClient-   main-is: TestClient13.hs-   build-depends: base-                , react-flux-                , time-                , deepseq-   if impl(ghcjs)-      build-depends: ghcjs-base--executable test-client-14+executable test-client    if !flag(test-client)         Buildable: False    ghc-options: -Wall@@ -160,11 +149,11 @@     default-language: Haskell2010    hs-source-dirs: test/client-   other-modules: TestClient-   main-is: TestClient14.hs+   main-is: TestClient.hs    build-depends: base                 , react-flux                 , time+                , text                 , deepseq                 , aeson    if impl(ghcjs)
src/React/Flux.hs view
@@ -30,14 +30,20 @@ -- >})(window, window['React'], window['ReactDOM']); -- -- __Node Deployment__: 'reactRenderToString' is used to render the application to a string when--- running in node (not the browser).  To execute with node, you need to get @global.React@ (and--- @global.ReactDOM@ for 0.14) before executing all.js.  The TODO example application does this by+-- running in node (not the browser).  To execute with node, you need to get @global.React@ and+-- @global.ReactDOMServer@  before executing all.js.  The TODO example application does this by -- creating a file @run-in-node.js@ with the contents -- -- >React = require("react");--- >ReactDOM = require("react-dom");+-- >ReactDOMServer = require("react-dom/server"); -- >require("../../js-build/install-root/bin/todo-node.jsexe/all.js"); --+-- __React Native__: This module also works with <https://facebook.github.io/react-native/ React-Native>+-- to create a standalone native applications.  When combined with <http://electron.atom.io/ electron>,+-- you can even create standalone desktop applications.  The workflow is to use 'reactRender' the+-- same as web deployment but use the resulting JavaScript file in react-native and/or electron.+-- <https://github.com/jyrimatti/hseverywhere Jyrimatti has an example using react-native>.+-- -- __Testing__:  I use the following approach to test my react-flux application.  First, I use unit -- testing to test the dispatcher and store 'transform' functions.  Since the dispatcher and the -- store transform are just data manipulation, existing Haskell tools like hspec, QuickCheck,@@ -52,6 +58,7 @@ -- The file <https://bitbucket.org/wuzzeb/react-flux/src/tip/test/spec/TodoSpec.hs test\/spec\/TodoSpec.hs> -- in the source code contains a hspec-webdriver test for the TODO example application. +{-# OPTIONS_GHC -fno-warn-duplicate-exports #-} -- ArgumentsToProps is exported twice, once by React.Flux.PropertiesAndEvents and once here module React.Flux (   -- * Dispatcher   -- $dispatcher@@ -76,11 +83,13 @@   -- * Elements   , ReactElement   , ReactElementM(..)+  , elemString   , elemText+  , elemJSString   , elemShow   , view-  , viewWithKey-  , ReactViewKey+  , viewWithSKey+  , viewWithIKey   , childrenPassedToView   , foreignClass   , module React.Flux.DOM@@ -90,9 +99,16 @@   -- * Main   , reactRender   , reactRenderToString+  , exportViewToJavaScript+  , ArgumentsToProps+  , ReturnProps(..)    -- * Performance   -- $performance++  -- * Depracated+  , viewWithKey+  , ReactViewKey ) where  import Data.Typeable (Typeable)@@ -106,7 +122,7 @@ import React.Flux.Store  #ifdef __GHCJS__-import GHCJS.Types (JSString, JSVal, nullRef)+import GHCJS.Types (JSVal, nullRef) import GHCJS.Marshal (fromJSVal) #endif @@ -138,8 +154,8 @@  #endif --- | Render your React application to a string using either @React.renderToString@ if the first--- argument is false or @React.renderToStaticMarkup@ if the first argument is true.+-- | Render your React application to a string using either @ReactDOMServer.renderToString@ if the first+-- argument is false or @ReactDOMServer.renderToStaticMarkup@ if the first argument is true. -- Use this only on the server when running with node. reactRenderToString :: Typeable props                     => Bool -- ^ Render to static markup?  If true, this won't create extra DOM attributes@@ -159,11 +175,11 @@     maybe (error "Unable to convert string return to Text") return mtxt  foreign import javascript unsafe-    "(typeof ReactDOM === 'object' ? ReactDOM : React)['renderToString']($1)"+    "(typeof ReactDOMServer === 'object' ? ReactDOMServer : (typeof ReactDOM === 'object' ? ReactDOM : React))['renderToString']($1)"     js_ReactRenderToString :: ReactElementRef -> IO JSVal  foreign import javascript unsafe-    "(typeof ReactDOM === 'object' ? ReactDOM : React)['renderToStaticMarkup']($1)"+    "(typeof ReactDOMServer === 'object' ? ReactDOMServer : (typeof ReactDOM === 'object' ? ReactDOM : React))['renderToStaticMarkup']($1)"     js_ReactRenderStaticMarkup :: ReactElementRef -> IO JSVal  #else@@ -178,6 +194,8 @@ -- <https://facebook.github.io/react/docs/reconciliation.html virtual DOM/reconciliation> and  -- <https://facebook.github.io/react/docs/events.html event handlers> registered on the document. --+-- __Reconciliation__+-- -- To support fast reconciliation, React uses key properties (set by 'viewWithKey') and a -- @shouldComponentUpdate@ lifetime class method.  The React documentation on -- <https://facebook.github.io/react/docs/advanced-performance.html performance and immutable-js> talks@@ -185,24 +203,104 @@ -- implement a @shouldComponentUpdate@ method which compares if the javascript object representing -- the Haskell values for the @props@, @state@, and/or @storeData@ have changed.  Thus if you do not -- modify a Haskell value that is used for the @props@ or @state@ or @storeData@, React will skip--- re-rendering that view instance.  Care should be taken in the 'transform' function to not edit or--- recreate any values that are used as @props@.  For example, instead of something like+-- re-rendering that view instance.  Note that we are not checking equality, just if the javascript+-- object representing a Haskell object has changed, with some special support for pairs and tuples+-- of size three. ----- >[ (idx, todo) | (idx, todo) <- todos, idx /= deleteIdx ]+-- There is subtle issue: this check only works if the props are not a thunk but are an actual data+-- constructor.  Consider the following ----- you should prefer+-- >data MyStoreData = MyStoreData {+-- >   myA :: !A+-- > , myB :: !B+-- > , myC :: !C+-- > , myD :: !D+-- >} deriving (Show, Typeable)+-- >+-- >myAview :: ReactView A+-- >myAview = defineView ....+-- >+-- >myStoreView :: ReactView ()+-- >myStoreView = defineControllerView "my store" myStore $ \myData () ->+-- >    div_ $ view myAview (myA myData) mempty+-- >    div_ .... ----- >filter ((/=deleteIdx) . fst) todos--- --- After either of these transforms, the list of todos has changed so @mainSection@ will be re-rendered by--- React.  @mainSection@ calls @todoItem@ with the tuple @(idx,todo)@ as the props. In the latter--- transform snippet above, the tuple value for the entries is kept unchanged, so the--- @shouldComponentUpdate@ function for @todoItem@ will return false and React will not re-render--- each todo item.  If instead the tuple had been re-created as in the first snippet, the underlying--- javascript object will change even though the value is equal.  The @shouldComponentUpdate@--- function for @todoItem@ will then return true and React will re-render every todo item.  Thus the--- latter snippet is preferred.  In summary, if you are careful to only update the part of the store--- data that changed, React will only re-render those part of the page.+-- In @myStoreView@, note that @myA myData@ is passed as the props to @myAview@.  So consider the+-- situtation when say an action changes @C@ but leaves @A@ unchanged.  We would like for the+-- rendering of @myAview@ to be skipped, but unfortunately it will be re-rendered.  The reason is+-- that the props passed to @myAview@ is an unevaluated thunk @myA myData@.  Sure, the @A@+-- constructor has not changed and if the thunk is forced it will return this unchanged @A@ data+-- constructor, but the @shouldComponentUpdate@ test does not do any computation or evaluation, it+-- just checks if the passed in javascript object is the same as it was the last time the view was+-- rendered.  We can fix this by forcing the thunk before passing it to 'view', which I do via bang+-- patterns.  Instead of ever calling 'view' directly from a rendering function, for each+-- 'ReactView' I create a combinator as follows:+--+-- >myAview_ :: A -> ReactElementM handler ()+-- >myAview_ !a = view myAview a mempty+-- >+-- >myStoreView :: ReactView ()+-- >myStoreView = defineControllerView "my store" myStore $ \myData () ->+-- >    div_ $ myAview_ (myA myData)+-- >    div_ ....+--+-- Note the bang pattern on the @a@ parameter to @myAview_@.  What now happens is that the bang pattern+-- forces the thunk @myA myData@ to turn into the @A@ data constructor.  If an action does not edit the @A@ portion+-- of the store data, this will still be represented by the same javascript object as before and+-- React will not re-render the @myAview@.+--+-- Now consider another situtation where you would like a view that takes A and B.+--+-- >myAandBview :: ReactView (A, B)+-- >myAandBview = defineView ....+-- >+-- >myAandBview_ :: A -> B -> ReactElementM handler ()+-- >myAandBview_ !a !b = view myAandBview (a, b) mempty+-- >+-- >myStoreView :: ReactView ()+-- >myStoreView = defineControllerView "my store" myStore $ \myData () ->+-- >    div_ $ myAview_ (myA myData)+-- >    div_ $ myAandBview_ (myA myData) (myB myData)+-- >    div_ ....+--+-- Again, if you have an action that just changes @C@ you would like @myAandBview@ to not be+-- re-rendered.  With the simple javascript object check, it would be re-rendered because the props+-- are a tuple and the Haskell value (and thus javascript object) for the tuple is being recreated each+-- time @myStoreView@ is rendered.  To overcome this obstacle, @react-flux@ contains special code to check pairs+-- and tuples of size three.  If the props are a pair or a tuple of size three, the components of+-- the tuple will be compared to see if they are the same javascript object.  Thus similar to the+-- above we need to make sure each component of the tuple is not a thunk but a data constructor,+-- which happens via the bang patterns in @myAandBview_@.  The end result is that if an action just+-- changes @C@ or @D@ and leaves @A@ and @B@ unchanged, the above code will cause React to not+-- re-render @myAandBview@ because the two components of the pair are forced and are still the same+-- unchanged data value/javascript object.  You can see this in action inside the test suite if you+-- would like an example.+--+-- So far we have been focusing on making sure the new props are not a thunk by forcing it before+-- passing it into 'view'.  But we also need to make sure the initial props are not a thunk.  This+-- is not quite as bad since the check will only fail the next time a re-render occurs and after+-- that everything will be OK so we will still mostly skip re-rendering, but is still a small+-- annoyance.  There are several ways to fix this, but the easiest is to add bang patterns to the+-- definition of @MyStoreData@.  If you scroll up you can see that each member of @MyStoreData@ has+-- a bang pattern.  Thus when an action does change @A@, whatever a new value is set into @myA@, it+-- will not be a thunk but an actual data constructor.  Then the initial props passed into the view+-- will not be a thunk.+--+-- In summary, you should follow these rules:+--+--  1. Use bang patterns on each member in your store data.  In fact, once GHC 8 is released, I+--  plan on turning on the new @StrictData@ extension and then all these bang patterns can be+--  dropped.+--+--  2. Try and keep your view parameters as part of the store that will be unchanged by some+--  actions.  Use tuples of size two or three to combine multiple parts of the store data or even+--  data from multiple stores. (Tuples of larger size could be supported without much effort if+--  required.)+--+--  3. For each view, make a combinator with a underscore suffix which uses bang patterns to force+--  the props before passing it to the 'view' function.+--+-- __Events__ -- -- For events, React registers only global event handlers and also keeps event objects (the object -- passed to the handlers) in a pool and re-uses them for successive events.  We want to parse this
src/React/Flux/Addons/Bootstrap.hs view
@@ -1,6 +1,12 @@ -- | Bindings to <http://react-bootstrap.github.io/ React-Bootstrap>.  To use this -- binding, include the browser-global version of @react-bootstrap.js@ so that @window.ReactBootstrap@ -- is defined.  You can then use 'bootstrap_' inside your rendering functions.+--+-- Note: I initially wrote these bindings when I was first starting React, but after some experience+-- I think that bootstrap and react just do not fit together and I no longer use bootstrap and react+-- together.  While I am leaving this here for backwards compatibility, I suggest you use a different+-- library.  I am currently using <http://www.material-ui.com Material UI> and accessing the+-- components using @foreign_@. module React.Flux.Addons.Bootstrap (     bootstrap_ ) where
src/React/Flux/Addons/Intl.hs view
@@ -1,13 +1,7 @@--- | Bindings to the <http://formatjs.io/react/ ReactIntl> library, which allows easy formatting of--- numbers, dates, times, relative times, pluralization, and translated messages. This library can--- be used for formatting and pluralization even if you intend to present your application in a single--- language and locale.------ These bindings are for the 2.0 version of ReactIntl which is currently as a release candidate--- (I am currently using v2.0.0-rc-1).  For temporary documentation,--- see <https://github.com/yahoo/react-intl/issues/162 issue62>.  Because this is a binding to a--- release candidate, the API below might change!  I consider it unlikely, but if a change is required--- I will violate the PVP and not increment the major number, just the minor number.+-- | Bindings to the <https://github.com/yahoo/react-intl ReactIntl> library version 2, which allows easy+-- formatting of numbers, dates, times, relative times, pluralization, and translated messages. This+-- library can be used for formatting and pluralization even if you intend to present your application+-- in a single language and locale. -- -- To use these bindings, you need to provide the @ReactIntl@ variable.  In the browser you can just -- load the @react-intl.min.js@ script onto the page so that @window.ReactIntl@ exists.  If you are@@ -91,6 +85,10 @@     intlProvider_    -- * Formatting+  , IntlProperty+  , iprop+  , dayProp+  , timeProp      -- ** Numbers   , int_@@ -125,6 +123,8 @@   , messageProp'   , htmlMsg   , htmlMsg'+  , formattedMessage_+  , formattedHtmlMessage_    -- * Translation   , Message(..)@@ -136,7 +136,6 @@  import Control.Monad (when, forM_) import Data.Aeson (Object, Value(Object), object, (.=))-import Data.Aeson.Types (Pair) import Data.Char (ord, isPrint) import Data.List (sortBy) import Data.Maybe (catMaybes, fromMaybe)@@ -146,7 +145,7 @@ import Language.Haskell.TH (runIO, Q, Loc, location, ExpQ) import Language.Haskell.TH.Syntax (liftString, qGetQ, qPutQ, reportWarning, Dec) import React.Flux-import React.Flux.Internal (PropertyOrHandler(PropertyFromContext))+import React.Flux.Internal (PropertyOrHandler(PropertyFromContext), toJSString) import System.IO (withFile, IOMode(..)) import qualified Data.Aeson as Aeson import qualified Data.ByteString as B@@ -157,9 +156,9 @@  #ifdef __GHCJS__ -import GHCJS.Types (JSVal, JSString)+import GHCJS.Types (JSString, JSVal) import GHCJS.Marshal (ToJSVal(..))-import qualified Data.JSString as JSS+import qualified JavaScript.Object as JSO  foreign import javascript unsafe     "$r = ReactIntl['IntlProvider']"@@ -194,8 +193,8 @@     js_mkDate :: Int -> Int -> Int -> JSVal  -- | Convert a day to a javascript Date-dayToRef :: Day -> JSVal-dayToRef day = js_mkDate (fromIntegral y) m d+dayToJSVal :: Day -> JSVal+dayToJSVal day = js_mkDate (fromIntegral y) m d     where         (y, m, d) = toGregorian day @@ -204,31 +203,17 @@     js_mkDateTime :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> JSVal  -- | Convert a UTCTime to a javascript date object.-timeToRef :: UTCTime -> JSVal-timeToRef (UTCTime uday time) = js_mkDateTime (fromIntegral year) month day hour minute sec micro+timeToJSVal :: UTCTime -> JSVal+timeToJSVal (UTCTime uday time) = js_mkDateTime (fromIntegral year) month day hour minute sec milli     where         (year, month, day) = toGregorian uday         TimeOfDay hour minute pSec = timeToTimeOfDay time         (sec, fracSec) = properFraction pSec-        micro = round $ fracSec * 1000000-+        milli = round $ fracSec * 1000 -- milli is 10^3  foreign import javascript unsafe     "$1['intl'][$2]($3, $4)"-    js_callContextAPI :: JSVal -> JSString -> JSVal -> JSVal -> IO JSVal---data ContextApiCall a = ContextApiCall String a [Pair] JSVal--instance ToJSVal a => ToJSVal (ContextApiCall a) where-    toJSVal (ContextApiCall name a b ctx) = do-        aRef <- toJSVal a-        bRef <- toJSVal $ object b-        js_callContextAPI ctx (JSS.pack name) aRef bRef--formatCtx :: ToJSVal a => String -> String -> a -> [Pair] -> PropertyOrHandler handler-formatCtx name func val options = PropertyFromContext name $ ContextApiCall func val options-+    js_callContextAPI :: JSVal -> JSString -> JSVal -> JSO.Object -> IO JSVal #else  type JSVal = ()@@ -254,21 +239,53 @@ js_formatHtmlMsg :: JSVal js_formatHtmlMsg = () -dayToRef :: Day -> JSVal-dayToRef _ = ()+dayToJSVal :: Day -> JSVal+dayToJSVal _ = () -timeToRef :: UTCTime -> JSVal-timeToRef _ = ()+timeToJSVal :: UTCTime -> JSVal+timeToJSVal _ = ()  class ToJSVal a+instance ToJSVal JSVal+type JSString = String+#endif -formatCtx :: String -> String -> a -> [Pair] -> PropertyOrHandler handler-formatCtx name _ _ _ = PropertyFromContext name $ \() -> () +data IntlProperty = forall ref. ToJSVal ref => IntlProperty JSString ref++iprop :: ToJSVal v => JSString -> v -> IntlProperty+iprop = IntlProperty++dayProp :: JSString -> Day -> IntlProperty+dayProp n d = IntlProperty n (dayToJSVal d)++timeProp :: JSString -> UTCTime -> IntlProperty+timeProp n t = IntlProperty n (timeToJSVal t)++#ifdef __GHCJS__+data ContextApiCall a = ContextApiCall JSString a [IntlProperty] JSVal++instance ToJSVal a => ToJSVal (ContextApiCall a) where+    toJSVal (ContextApiCall name a props ctx) = do+        aRef <- toJSVal a+        propsObj <- JSO.create+        forM_ props $ \(IntlProperty n p) -> do+            pRef <- toJSVal p+            JSO.setProp n pRef propsObj+        js_callContextAPI ctx name aRef propsObj++formatCtx :: ToJSVal a => JSString -> JSString -> a -> [IntlProperty] -> PropertyOrHandler handler+formatCtx name func val options = PropertyFromContext name $ ContextApiCall func val options++#else+formatCtx :: JSString -> JSString -> a -> [IntlProperty] -> PropertyOrHandler handler+formatCtx name _ _ _ = PropertyFromContext name $ \() -> () #endif ++ -- | Use the IntlProvider to set the @locale@, @formats@, and @messages@ property.-intlProvider_ :: String -- ^ the locale to use+intlProvider_ :: JSString -- ^ the locale to use               -> Maybe JSVal                   -- ^ A reference to translated messages, which must be an object with keys                   -- 'MessageId' and value the translated message.  Set this as Nothing if you are not using@@ -283,7 +300,7 @@               -> ReactElementM eventHandler a intlProvider_ locale mmsgs mformats = foreignClass js_intlProvider props     where-        props = catMaybes [ Just ("locale" @= locale)+        props = catMaybes [ Just ("locale" &= locale)                           , (property "messages") <$> mmsgs                           , (property "formats" . Object) <$> mformats                           ]@@ -312,10 +329,10 @@ -- the number has not changed. 'formattedNumberProp' is needed if the formatted number has to be -- a property on another element, such as the placeholder for an input element. formattedNumberProp :: ToJSVal num-                    => String -- ^ the property to set+                    => JSString -- ^ the property to set                     -> num -- ^ the number to format-                    -> [Pair] -- ^ any options accepted by-                              -- <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat Intl.NumberFormat>+                    -> [IntlProperty] -- ^ any options accepted by+                                      -- <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat Intl.NumberFormat>                     -> PropertyOrHandler handler formattedNumberProp name x options = formatCtx name "formatNumber" x options @@ -330,21 +347,21 @@ -- These properties coorespond directly the options accepted by -- <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat Intl.DateTimeFormat>. data DayFormat = DayFormat {-    weekdayF :: Maybe String -- ^ possible values are narrow, short, and long-  , eraF :: Maybe String -- ^ possible values are narrow, short, and long-  , yearF :: Maybe String -- ^ possible values are numeric and 2-digit-  , monthF :: Maybe String -- ^ possible values are numeric, 2-digit, narrow, short, and long-  , dayF :: Maybe String -- ^ possible values are numeric and 2-digit+    weekdayF :: Maybe JSString -- ^ possible values are narrow, short, and long+  , eraF :: Maybe JSString -- ^ possible values are narrow, short, and long+  , yearF :: Maybe JSString -- ^ possible values are numeric and 2-digit+  , monthF :: Maybe JSString -- ^ possible values are numeric, 2-digit, narrow, short, and long+  , dayF :: Maybe JSString -- ^ possible values are numeric and 2-digit } deriving Show  -- | Convert a format to the properties accepted by FormattedDate dayFtoProps :: DayFormat -> [PropertyOrHandler handler] dayFtoProps (DayFormat w e y m d) = catMaybes-    [ ("weekday"@=) <$> w-    , ("era"@=) <$> e-    , ("year"@=) <$> y-    , ("month"@=) <$> m-    , ("day"@=) <$> d+    [ ("weekday"&=) <$> w+    , ("era"&=) <$> e+    , ("year"&=) <$> y+    , ("month"&=) <$> m+    , ("day"&=) <$> d     ]  -- | A short day format, where month is \"short\" and year and day are \"numeric\".@@ -363,19 +380,19 @@ -- These properties coorespond directly the options accepted by -- <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat Intl.DateTimeFormat>. data TimeFormat = TimeFormat {-    hourF :: Maybe String -- ^ possible values are numeric and 2-digit-  , minuteF :: Maybe String -- ^ possible values are numeric and 2-digit-  , secondF :: Maybe String -- ^ possible values are numeric and 2-digit-  , timeZoneNameF :: Maybe String -- ^ possible values are short and long+    hourF :: Maybe JSString -- ^ possible values are numeric and 2-digit+  , minuteF :: Maybe JSString -- ^ possible values are numeric and 2-digit+  , secondF :: Maybe JSString -- ^ possible values are numeric and 2-digit+  , timeZoneNameF :: Maybe JSString -- ^ possible values are short and long } deriving Show  -- | Convert a time format to properties for the FormattedDate element timeFtoProps :: TimeFormat -> [PropertyOrHandler handler] timeFtoProps (TimeFormat h m s t) = catMaybes-    [ ("hour"@=) <$> h-    , ("minute"@=) <$> m-    , ("second"@=) <$> s-    , ("timeZoneName"@=) <$> t+    [ ("hour"&=) <$> h+    , ("minute"&=) <$> m+    , ("second"&=) <$> s+    , ("timeZoneName"&=) <$> t     ]  -- | A default date and time format, using 'shortDate' and then numeric for hour, minute, and@@ -393,7 +410,7 @@ day_ :: DayFormat -> Day -> ReactElementM eventHandler () day_ fmt day = time_ [property "dateTime" dateRef] $ foreignClass js_formatDate props mempty     where-        dateRef = dayToRef day+        dateRef = dayToJSVal day         props = property "value" dateRef : dayFtoProps fmt  -- | Display a 'UTCTime' using the given format.  Despite giving the time in UTC, it will be@@ -402,7 +419,7 @@ utcTime_ :: (DayFormat, TimeFormat) -> UTCTime -> ReactElementM eventHandler () utcTime_ (dayFmt, timeF) t = time_ [property "dateTime" timeRef] $ foreignClass js_formatDate props mempty     where-        timeRef = timeToRef t+        timeRef = timeToJSVal t         props = property "value" timeRef : (dayFtoProps dayFmt ++ timeFtoProps timeF)  -- | A raw <http://formatjs.io/react/#formatted-date FormattedDate> class which allows custom@@ -413,46 +430,46 @@ formattedDate_ :: Either Day UTCTime -> [PropertyOrHandler eventHandler] -> ReactElementM eventHandler () formattedDate_ t props = foreignClass js_formatDate (valProp:props) mempty     where-        valProp = property "value" $ either dayToRef timeToRef t+        valProp = property "value" $ either dayToJSVal timeToJSVal t  -- | Format a day or time as a string, and then use it as the value for a property.  'day_', -- 'utcTime_', or 'formattedDate_' should be prefered because as components they can avoid re-rendering when -- the date has not changed. 'formattedDateProp' is needed if the formatted date has to be -- a property on another element, such as the placeholder for an input element.-formattedDateProp :: String -- ^ the property to set+formattedDateProp :: JSString -- ^ the property to set                   -> Either Day UTCTime -- ^ the day or time to format-                  -> [Pair] -- ^ Any options supported by-                            -- <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat Intl.DateTimeFormat>.+                  -> [IntlProperty] -- ^ Any options supported by+                                    -- <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat Intl.DateTimeFormat>.                   -> PropertyOrHandler eventHandler formattedDateProp name (Left day) options =-    formatCtx name "formatDate" (dayToRef day) options+    formatCtx name "formatDate" (dayToJSVal day) options formattedDateProp name (Right time) options =-    formatCtx name "formatTime" (timeToRef time) options+    formatCtx name "formatTime" (timeToJSVal time) options  -- | Display the 'UTCTime' as a relative time.  In addition, wrap the display in a HTML5 -- <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time time> element. relativeTo_ :: UTCTime -> ReactElementM eventHandler () relativeTo_ t = time_ [property "dateTime" timeRef] $ foreignClass js_formatRelative [property "value" timeRef] mempty     where-        timeRef = timeToRef t+        timeRef = timeToJSVal t  -- | Format the given UTCTime using the <http://formatjs.io/react/#formatted-relative FormattedRelative> -- class to display a relative time to now.  The given 'UTCTime' is passed in the value property. -- The supported style/formatting properties are \"units\" which can be one of second, minute, hour, -- day, month, or year and \"style\" which if given must be numeric. formattedRelative_ :: UTCTime -> [PropertyOrHandler eventHandler] -> ReactElementM eventHandler ()-formattedRelative_ t props = foreignClass js_formatRelative (property "value" (timeToRef t) : props) mempty+formattedRelative_ t props = foreignClass js_formatRelative (property "value" (timeToJSVal t) : props) mempty  -- | Format a time as a relative time string, and then use it as the value for a property. -- 'relativeTo_' or 'formattedRelative_' should be prefered because as components they can avoid re-rendering when -- the date has not changed. 'formattedRelativeProp' is needed if the formatted date has to be -- a property on another element, such as the placeholder for an input element.-formattedRelativeProp :: String -- ^ te property to set+formattedRelativeProp :: JSString -- ^ te property to set                       -> UTCTime -- ^ the time to format-                      -> [Pair] -- ^ an object with properties \"units\" and \"style\".  \"units\" accepts values second, minute, hour-                                -- day, month, or year and \"style\" accepts only the value \"numeric\".+                      -> [IntlProperty] -- ^ an object with properties \"units\" and \"style\".  \"units\" accepts values second, minute, hour+                                        -- day, month, or year and \"style\" accepts only the value \"numeric\".                       -> PropertyOrHandler eventHandler-formattedRelativeProp name time options = formatCtx name "formatRelative" (timeToRef time) options+formattedRelativeProp name time options = formatCtx name "formatRelative" (timeToJSVal time) options  -------------------------------------------------------------------------------- -- Plural@@ -468,7 +485,7 @@ -- | Format a number properly based on pluralization, and then use it as the value for a property. -- 'plural_' should be preferred, but 'pluralProp' can be used in places where a component is not -- possible such as the placeholder of an input element.-pluralProp :: ToJSVal val => String -> val -> [Pair] -> PropertyOrHandler eventHandler+pluralProp :: ToJSVal val => JSString -> val -> [IntlProperty] -> PropertyOrHandler eventHandler pluralProp name val options = formatCtx name "formatPlural" val options  --------------------------------------------------------------------------------@@ -519,9 +536,9 @@         -> T.Text -- ^ The default message written in <http://formatjs.io/guides/message-syntax/ ICU message syntax>.                   -- This message is used if no translation is found, and is also the message given to the translators.         -> ExpQ --Q (TExp ([PropertyOrHandler eventHandler] -> ReactElementM eventHandler ()))-message ident m = formattedMessage [|js_formatMsg|] ident $ Message "" m+message ident m = formatMessage [|js_formatMsg|] ident $ Message "" m --- | Similar to 'message', but produce an expression of type @['Pair'] -> PropertyOrHandler handler@,+-- | Similar to 'message', but produce an expression of type @['IntlProperty'] -> PropertyOrHandler handler@, -- which should be passed the values for the message.  This allows you to format messages in places -- where using a component like 'message' is not possible, such as the placeholder of input -- elements. 'message' should be prefered since it can avoid re-rendering the formatting if the@@ -533,7 +550,7 @@ -- >       , $(messageProp "placeholder" "ageplaceholder" "Hello {name}, enter your age") -- >             [ "name" .= nameFrom storeData ] -- >       ]-messageProp :: String -- ^ the property name to set+messageProp :: T.Text -- ^ the property name to set             -> MessageId -- ^ the message identifier             -> T.Text -- ^ the default message written in ICU message syntax.             -> ExpQ@@ -545,10 +562,10 @@          -> T.Text -- ^ A description indented to provide context for translators          -> T.Text -- ^ The default message written in ICU message syntax          -> ExpQ --Q (TExp ([PropertyOrHandler eventHandler] -> ReactElementM eventHandler ()))-message' ident descr m = formattedMessage [|js_formatMsg|] ident $ Message descr m+message' ident descr m = formatMessage [|js_formatMsg|] ident $ Message descr m  -- | A varient of 'messageProp' which allows you to specify some context for translators.-messageProp' :: String -- ^ property to set+messageProp' :: T.Text -- ^ property to set              -> MessageId              -> T.Text -- ^ A description intended to provide context for translators              -> T.Text -- ^ The default message written in ICU message syntax@@ -563,14 +580,14 @@ htmlMsg :: MessageId         -> T.Text -- ^ default message written in ICU message syntax         -> ExpQ-htmlMsg ident m = formattedMessage [|js_formatHtmlMsg|] ident $ Message "" m+htmlMsg ident m = formatMessage [|js_formatHtmlMsg|] ident $ Message "" m  -- | A variant of 'htmlMsg' that allows you to specify some context for translators. htmlMsg' :: MessageId          -> T.Text -- ^ A description intended to provide context for translators          -> T.Text -- ^ The default message written in ICU message syntax          -> ExpQ-htmlMsg' ident descr m = formattedMessage [|js_formatHtmlMsg|] ident $ Message descr m+htmlMsg' ident descr m = formatMessage [|js_formatHtmlMsg|] ident $ Message descr m  recordMessage :: MessageId -> Message -> Q () recordMessage ident m = do@@ -587,18 +604,30 @@     qPutQ $ H.insert ident (m, curLoc) mmap  -- | Utility function for messages-formattedMessage :: ExpQ -> MessageId -> Message -> ExpQ --Q (TExp ([PropertyOrHandler eventHandler] -> ReactElementM eventHandler ()))-formattedMessage cls ident m = do+formatMessage :: ExpQ -> MessageId -> Message -> ExpQ --Q (TExp ([PropertyOrHandler eventHandler] -> ReactElementM eventHandler ()))+formatMessage cls ident m = do     recordMessage ident m     let liftText x = [| T.pack $(liftString $ T.unpack x)|]         liftedMsg = [| Message $(liftText $ msgDescription m) $(liftText $ msgDefaultMsg m) |]     [|\vals -> foreignClass $cls (messageToProps $(liftText ident) $liftedMsg vals) mempty |] -formatMessageProp :: String -> String -> MessageId -> Message -> ExpQ -- Q (TExp ([Pair] -> PropertyOrHandler eventHandler))+formatMessageProp :: T.Text -> T.Text -> MessageId -> Message -> ExpQ -- Q (TExp ([IntlProperty] -> PropertyOrHandler eventHandler)) formatMessageProp func name ident m = do     recordMessage ident m     let liftedMsg = [| object ["id" .= T.pack $(liftString $ T.unpack ident), "defaultMessage" .= T.pack $(liftString $ T.unpack $ msgDefaultMsg m) ] |]-    [|\options -> formatCtx $(liftString name) $(liftString func) $liftedMsg options |]+    [|\options -> formatCtx (toJSString $(liftString $ T.unpack name)) (toJSString $(liftString $ T.unpack func)) $liftedMsg options |]++-- | A raw @FormattedMessage@ element.  The given properties are passed directly with no handling.+-- Any message is not recorded in Template Haskell and will not appear in any resulting message file+-- created by 'writeIntlMessages'.+formattedMessage_ :: [PropertyOrHandler eventHandler] -> ReactElementM eventHandler ()+formattedMessage_ props = foreignClass js_formatMsg props mempty++-- | A raw @FormattedHTMLMessage@ element.  The given properties are passed directly with no handling.+-- Any message is not recorded in Template Haskell and will not appear in any resulting message file+-- created by 'writeIntlMessages'.+formattedHtmlMessage_ :: [PropertyOrHandler eventHandler] -> ReactElementM eventHandler ()+formattedHtmlMessage_ props = foreignClass js_formatHtmlMsg props mempty  -- | Perform an arbitrary IO action on the accumulated messages at compile time, which usually -- should be to write the messages to a file.  Despite producing a value of type @Q [Dec]@,
src/React/Flux/Combinators.hs view
@@ -8,6 +8,7 @@   , faIcon_   , foreign_   , labeledInput_+  , style   -- * Ajax   , initAjax   , jsonAjax@@ -32,7 +33,7 @@ import Control.DeepSeq (deepseq) import GHCJS.Foreign.Callback import GHCJS.Marshal (ToJSVal(..), toJSVal_aeson, FromJSVal(..))-import GHCJS.Types (JSString, JSVal)+import GHCJS.Types (JSVal) import qualified Data.Text as T import qualified Data.JSString.Text as JSS @@ -44,8 +45,6 @@ #else js_lookupWindow :: a -> () js_lookupWindow _ = ()--type JSString = String #endif  -- | A wrapper around 'foreignClass' that looks up the class on the `window`.  I use it for several@@ -74,11 +73,11 @@ -- >                       ] -- >        , callback "onChange" $ \(i :: String) -> dispatch $ ItemChangedTo i -- >        ]-foreign_ :: String -- ^ this should be the name of a property on `window` which contains a react class.+foreign_ :: JSString -- ^ this should be the name of a property on `window` which contains a react class.          -> [PropertyOrHandler handler] -- ^ properties          -> ReactElementM handler a -- ^ children          -> ReactElementM handler a-foreign_ x = foreignClass (js_lookupWindow $ toJSString x)+foreign_ x = foreignClass (js_lookupWindow x)  -- | A 'div_' with the given class name (multiple classes can be separated by spaces).  This is -- useful for defining rows and columns in your CSS framework of choice.  I use@@ -88,28 +87,19 @@ -- >    cldiv_ "pure-u-1-3" $ p_ "First Third" -- >    cldiv_ "pure-u-1-3" $ p_ "Middle Third" -- >    cldiv_ "pure-u-1-3" $ p_ "Last Third"------ You should consider writing something like the following for the various components in your frontend--- of choice.  In PureCSS, I use:------ >prow_ :: ReactElementM handler a -> ReactElementM handler a--- >prow_ = cldiv_ "pure-g"--- >--- >pcol_ :: String -> ReactElementM handler a -> ReactElementM handler a--- >pcol_ cl = cldiv_ (unwords $ map ("pure-u-"++) $ words cl)-cldiv_ :: String -> ReactElementM handler a -> ReactElementM handler a-cldiv_ cl = div_ ["className" @= cl]+cldiv_ :: JSString -> ReactElementM handler a -> ReactElementM handler a+cldiv_ cl = div_ ["className" &= cl]  -- | A 'button_' with the given class names and `onClick` handler. ----- >clbutton_ ["pure-button button-success"] (dispatch LaunchTheMissiles) $ do+-- >clbutton_ "pure-button button-success" (dispatch LaunchTheMissiles) $ do -- >    faIcon_ "rocket" -- >    "Launch the missiles!"-clbutton_ :: String  -- ^ class names separated by spaces+clbutton_ :: JSString  -- ^ class names separated by spaces           -> handler -- ^ the onClick handler for the button           -> ReactElementM handler a -- ^ the children           -> ReactElementM handler a-clbutton_ cl h = button_ ["className" @= cl, onClick (\_ _ -> h)]+clbutton_ cl h = button_ ["className" &= cl, onClick (\_ _ -> h)]  -- | A 'label_' and an 'input_' together.  Useful for laying out forms.  For example, a -- stacked <http://purecss.io/forms/ Pure CSS Form> could be@@ -123,20 +113,25 @@ -- >            ["type" $= "password"] -- -- The second 'labeledInput_' shows an example using "React.Flux.Addons.Intl".-labeledInput_ :: String -- ^ the ID for the input element+labeledInput_ :: JSString -- ^ the ID for the input element               -> ReactElementM handler () -- ^ the label content.  This is wrapped in a 'label_' with a `htmlFor` property                                           -- equal to the given ID.               -> [PropertyOrHandler handler] -- ^ the properties to pass to 'input_'.  A property with key `id` is added to this list of properties.               -> ReactElementM handler ()-labeledInput_ ident lbl props = label_ ["htmlFor" @= ident] lbl <> input_ (("id" @= ident):props)+labeledInput_ ident lbl props = label_ ["htmlFor" &= ident] lbl <> input_ (("id" &= ident):props)  -- | A <http://fortawesome.github.io/Font-Awesome/ Font Awesome> icon.  The given string is prefixed -- by `fa fa-` and then used as the class for an `i` element.  This allows you to icons such as -- -- >faIcon_ "fighter-jet" -- produces <i class="fa fa-fighter-jet"> -- >faIcon_ "refresh fa-spin" -- produces <i class="fa fa-refresh fa-spin">-faIcon_ :: String -> ReactElementM handler ()-faIcon_ cl = i_ ["className" @= ("fa fa-" ++ cl)] mempty+faIcon_ :: JSString -> ReactElementM handler ()+faIcon_ cl = i_ ["className" &= ("fa fa-" <> cl)] mempty++-- | A simple combinator to easily write <https://facebook.github.io/react/tips/inline-styles.html inline styles>+-- on elements.+style :: [(JSString,JSString)] -> PropertyOrHandler handler+style = nestedProperty "style" . map (\(n,a) -> (n &= a))  -------------------------------------------------------------------------------- --- Ajax
src/React/Flux/DOM.hs view
@@ -51,7 +51,7 @@ -- | This class allows the DOM combinators to optionally take a list of properties or handlers, or -- for the list to be omitted. class Term eventHandler arg result | result -> arg, result -> eventHandler where-    term :: String -> arg -> result+    term :: JSString -> arg -> result  instance (child ~ ReactElementM eventHandler a) => Term eventHandler [PropertyOrHandler eventHandler] (child -> ReactElementM eventHandler a) where     term name props = el name props
src/React/Flux/Internal.hs view
@@ -2,22 +2,27 @@ -- -- Normally you should not need to use anything in this module.  This module is only needed if you have -- complicated interaction with third-party javascript rendering code.+ module React.Flux.Internal(     ReactViewRef(..)-  , ReactViewKey(..)   , ReactElementRef(..)   , HandlerArg(..)   , PropertyOrHandler(..)   , property+  , (&=)   , ReactElement(..)   , ReactElementM(..)+  , elemString   , elemText+  , elemJSString   , elemShow   , el   , childrenPassedToView   , elementToM   , mkReactElement+  , exportViewToJs   , toJSString+  , JSString ) where  import           Data.String (IsString(..))@@ -25,10 +30,12 @@ import           Data.Typeable (Typeable) import           Control.Monad.Writer import           Control.Monad.Identity (Identity(..))+import qualified Data.Text as T  #ifdef __GHCJS__ import           Unsafe.Coerce import qualified Data.JSString as JSS+import qualified Data.JSString.Text as JSS import           JavaScript.Array (JSArray) import qualified JavaScript.Array as JSA import           GHCJS.Foreign.Callback@@ -39,7 +46,6 @@ import           React.Flux.Export #else import Data.Text (Text)-type Callback a = () type JSVal = () class ToJSVal a instance ToJSVal Value@@ -47,6 +53,10 @@ instance ToJSVal () class IsJSVal a type JSArray = JSVal+type JSString = String+instance IsJSVal JSVal+instance IsJSVal JSString+instance ToJSVal JSString #endif  -- type JSObject a = JSO.Object a@@ -72,29 +82,34 @@ -- passed as the second argument to @React.createElement@. data PropertyOrHandler handler =    forall ref. ToJSVal ref => Property-      { propertyName :: String+      { propertyName :: JSString       , propertyVal :: ref       }  | forall ref. ToJSVal ref => PropertyFromContext -      { propFromThisName :: String+      { propFromThisName :: JSString       , propFromThisVal :: JSVal -> ref -- ^ will be passed this.context       }  | NestedProperty-      { nestedPropertyName :: String+      { nestedPropertyName :: JSString       , nestedPropertyVals :: [PropertyOrHandler handler]       }  | ElementProperty-      { elementPropertyName :: String+      { elementPropertyName :: JSString       , elementValue :: ReactElementM handler ()       }  | CallbackPropertyWithArgumentArray-      { caPropertyName :: String+      { caPropertyName :: JSString       , caFunc :: JSArray -> IO handler       }  | CallbackPropertyWithSingleArgument-      { csPropertyName :: String+      { csPropertyName :: JSString       , csFunc :: HandlerArg -> handler       }+ | forall props. Typeable props => CallbackPropertyReturningView+      { cretPropertyName :: JSString+      , cretArgToProp :: JSArray -> IO props+      , cretView :: ReactViewRef props+      }  instance Functor PropertyOrHandler where     fmap _ (Property name val) = Property name val@@ -104,28 +119,16 @@         ElementProperty name $ ReactElementM $ mapWriter (\((),e) -> ((), fmap f e)) mkElem     fmap f (CallbackPropertyWithArgumentArray name h) = CallbackPropertyWithArgumentArray name (fmap f . h)     fmap f (CallbackPropertyWithSingleArgument name h) = CallbackPropertyWithSingleArgument name (f . h)+    fmap _ (CallbackPropertyReturningView name f v) = CallbackPropertyReturningView name f v  -- | Create a property from anything that can be converted to a JSVal-property :: ToJSVal val => String -> val -> PropertyOrHandler handler+property :: ToJSVal val => JSString -> val -> PropertyOrHandler handler property = Property --- | Keys in React can either be strings or integers-class ReactViewKey key where-    toKeyRef :: key -> IO JSVal--#if __GHCJS__-instance ReactViewKey String where-    toKeyRef = return . unsafeCoerce . toJSString--instance ReactViewKey Int where-    toKeyRef i = toJSVal i-#else-instance ReactViewKey String where-    toKeyRef = const $ return ()--instance ReactViewKey Int where-    toKeyRef = const $ return ()-#endif+-- | Create a property for anything that can be converted to a javascript value using the @ToJSVal@+-- class from the @ghcjs-base@ package..  This is just an infix version of 'property'.+(&=) :: ToJSVal a => JSString -> a -> PropertyOrHandler handler+n &= a = Property n a  -- | A React element is a node or list of nodes in a virtual tree.  Elements are the output of the -- rendering functions of classes.  React takes the output of the rendering function (which is a@@ -134,19 +137,18 @@ -- elements are rendered into the browser DOM as siblings. data ReactElement eventHandler     = ForeignElement-        { fName :: Either String (ReactViewRef Object)+        { fName :: Either JSString (ReactViewRef Object)         , fProps :: [PropertyOrHandler eventHandler]         , fChild :: ReactElement eventHandler         }-    | forall props key. (Typeable props, ReactViewKey key) => ViewElement+    | forall props. Typeable props => ViewElement         { ceClass :: ReactViewRef props-        , ceKey :: Maybe key-        -- TODO: ref?  ref support would need to tie into the Class too.+        , ceKey :: Maybe JSVal         , ceProps :: props         , ceChild :: ReactElement eventHandler         }     | ChildrenPassedToView-    | Content String+    | Content JSString     | Append (ReactElement eventHandler) (ReactElement eventHandler)     | EmptyElement @@ -200,22 +202,35 @@          in elementToM () $ Append e1' e2'  instance (a ~ ()) => IsString (ReactElementM eventHandler a) where-    fromString s = elementToM () $ Content s+    fromString s = elementToM () $ Content $ toJSString s --- | Create a text element from a string.  This is an alias for 'fromString'.  The text content is--- escaped to be HTML safe.  If you need to insert HTML, instead use the+-- | 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>--- property.-elemText :: String -> ReactElementM eventHandler ()-elemText s = elementToM () $ Content s+-- property.  This is an alias for 'fromString'.+elemString :: String -> ReactElementM eventHandler ()+elemString s = elementToM () $ Content $ toJSString s +-- | Create a text element from a text value. The text content is escaped to be HTML safe.+elemText :: T.Text -> ReactElementM eventHandler ()+#ifdef __GHCJS__+elemText s = elementToM () $ Content $ JSS.textToJSString s+#else+elemText s = elementToM () $ Content $ T.unpack s+#endif++-- | Create a text element from a @JSString@.  This is more efficient for hard-coded strings than+-- converting from text to a JavaScript string.  The string is escaped to be HTML safe.+elemJSString :: JSString -> ReactElementM eventHandler ()+elemJSString s = elementToM () $ Content s+ -- | Create an element containing text which is the result of 'show'ing the argument. -- Note that the resulting string is then escaped to be HTML safe. elemShow :: Show a => a -> ReactElementM eventHandler ()-elemShow s = elementToM () $ Content $ show s+elemShow s = elementToM () $ Content $ toJSString $ show s  -- | Create a React element.-el :: String -- ^ The element name (the first argument to @React.createElement@).+el :: JSString -- ^ The element name (the first argument to @React.createElement@).    -> [PropertyOrHandler eventHandler] -- ^ The properties to pass to the element (the second argument to @React.createElement@).    -> ReactElementM eventHandler a -- ^ The child elements (the third argument to @React.createElement@).    -> ReactElementM eventHandler a@@ -233,6 +248,8 @@ -- mkReactElement has two versions ---------------------------------------------------------------------------------------------------- +type CallbackToRelease = JSVal+ -- | 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.@@ -241,7 +258,7 @@                -> IO JSVal -- ^ this.context                -> IO [ReactElementRef] -- ^ this.props.children                -> ReactElementM eventHandler ()-               -> IO (ReactElementRef, [Callback (JSVal -> IO ())])+               -> IO (ReactElementRef, [CallbackToRelease])  #ifdef __GHCJS__ @@ -252,47 +269,52 @@         mToElem eM = do             let e = execWriter $ runReactElementM eM                 e' = case e of-                        Content txt -> ForeignElement (Left "span") [] (Content txt)+                        Content txt -> ForeignElement (Right $ ReactViewRef js_textWrapper) [] (Content txt)                         _ -> e             refs <- createElement e'             case refs of-                [] -> lift $ js_ReactCreateElementNoChildren "div"+                [] -> lift $ js_ReactCreateElementNoChildren js_divLikeElement                 [x] -> return x                 xs -> lift $ do                     emptyObj <- JSO.create                     let arr = jsval $ JSA.fromList $ map reactElementRef xs-                    js_ReactCreateElementName "div" emptyObj arr+                    js_ReactCreateForeignElement (ReactViewRef js_divLikeElement) emptyObj arr          -- add the property or handler to the javascript object         addPropOrHandlerToObj :: JSO.Object -> PropertyOrHandler eventHandler -> MkReactElementM ()         addPropOrHandlerToObj obj (Property n val) = lift $ do             vRef <- toJSVal val-            JSO.setProp (toJSString n) vRef obj+            JSO.setProp n vRef obj         addPropOrHandlerToObj obj (PropertyFromContext n f) = lift $ do             ctx <- getContext             vRef <- toJSVal $ f ctx-            JSO.setProp (toJSString n) vRef obj+            JSO.setProp n vRef obj         addPropOrHandlerToObj obj (NestedProperty n vals) = do             nested <- lift $ JSO.create             mapM_ (addPropOrHandlerToObj nested) vals-            lift $ JSO.setProp (toJSString n) (jsval nested) obj+            lift $ JSO.setProp n (jsval nested) obj         addPropOrHandlerToObj obj (ElementProperty name rM) = do             ReactElementRef ref <- mToElem rM-            lift $ JSO.setProp (toJSString name) ref obj+            lift $ JSO.setProp name ref obj         addPropOrHandlerToObj obj (CallbackPropertyWithArgumentArray name func) = do             -- this will be released by the render function of the class (jsbits/class.js)             cb <- lift $ syncCallback1 ContinueAsync $ \argref -> do                 handler <- func $ unsafeCoerce argref                 runHandler handler-            tell [cb]+            tell [jsval cb]             wrappedCb <- lift $ js_CreateArgumentsCallback cb-            lift $ JSO.setProp (toJSString name) wrappedCb obj+            lift $ JSO.setProp name wrappedCb obj         addPropOrHandlerToObj obj (CallbackPropertyWithSingleArgument name func) = do             -- this will be released by the render function of the class (jsbits/class.js)             cb <- lift $ syncCallback1 ContinueAsync $ \ref ->                 runHandler $ func $ HandlerArg ref+            tell [jsval cb]+            lift $ JSO.setProp name (jsval cb) obj++        addPropOrHandlerToObj obj (CallbackPropertyReturningView name toProps v) = do+            (cb, wrappedCb) <- lift $ exportViewToJs v toProps             tell [cb]-            lift $ JSO.setProp (toJSString name) (jsval cb) obj+            lift $ JSO.setProp name wrappedCb obj          -- call React.createElement         createElement :: ReactElement eventHandler -> MkReactElementM [ReactElementRef]@@ -309,7 +331,7 @@                              [x] -> x                              xs -> jsval $ JSA.fromList xs             e <- lift $ case fName f of-                Left s -> js_ReactCreateElementName (toJSString s) obj children+                Left s -> js_ReactCreateElementName s obj children                 Right ref -> js_ReactCreateForeignElement ref obj children             return [e]         createElement (ViewElement { ceClass = rc, ceProps = props, ceKey = mkey, ceChild = child }) = do@@ -322,17 +344,15 @@                              xs -> jsval $ JSA.fromList xs              e <- lift $ case mkey of-                Just key -> do-                    keyRef <- toKeyRef key-                    js_ReactCreateKeyedElement rc keyRef propsE children+                Just keyRef -> js_ReactCreateKeyedElement rc keyRef propsE children                 Nothing -> js_ReactCreateClass rc propsE children             return [e] -type MkReactElementM a = WriterT [Callback (JSVal -> IO ())] IO a+type MkReactElementM a = WriterT [CallbackToRelease] IO a  foreign import javascript unsafe     "React['createElement']($1)"-    js_ReactCreateElementNoChildren :: JSString -> IO ReactElementRef+    js_ReactCreateElementNoChildren :: JSVal -> IO ReactElementRef  foreign import javascript unsafe     "React['createElement']($1, $2, $3)"@@ -354,16 +374,46 @@     "hsreact$mk_arguments_callback($1)"     js_CreateArgumentsCallback :: Callback (JSVal -> IO ()) -> IO JSVal -js_ReactCreateContent :: String -> ReactElementRef-js_ReactCreateContent = ReactElementRef . unsafeCoerce . toJSString+foreign import javascript unsafe+    "hsreact$wrap_callback_returning_element($1)"+    js_wrapCallbackReturningElement :: Callback (JSVal -> JSVal -> IO ()) -> IO JSVal +foreign import javascript unsafe+    "$1.elem = $2"+    js_setElemReturnFromCallback :: JSVal -> ReactElementRef -> IO ()++foreign import javascript unsafe+    "$r = hsreact$divLikeElement"+    js_divLikeElement :: JSVal++foreign import javascript unsafe+    "$r = hsreact$textWrapper"+    js_textWrapper :: JSVal++js_ReactCreateContent :: JSString -> ReactElementRef+js_ReactCreateContent = ReactElementRef . unsafeCoerce+ toJSString :: String -> JSString toJSString = JSS.pack ++exportViewToJs :: Typeable props => ReactViewRef props -> (JSArray -> IO props) -> IO (CallbackToRelease, JSVal)+exportViewToJs view toProps = do+    cb <- syncCallback2 ContinueAsync $ \ret argref -> do+        props <- toProps $ unsafeCoerce argref+        propsE <- export props -- this will be released inside the lifetime events for the class+        e <- js_ReactCreateClass view propsE jsNull+        js_setElemReturnFromCallback ret e+    wrappedCb <- js_wrapCallbackReturningElement cb+    return (jsval cb, wrappedCb)+ #else mkReactElement _ _ _ _ = return (ReactElementRef (), [])  toJSString :: String -> String toJSString = id++exportViewToJs :: Typeable props => ReactViewRef props -> (JSArray -> IO props) -> IO (CallbackToRelease, JSVal)+exportViewToJs _ _ = return ((), ())  #endif
src/React/Flux/Lifecycle.hs view
@@ -68,7 +68,7 @@ -- 'lRef'. data LDOM = LDOM   { lThis :: IO HTMLElement-  , lRef :: String -> IO HTMLElement+  , lRef :: JSString -> IO HTMLElement   }  -- | Set the state of the class.@@ -124,7 +124,7 @@     renderCb <- mkRenderCallback (js_ReactGetState >=> parseExport) runStateViewHandler render      let dom this = LDOM { lThis = js_ReactFindDOMNode this-                        , lRef = \r -> js_ReactGetRef this $ toJSString r+                        , lRef = \r -> js_ReactGetRef this r                         }          setStateFn this s = export s >>= js_ReactUpdateAndReleaseState this
src/React/Flux/PropertiesAndEvents.hs view
@@ -11,10 +11,15 @@   , nestedProperty   , CallbackFunction   , callback+  , callbackView+  , ArgumentsToProps+  , ReturnProps(..)+  , callbackViewWithProps    -- ** Combinators   , (@=)   , ($=)+  , (&=)   , classNames    -- * Creating Events@@ -89,25 +94,27 @@ import           Control.Concurrent.MVar (newMVar) import           Control.DeepSeq import           System.IO.Unsafe (unsafePerformIO)+import           Data.Typeable (Typeable)+import           Data.Monoid ((<>)) import qualified Data.Text as T import qualified Data.Aeson as A import qualified Data.HashMap.Strict as M  import           React.Flux.Internal import           React.Flux.Store-import           React.Flux.Views (ViewEventHandler, StatefulViewEventHandler)+import           React.Flux.Views (ReactView(..), ViewEventHandler, StatefulViewEventHandler, ArgumentsToProps(..), ReturnProps(..))  #ifdef __GHCJS__ import           Data.Maybe (fromMaybe)  import           GHCJS.Foreign (fromJSBool) import           GHCJS.Marshal (FromJSVal(..))-import           GHCJS.Types (JSVal, nullRef, JSString, IsJSVal)+import           GHCJS.Types (JSVal, nullRef, IsJSVal) import           JavaScript.Array as JSA+import qualified Data.JSString.Text as JSS  #else type JSVal = ()-type JSString = String type JSArray = () class FromJSVal a class IsJSVal a@@ -118,7 +125,7 @@ -- | Some third-party React classes allow passing React elements as properties.  This function -- will first run the given 'ReactElementM' to obtain an element or elements, and then use that -- element as the value for a property with the given key.-elementProperty :: String -> ReactElementM handler () -> PropertyOrHandler handler+elementProperty :: JSString -> ReactElementM handler () -> PropertyOrHandler handler elementProperty = ElementProperty  -- | Allows you to create nested object properties.  The list of properties passed in will be@@ -132,7 +139,7 @@ -- would create a javascript object -- -- >{"Hello": {a: 100, b: "World"}, "c": "!!!"}-nestedProperty :: String -> [PropertyOrHandler handler] -> PropertyOrHandler handler+nestedProperty :: JSString -> [PropertyOrHandler handler] -> PropertyOrHandler handler nestedProperty = NestedProperty  -- | A class which is used to implement <https://wiki.haskell.org/Varargs variable argument functions>.@@ -176,21 +183,71 @@ -- >baz :: ViewEventHandler -- -- For another example, see the haddock comments in "React.Flux.Addons.Bootstrap".-callback :: CallbackFunction handler func => String -> func -> PropertyOrHandler handler+callback :: CallbackFunction handler func => JSString -> func -> PropertyOrHandler handler callback name func = CallbackPropertyWithArgumentArray name $ \arr -> applyFromArguments arr 0 func +-- | Create a zero-argument callback property.  When this callback function is executed, it+-- will render the given view and return the resulting React element.  If you need to +-- create a callback which expects arguments, use 'callbackViewWithProps' instead.+callbackView :: JSString -> ReactView () -> PropertyOrHandler handler+callbackView name v = CallbackPropertyReturningView name (const $ return ()) (reactView v)++-- | Create a callback that when called will render a view.  This is useful for interacting with third-party React classes that expect+-- a property which is a function which when called returns a React element.   The way this works is+-- as follows:+--+-- 1. You create a Haskell function which translates the javascript arguments of the callback into a Haskell+-- value of type @ReturnProps props@.  This is a variable-argument function using the 'ArgumentsToProps' class.+-- For example,+--+--       @+--       data MyProps = MyProps { theInt :: Int, theString :: String }+--       myArgsToProps :: Int -> String -> ReturnProps MyProps+--       myArgsToProps i s = ReturnProps $ MyProps i s+--       @+--+-- 2. You create a view which receives these properties and renders itself.  This view will not+-- receive any children.+--+--       @+--       myView :: ReactView MyProps+--       mYView = defineView "my view" $ \\myProps -> ...+--       @+--+-- 3. You can then use 'callbackViewWithProps' to create a property which is a JavaScript function.+-- When this JavaScript function is executed, the JavaScript arguments are converted to the props,+-- the view is rendered using the props, and the resulting React element is returned from the+-- JavaScript function.+--+--       @+--       someOtherView :: ReactView ()+--       someOtherView = defineView "some other view" $ \\() ->+--           div_ $+--              foreignClass_ "theForeginThing"+--                  [ callbackViewWithProps "the_propname_to_pass_to_theForeignThing" myView myArgsToProps+--                  , "hello" $= "world"+--                  ] mempty+--      @+--+--      @theForeignThing@ React class will receive a property called+--      @the_propname_to_pass_to_theForeignThing@.  The value of this property is a JavaScript+--      function which when executed will convert the arguments to @props@, render the view, and+--      return the resulting React element.+callbackViewWithProps :: (Typeable props, ArgumentsToProps props func) => JSString -> ReactView props -> func -> PropertyOrHandler handler+callbackViewWithProps name v func = CallbackPropertyReturningView name (\arr -> returnViewFromArguments arr 0 func) (reactView v)+ ---------------------------------------------------------------------------------------------------- --- Combinators ---------------------------------------------------------------------------------------------------- --- | Create a property.-(@=) :: A.ToJSON a => T.Text -> a -> PropertyOrHandler handler-n @= a = Property (T.unpack n) (A.toJSON a)+-- | Create a property from any aeson value (the at sign looks like "A" for aeson)+(@=) :: A.ToJSON a => JSString -> a -> PropertyOrHandler handler+n @= a = Property n (A.toJSON a)  -- | Create a text-valued property.  This is here to avoid problems when OverloadedStrings extension -- is enabled-($=) :: T.Text -> T.Text -> PropertyOrHandler handler-n $= a = Property (T.unpack n) a+($=) :: JSString -> JSString -> PropertyOrHandler handler+n $= a = Property n a  -- | Set the <https://facebook.github.io/react/docs/class-name-manipulation.html className> property to consist -- of all the names which are matched with True, allowing you to easily toggle class names based on@@ -213,12 +270,12 @@     show _ = "EventTarget"  -- | Access a property in an event target-eventTargetProp :: FromJSVal val => EventTarget -> String -> val-eventTargetProp (EventTarget ref) key = ref .: toJSString key+eventTargetProp :: FromJSVal val => EventTarget -> JSString -> val+eventTargetProp (EventTarget ref) key = ref .: key  -- | Every event in React is a synthetic event, a cross-browser wrapper around the native event. data Event = Event-    { evtType :: String+    { evtType :: T.Text     , evtBubbles :: Bool     , evtCancelable :: Bool     , evtCurrentTarget :: EventTarget@@ -240,7 +297,7 @@ -- >           ] -- -- In this case, @val@ would coorespond to the javascript expression @evt.target.value@.-target :: FromJSVal val => Event -> String -> val+target :: FromJSVal val => Event -> JSString -> val target e s = eventTargetProp (evtTarget e) s  parseEvent :: HandlerArg -> Event@@ -257,16 +314,17 @@     , evtHandlerArg = arg     } --- | Use this to create an event handler for an event not covered by the rest of this module.  At--- the moment, this is just the media events (onPlay, onPause, etc.) on image and video tags.-on :: String -> (Event -> handler) -> PropertyOrHandler handler+-- | Use this to create an event handler for an event not covered by the rest of this module.+-- (Events are not covered if they don't have extra arguments that require special handling.)+-- For example, onPlay and onPause are events you could use with @on@.+on :: JSString -> (Event -> handler) -> PropertyOrHandler handler on name f = CallbackPropertyWithSingleArgument     { csPropertyName = name     , csFunc = f . parseEvent     }  -- | Construct a handler from a detail parser, used by the various events below.-on2 :: String -- ^ The event name+on2 :: JSString -- ^ The event name     -> (HandlerArg -> detail) -- ^ A function parsing the details for the specific event.     -> (Event -> detail -> handler) -- ^ The function implementing the handler.     -> PropertyOrHandler handler@@ -334,7 +392,7 @@ -- | By default, the handlers below are triggered during the bubbling phase.  Use this to switch -- them to trigger during the capture phase. capturePhase :: PropertyOrHandler handler -> PropertyOrHandler handler-capturePhase (CallbackPropertyWithSingleArgument n h) = CallbackPropertyWithSingleArgument (n ++ "Capture") h+capturePhase (CallbackPropertyWithSingleArgument n h) = CallbackPropertyWithSingleArgument (n <> "Capture") h capturePhase _ = error "You must use React.Flux.PropertiesAndEvents.capturePhase on an event handler"  ---------------------------------------------------------------------------------------------------@@ -351,10 +409,10 @@   { keyEvtAltKey :: Bool   , keyEvtCharCode :: Int   , keyEvtCtrlKey :: Bool-  , keyGetModifierState :: String -> Bool-  , keyKey :: String+  , keyGetModifierState :: T.Text -> Bool+  , keyKey :: T.Text   , keyCode :: Int-  , keyLocale :: String+  , keyLocale :: Maybe T.Text   , keyLocation :: Int   , keyMetaKey :: Bool   , keyRepeat :: Bool@@ -434,7 +492,7 @@   , mouseClientX :: Int   , mouseClientY :: Int   , mouseCtrlKey :: Bool-  , mouseGetModifierState :: String -> Bool+  , mouseGetModifierState :: T.Text -> Bool   , mouseMetaKey :: Bool   , mousePageX :: Int   , mousePageY :: Int@@ -549,7 +607,7 @@     touchAltKey :: Bool   , changedTouches :: [Touch]   , touchCtrlKey :: Bool-  , touchGetModifierState :: String -> Bool+  , touchGetModifierState :: T.Text -> Bool   , touchMetaKey :: Bool   , touchShiftKey :: Bool   , touchTargets :: [Touch]@@ -669,8 +727,8 @@     "$1['getModifierState']($2)"     js_GetModifierState :: JSVal -> JSString -> JSVal -getModifierState :: JSVal -> String -> Bool-getModifierState ref = fromJSBool . js_GetModifierState ref . toJSString+getModifierState :: JSVal -> T.Text -> Bool+getModifierState ref = fromJSBool . js_GetModifierState ref . JSS.textToJSString  arrayLength :: JSArray -> Int arrayLength = JSA.length@@ -680,16 +738,16 @@  #else -js_getProp :: a -> String -> JSVal+js_getProp :: a -> JSString -> JSVal js_getProp _ _ = () -js_getArrayProp :: a -> String -> JSVal+js_getArrayProp :: a -> JSString -> JSVal js_getArrayProp _ _ = () -(.:) :: JSVal -> String -> b+(.:) :: JSVal -> JSString -> b _ .: _ = undefined -getModifierState :: JSVal -> String -> Bool+getModifierState :: JSVal -> T.Text -> Bool getModifierState _ _ = False  arrayLength :: JSArray -> Int
src/React/Flux/Store.hs view
@@ -39,7 +39,7 @@ -- plus node can also be used for unit testing. -- -- >data Todo = Todo {--- >    todoText :: String+-- >    todoText :: Text -- >  , todoComplete :: Bool -- >  , todoIsEditing :: Bool -- >} deriving (Show, Typeable)@@ -48,10 +48,10 @@ -- >    todoList :: [(Int, Todo)] -- >} deriving (Show, Typeable) -- >--- >data TodoAction = TodoCreate String+-- >data TodoAction = TodoCreate Text -- >                | TodoDelete Int -- >                | TodoEdit Int--- >                | UpdateText Int String+-- >                | UpdateText Int Text -- >                | ToggleAllComplete -- >                | TodoSetComplete Int Bool -- >                | ClearCompletedTodos
src/React/Flux/Views.hs view
@@ -1,4 +1,5 @@ -- | Internal module containing the view definitions+{-# LANGUAGE UndecidableInstances #-} module React.Flux.Views where  import Control.Monad.Writer@@ -13,11 +14,17 @@ import React.Flux.Export import JavaScript.Array import GHCJS.Foreign.Callback-import GHCJS.Types (JSVal, JSString, IsJSVal, jsval)-import GHCJS.Marshal (ToJSVal(..))+import GHCJS.Types (JSVal, IsJSVal, nullRef)+import GHCJS.Marshal (ToJSVal(..), FromJSVal(..))+import GHCJS.Marshal.Pure (PToJSVal(..))+import qualified JavaScript.Array as JSA  #else type JSVal = ()+type JSArray = ()+class FromJSVal a+pToJSVal :: a -> JSVal+pToJSVal _ = () #endif  @@ -65,7 +72,7 @@ -- >        mainSection_ todoState -- >        todoFooter_ todoState defineControllerView :: (StoreData storeData, Typeable props)-                 => String -- ^ A name for this view, used only for debugging/console logging+                 => JSString -- ^ A name for this view, used only for debugging/console logging                  -> ReactStore storeData -- ^ The store this controller view should attach to.                  -> (storeData -> props -> ReactElementM ViewEventHandler ()) -- ^ The rendering function                  -> ReactView props@@ -75,7 +82,7 @@ defineControllerView name (ReactStore store _) buildNode = unsafePerformIO $ do     let render sd props = return $ buildNode sd props     renderCb <- mkRenderCallback (js_ReactGetState >=> parseExport) runViewHandler render-    ReactView <$> js_createControllerView (toJSString name) store renderCb+    ReactView <$> js_createControllerView name store renderCb  -- | Transform a controller view handler to a raw handler. runViewHandler :: ReactThis state props -> ViewEventHandler -> IO ()@@ -99,7 +106,7 @@ -- One option to implement views is to just use a Haskell function taking the @props@ as input and -- producing a 'ReactElementM'.  For small views, such a Haskell function is ideal. -- Using a 'ReactView' provides more than just a Haskell function when used with a key property with--- 'viewWithKey'.  The key property allows React to more easily reconcile the virtual DOM with the+-- 'viewWithSKey' and 'viewWithIKey'.  The key property allows React to more easily reconcile the virtual DOM with the -- browser DOM. -- -- The following is two example views: @mainSection_@ is just a Haskell function and @todoItem@@@ -147,9 +154,9 @@ -- >                } -- > -- >todoItem_ :: (Int, Todo) -> ReactElementM eventHandler ()--- >todoItem_ todo = viewWithKey todoItem (fst todo) todo mempty+-- >todoItem_ !todo = viewWithIKey todoItem (fst todo) todo mempty defineView :: Typeable props-       => String -- ^ A name for this view, used only for debugging/console logging+       => JSString -- ^ A name for this view, used only for debugging/console logging        -> (props -> ReactElementM ViewEventHandler ()) -- ^ The rendering function        -> ReactView props @@ -158,7 +165,7 @@ defineView name buildNode = unsafePerformIO $ do     let render () props = return $ buildNode props     renderCb <- mkRenderCallback (const $ return ()) runViewHandler render-    ReactView <$> js_createView (toJSString name) renderCb+    ReactView <$> js_createView name renderCb  #else @@ -194,37 +201,37 @@ -- new state.  Any more complicated state should be moved out into a (possibly new) store. -- -- >data TextInputArgs = TextInputArgs {--- >      tiaId :: Maybe String--- >    , tiaClass :: String--- >    , tiaPlaceholder :: String--- >    , tiaOnSave :: String -> [SomeStoreAction]--- >    , tiaValue :: Maybe String+-- >      tiaId :: Maybe JSString+-- >    , tiaClass :: JSString+-- >    , tiaPlaceholder :: JSString+-- >    , tiaOnSave :: Text -> [SomeStoreAction]+-- >    , tiaValue :: Maybe Text -- >} deriving (Typeable) -- > -- >todoTextInput :: ReactView TextInputArgs -- >todoTextInput = defineStatefulView "todo text input" "" $ \curText args -> -- >    input_ $--- >        maybe [] (\i -> ["id" @= i]) (tiaId args)+-- >        maybe [] (\i -> ["id" &= i]) (tiaId args) -- >        ++--- >        [ "className" @= tiaClass args--- >        , "placeholder" @= tiaPlaceholder args--- >        , "value" @= curText--- >        , "autoFocus" @= True+-- >        [ "className" &= tiaClass args+-- >        , "placeholder" &= tiaPlaceholder args+-- >        , "value" &= curText+-- >        , "autoFocus" &= True -- >        , onChange $ \evt _ -> ([], Just $ target evt "value") -- >        , onBlur $ \_ _ curState ->--- >             if not (null curState)+-- >             if not (Text.null curState) -- >                 then (tiaOnSave args curState, Just "") -- >                 else ([], Nothing) -- >        , onKeyDown $ \_ evt curState ->--- >             if keyCode evt == 13 && not (null curState) -- 13 is enter+-- >             if keyCode evt == 13 && not (Text.null curState) -- 13 is enter -- >                 then (tiaOnSave args curState, Just "") -- >                 else ([], Nothing) -- >        ] -- > -- >todoTextInput_ :: TextInputArgs -> ReactElementM eventHandler ()--- >todoTextInput_ args = view todoTextInput args mempty+-- >todoTextInput_ !args = view todoTextInput args mempty defineStatefulView :: (Typeable state, NFData state, Typeable props)-               => String -- ^ A name for this view, used only for debugging/console logging+               => JSString -- ^ A name for this view, used only for debugging/console logging                -> state -- ^ The initial state                -> (state -> props -> ReactElementM (StatefulViewEventHandler state) ()) -- ^ The rendering function                -> ReactView props@@ -235,7 +242,7 @@     initialRef <- export initial     let render state props = return $ buildNode state props     renderCb <- mkRenderCallback (js_ReactGetState >=> parseExport) runStateViewHandler render-    ReactView <$> js_createStatefulView (toJSString name) initialRef renderCb+    ReactView <$> js_createStatefulView name initialRef renderCb  -- | Transform a stateful view event handler to a raw event handler runStateViewHandler :: (Typeable state, NFData state)@@ -360,7 +367,7 @@      (element, evtCallbacks) <- mkReactElement (runHandler this) getContext getPropsChildren node -    evtCallbacksRef <- toJSVal $ map jsval evtCallbacks+    evtCallbacksRef <- toJSVal evtCallbacks     js_RenderCbSetResults arg evtCallbacksRef element  parseExport :: Typeable a => Export a -> IO a@@ -385,12 +392,27 @@      -> ReactElementM eventHandler a view rc props (ReactElementM child) =     let (a, childEl) = runWriter child-     in elementToM a $ ViewElement (reactView rc) (Nothing :: Maybe Int) props childEl+     in elementToM a $ ViewElement (reactView rc) Nothing props childEl --- | Create an element from a view, and also pass in a key property for the instance.  Key--- properties speed up the <https://facebook.github.io/react/docs/reconciliation.html reconciliation>--- of the virtual DOM with the DOM.  The key does not need to be globally unqiue, it only needs to--- be unique within the siblings of an element.+-- | Keys in React can either be strings or integers+class ReactViewKey key where+    toKeyRef :: key -> JSVal+#if __GHCJS__+instance ReactViewKey String where+    toKeyRef = pToJSVal++instance ReactViewKey Int where+    toKeyRef = pToJSVal+#else+instance ReactViewKey String where+    toKeyRef = const ()++instance ReactViewKey Int where+    toKeyRef = const ()+#endif++-- | A deprecated way to create a view with a key which has problems when OverloadedStrings is+-- active.  Use 'viewWithSKey' or 'viewWithIKey' instead. viewWithKey :: (Typeable props, ReactViewKey key)             => ReactView props -- ^ the view             -> key -- ^ A value unique within the siblings of this element@@ -399,8 +421,33 @@             -> ReactElementM eventHandler a viewWithKey rc key props (ReactElementM child) =     let (a, childEl) = runWriter child-     in elementToM a $ ViewElement (reactView rc) (Just key) props childEl+     in elementToM a $ ViewElement (reactView rc) (Just $ toKeyRef key) props childEl +-- | Create an element from a view, and also pass in a string key property for the instance.  Key+-- properties speed up the <https://facebook.github.io/react/docs/reconciliation.html reconciliation>+-- of the virtual DOM with the DOM.  The key does not need to be globally unqiue, it only needs to+-- be unique within the siblings of an element.+viewWithSKey :: Typeable props+             => ReactView props -- ^ the view+             -> JSString -- ^ The key, a value unique within the siblings of this element+             -> props -- ^ The properties to pass to the view instance+             -> ReactElementM eventHandler a -- ^ The children of the view+             -> ReactElementM eventHandler a+viewWithSKey rc key props (ReactElementM child) =+    let (a, childEl) = runWriter child+     in elementToM a $ ViewElement (reactView rc) (Just $ pToJSVal key) props childEl++-- | Similar to 'viewWithSKey', but with an integer key instead of a string key.+viewWithIKey :: Typeable props+             => ReactView props -- ^ the view+             -> Int -- ^ The key, a value unique within the siblings of this element+             -> props -- ^ The properties to pass to the view instance+             -> ReactElementM eventHandler a -- ^ The children of the view+             -> ReactElementM eventHandler a+viewWithIKey rc key props (ReactElementM child) =+    let (a, childEl) = runWriter child+     in elementToM a $ ViewElement (reactView rc) (Just $ pToJSVal key) props childEl+ -- | Create a 'ReactElement' for a class defined in javascript.  See -- 'React.Flux.Combinators.foreign_' for a convenient wrapper and some examples. foreignClass :: JSVal -- ^ The javascript reference to the class@@ -417,3 +464,90 @@ #else foreignClass _ _ x = x #endif++-- | A class which is used to implement <https://wiki.haskell.org/Varargs variable argument functions>.+-- These variable argument functions are used to convert from a JavaScript+-- <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments arguments array>+-- to a Haskell value of type @props@.+--+-- Any function where each argument implements 'FromJSVal' and the result is 'ReturnProps' is an+-- instance of this class.  Entries from the JavaScript arguments array are matched one-by-one to+-- the arguments before 'ReturnProps' value.  If the Haskell function has more parameters than the+-- javascript @arguments@ object, a javascript null is used for the conversion.  Since the 'Maybe'+-- instance of 'FromJSVal' converts null references to 'Nothing', you can exploit this to handle+-- arguments not given to the JavaScript function.+class ArgumentsToProps props a | a -> props where+    returnViewFromArguments :: JSArray -> Int -> a -> IO props++-- | A type needed to make GHC happy when solving for instances of 'ArgumentsToProps'.+newtype ReturnProps props = ReturnProps props++instance ArgumentsToProps props (ReturnProps props) where+    returnViewFromArguments _ _ (ReturnProps v) = return v++instance (FromJSVal a, ArgumentsToProps props b) => ArgumentsToProps props (a -> b) where+#if __GHCJS__+    returnViewFromArguments args k f = do+        ma <- fromJSVal $ if k >= JSA.length args then nullRef else JSA.index k args+        a <- maybe (error "Unable to decode callback argument") return ma+        returnViewFromArguments args (k+1) $ f a+#else+    returnViewFromArguments _ _ _ = error "Not supported in GHC"+#endif++-- | Export a Haskell view to a JavaScript function.  This allows you to embed a Haskell react-flux+-- application into a larger existing JavaScript React application.  If you want to use JavaScript+-- classes in your Haskell application, you should instead use 'React.Flux.Combinators.foreign_' and 'foreignClass'.+--+-- The way this works is as follows:+--+-- 1. You create a Haskell function which translates the javascript arguments of into a Haskell+-- value of type @ReturnProps props@.  This is a variable-argument function using the 'ArgumentsToProps' class.+-- For example,+--+--       @+--       data MyProps = MyProps { theInt :: Int, theString :: String }+--       myArgsToProps :: Int -> String -> ReturnProps MyProps+--       myArgsToProps i s = ReturnProps $ MyProps i s+--       @+--+-- 2. You create a view which receives these properties and renders itself.  This view will not+-- receive any children.+--+--       @+--       myView :: ReactView MyProps+--       myView = defineView "my view" $ \\myProps -> ...+--       @+--+-- 3. You can then use 'exportViewToJavaScript' to create a JavaScript function.  When this+-- JavaScript function is executed, the JavaScript arguments are converted to the props,+-- the view is rendered using the props, and the resulting React element is returned from the+-- JavaScript function.+--+--       @+--       foreign import javascript unsafe+--           "window[\'myHaskellView\'] = $1;"+--           js_setMyView :: JSVal -> IO ()+--+--       exportMyView :: IO ()+--       exportMyView = exportViewToJavaScript myView myArgsToProps >>= js_setMyView+--       @+--+--       @exportMyView@ should be called from your main function.  After executing @exportMyView@,+--       the @window.myHaskellView@ property will be a javascript function.+--+-- 4. Call the javascript function with two arguments to return a React element which can be used+-- in a JavaScript React class rendering function.+-- +--       @+--       var myJsView = React.createClass({+--           render: function() {+--               return \<div\>{window.myHaskellView(5, "Hello World")}\</div\>;+--           }+--       };+--       @+exportViewToJavaScript :: (Typeable props, ArgumentsToProps props func) => ReactView props -> func -> IO JSVal+exportViewToJavaScript v func = do+    (_callbackToRelease, wrappedCb) <- exportViewToJs (reactView v) (\arr -> returnViewFromArguments arr 0 func)+    return wrappedCb+
test/client/TestClient.hs view
@@ -1,5 +1,6 @@-{-# LANGUAGE OverloadedStrings, TypeFamilies, ScopedTypeVariables, DeriveAnyClass, FlexibleInstances, DeriveGeneric #-}-module TestClient (testClient) where+{-# LANGUAGE OverloadedStrings, TypeFamilies, ScopedTypeVariables, DeriveAnyClass,+             FlexibleInstances, DeriveGeneric, BangPatterns, TemplateHaskell #-}+module Main (main) where  import Control.DeepSeq (NFData) import Control.Monad@@ -8,14 +9,16 @@ import Data.Maybe import Debug.Trace import GHC.Generics (Generic)+import Data.Time (UTCTime(..), fromGregorian) import React.Flux import React.Flux.Lifecycle-import React.Flux.Internal (toJSString) import React.Flux.Addons.React-import React.Flux.Addons.Bootstrap+import React.Flux.Addons.Intl+import qualified Data.Text as T  import GHCJS.Types (JSVal, JSString) import GHCJS.Marshal (fromJSVal)+import qualified Data.JSString.Text as JSS  foreign import javascript unsafe     "(function(x) { \@@ -28,31 +31,34 @@     deriving (Show, Typeable)  instance StoreData OutputStoreData where-    type StoreAction OutputStoreData = [String]+    type StoreAction OutputStoreData = [T.Text]     -- log both to the console and to js_output     transform ss OutputStoreData = do-        mapM_ (js_output . toJSString) ss-        trace (unlines ss) $ return OutputStoreData+        mapM_ (js_output . JSS.textToJSString) ss+        trace (unlines $ map T.unpack ss) $ return OutputStoreData  outputStore :: ReactStore OutputStoreData outputStore = mkStore OutputStoreData -output :: [String] -> [SomeStoreAction]+output :: [T.Text] -> [SomeStoreAction] output s = [SomeStoreAction outputStore s] -outputIO :: [String] -> IO ()+outputIO :: [T.Text] -> IO () outputIO ss = void $ transform ss OutputStoreData  -------------------------------------------------------------------------------- --- Events -------------------------------------------------------------------------------- -logM :: (String -> Bool) -> String-logM f = "alt modifier: " ++ show (f "Alt")+logM :: (T.Text -> Bool) -> T.Text+logM f = "alt modifier: " <> (T.pack $ show (f "Alt")) -logT :: EventTarget -> String+logT :: EventTarget -> T.Text logT t = eventTargetProp t "id" +tshow :: Show a => a -> T.Text+tshow = T.pack . show+ rawShowView :: ReactView Int rawShowView = defineView "raw show view" elemShow @@ -64,15 +70,15 @@                     , "placeholder" $= "onKeyDown"                     , onKeyDown $ \e k -> output                         [ "keydown"-                        , show e-                        , show k+                        , tshow e+                        , tshow k                         , logM (keyGetModifierState k)                         , logT (evtTarget e)                         , logT (evtCurrentTarget e)                         ]                     , onFocus $ \e _ -> output                         [ "focus"-                        , show e+                        , tshow e                         --, logT $ focusRelatedTarget f                         ]                     ]@@ -80,8 +86,8 @@         p_ $ label_ [ "id" $= "clickinput"                     , onClick $ \e m -> output                         [ "click"-                        , show e-                        , show m +                        , tshow e+                        , tshow m                          , logM (mouseGetModifierState m)                         --, logT (mouseRelatedTarget m)                         ]@@ -91,8 +97,8 @@         p_ $ label_ [ "id" $= "touchinput"                     , onTouchStart $ \e t -> output                         [ "touchstart"-                        , show e-                        , show t+                        , tshow e+                        , tshow t                         , logM (touchGetModifierState t)                         , logT (touchTarget $ head $ touchTargets t)                         , "endtouch"@@ -127,11 +133,11 @@ --- Lifecycle -------------------------------------------------------------------------------- -logPandS :: LPropsAndState String Int -> IO ()+logPandS :: LPropsAndState T.Text Int -> IO () logPandS ps = do     p <- lGetProps ps     st <- lGetState ps-    outputIO ["Current props and state: " ++ p ++ ", " ++ show st]+    outputIO ["Current props and state: " <> p <> ", " <> (T.pack $ show st)]  foreign import javascript unsafe     "$1.id"@@ -142,18 +148,18 @@     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+    outputIO [ "this id = " <> fromMaybe "Nothing" this+             , "refStr id = " <> fromMaybe "Nothing" x+             , "refProps id = " <> fromMaybe "Nothing" y              ]  -testLifecycle :: ReactView String+testLifecycle :: ReactView T.Text 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+        span_ ["ref" $= "refProps", "id" $= "world"] $ elemText $ "Current props: " <> p         button_ [ "id" $= "increment-state"                 , onClick $ \_ _ st -> ([], Just $ st + 1)                 ] "Incr"@@ -171,24 +177,24 @@     , lComponentWillReceiveProps = Just $ \pAndS _dom _setStateFn newProps -> do         outputIO ["will recv props"]         logPandS pAndS-        outputIO ["New props: " ++ newProps]+        outputIO ["New props: " <> newProps]      , lComponentWillUpdate = Just $ \pAndS _dom newProps newState -> do         outputIO ["will update"]         logPandS pAndS-        outputIO ["New props: " ++ newProps, "New state: " ++ show newState]+        outputIO ["New props: " <> newProps, "New state: " <> tshow newState]      , lComponentDidUpdate = Just $ \pAndS _dom _setStateFn oldProps oldState -> do         outputIO ["did update"]         logPandS pAndS-        outputIO ["Old props: " ++ oldProps, "Old state: " ++ show oldState]+        outputIO ["Old props: " <> oldProps, "Old state: " <> tshow oldState]      , lComponentWillUnmount = Just $ \pAndS _dom -> do         outputIO ["will unmount"]         logPandS pAndS     } -testLifecycle_ :: String -> ReactElementM eventHandler ()+testLifecycle_ :: T.Text -> ReactElementM eventHandler () testLifecycle_ s = view testLifecycle s $ span_ ["id" $= "child-passed-to-view"] "I am a child!!!"  --------------------------------------------------------------------------------@@ -213,7 +219,7 @@ --- CSS Transitions -------------------------------------------------------------------------------- -cssTransitions :: ReactView [String]+cssTransitions :: ReactView [T.Text] cssTransitions = defineView "css transitions" $ \items ->     div_ ["id" $= "css-transitions"] $         cssTransitionGroup ["transitionName" $= "example"] $@@ -221,77 +227,250 @@                 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+-- | The data in the store is four 'ShouldComponentUpdateData's.  The reason is we test+-- views with tuples of size up to 3, and so we want 4 entries in the store to be able to+-- test editing the store but not changing anything that is passed to a view, to test that+-- the shouldComponentUpdate function is working properly.+data ShouldComponentUpdate = ShouldComponentUpdate {+    scu1 :: !ShouldComponentUpdateData -- ^ passed to all three views+  , scu2 :: !ShouldComponentUpdateData -- ^ only passed to the pair view+  , scu3 :: !ShouldComponentUpdateData -- ^ only passed to the triple view+  , scu4 :: !ShouldComponentUpdateData -- ^ not passed to any view+} deriving (Typeable, Show)++data SCUIndex = SCU1 | SCU2 | SCU3 | SCU4+    deriving (Show, Eq, Typeable, Generic, NFData, Bounded, Enum)++data ShouldComponentUpdateAction = IncrementAllSCUData SCUIndex+                                 | IncrementFirstSCUData SCUIndex                                  | NoChangeToSCUData-    deriving (Show, Typeable, Generic, NFData)+    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"+toggleSCU :: ShouldComponentUpdateData -> ShouldComponentUpdateData+toggleSCU (ShouldComponentUpdateData i s) = ShouldComponentUpdateData (i+1) s -shouldComponentUpdateStore :: ReactStore [ShouldComponentUpdateData]-shouldComponentUpdateStore = mkStore [ ShouldComponentUpdateData 1 "Hello"-                                     , ShouldComponentUpdateData 2 "World"-                                     , ShouldComponentUpdateData 3 "!!!"-                                     ]+instance StoreData [ShouldComponentUpdate] where+    type StoreAction [ShouldComponentUpdate] = ShouldComponentUpdateAction+    transform _ [] = error "Will never happen"+    transform action ds@(first:rest) =+        pure $ case action of +            NoChangeToSCUData -> ds+            (IncrementAllSCUData SCU1) -> [ShouldComponentUpdate (toggleSCU s1) s2 s3 s4 | ShouldComponentUpdate s1 s2 s3 s4 <- ds]+            (IncrementAllSCUData SCU2) -> [ShouldComponentUpdate s1 (toggleSCU s2) s3 s4  | ShouldComponentUpdate s1 s2 s3 s4 <- ds]+            (IncrementAllSCUData SCU3) -> [ShouldComponentUpdate s1 s2 (toggleSCU s3) s4 | ShouldComponentUpdate s1 s2 s3 s4 <- ds]+            (IncrementAllSCUData SCU4) -> [ShouldComponentUpdate s1 s2 s3 (toggleSCU s4) | ShouldComponentUpdate s1 s2 s3 s4 <- ds]+            (IncrementFirstSCUData SCU1) -> first { scu1 = toggleSCU $ scu1 first } : rest+            (IncrementFirstSCUData SCU2) -> first { scu2 = toggleSCU $ scu2 first } : rest+            (IncrementFirstSCUData SCU3) -> first { scu3 = toggleSCU $ scu3 first } : rest+            (IncrementFirstSCUData SCU4) -> first { scu4 = toggleSCU $ scu4 first } : rest +shouldComponentUpdateStore :: ReactStore [ShouldComponentUpdate]+shouldComponentUpdateStore = mkStore+        [ ShouldComponentUpdate (mkS 1 "Quick Ben") (mkS 2 "Whiskeyjack") (mkS 3 "Fiddler") (mkS 4 "Kellanved")+        , ShouldComponentUpdate (mkS 5 "Karsa") (mkS 6 "Tehol") (mkS 7 "Tayschrenn") (mkS 8 "Kruppe")+        , ShouldComponentUpdate (mkS 9 "Anomander Rake") (mkS 10 "Iskaral Pust") (mkS 11 "Dujek") (mkS 12 "Tavore")+        ]+    where+        mkS = ShouldComponentUpdateData+ -- | This will log wheenver componentWillUpdate lifecycle event occurs logComponentWillUpdate :: ReactView ShouldComponentUpdateData-logComponentWillUpdate = defineLifecycleView "shouldComponentUpdate spec" () lifecycleConfig+logComponentWillUpdate = defineLifecycleView "shouldComponentUpdate single spec" () lifecycleConfig     { lRender = \() (ShouldComponentUpdateData i s) ->-                    span_ (elemShow i) <> span_ (elemText s)+                    span_ (elemShow i) <> span_ (elemString 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+          outputIO [ "Component will update single"+                   , "current props: " <> tshow curI <> " " <> T.pack curS+                   , "new props: " <> tshow newI <> " " <> T.pack newS                    ]     } +logComp1_ :: ShouldComponentUpdateData -> Int -> ReactElementM handler ()+logComp1_ !sc i = viewWithKey logComponentWillUpdate i sc mempty++logComponentWillUpdatePair :: ReactView (ShouldComponentUpdateData, ShouldComponentUpdateData)+logComponentWillUpdatePair = defineLifecycleView "shouldComponentUpdate pair spec" () lifecycleConfig+    { lRender = \() (ShouldComponentUpdateData i1 s1, ShouldComponentUpdateData i2 s2) ->+                    span_ (elemShow i1) <> span_ (elemString s1) <> span_ (elemShow i2) <> span_ (elemString s2)+    , lComponentWillUpdate = Just $ \curProps _ (ShouldComponentUpdateData newI1 newS1, ShouldComponentUpdateData newI2 newS2) () -> do+          (ShouldComponentUpdateData curI1 curS1, ShouldComponentUpdateData curI2 curS2) <- lGetProps curProps+          outputIO [ "Component will update for pair input view"+                   , T.pack $ "current props: " ++ show curI1 ++ " " ++ curS1 ++ " " ++ show curI2 ++ " " ++ curS2+                   , T.pack $ "new props: " ++ show newI1 ++ " " ++ newS1 ++ " " ++ show newI2 ++ " " ++ newS2+                   ]+    }++logComp2_ :: ShouldComponentUpdateData -> ShouldComponentUpdateData -> Int -> ReactElementM handler ()+logComp2_ !sc1 !sc2 i = viewWithKey logComponentWillUpdatePair i (sc1, sc2) mempty++logComponentWillUpdateTriple :: ReactView (ShouldComponentUpdateData, ShouldComponentUpdateData, ShouldComponentUpdateData)+logComponentWillUpdateTriple = defineLifecycleView "shouldComponentUpdate triple spec" () lifecycleConfig+    { lRender = \() (ShouldComponentUpdateData i1 s1, ShouldComponentUpdateData i2 s2, ShouldComponentUpdateData i3 s3) ->+                  span_ (elemShow i1) <> span_ (elemString s1) <> span_ (elemShow i2) <> span_ (elemString s2) <> span_ (elemShow i3) <> span_ (elemString s3)+    , lComponentWillUpdate = Just $ \curProps _+        (ShouldComponentUpdateData newI1 newS1, ShouldComponentUpdateData newI2 newS2, ShouldComponentUpdateData newI3 newS3) () -> do+            (ShouldComponentUpdateData curI1 curS1, ShouldComponentUpdateData curI2 curS2, ShouldComponentUpdateData curI3 curS3) <- lGetProps curProps+            outputIO [ "Component will update for triple input view"+                     , T.pack $ "current props: " ++ show curI1 ++ " " ++ curS1 ++ " " ++ show curI2 ++ " " ++ curS2 ++ " " ++ show curI3 ++ " " ++ curS3+                     , T.pack $ "new props: " ++ show newI1 ++ " " ++ newS1 ++ " " ++ show newI2 ++ " " ++ newS2 ++ " " ++ show newI3 ++ " " ++ newS3+                     ]+    }++logComp3_ :: ShouldComponentUpdateData -> ShouldComponentUpdateData -> ShouldComponentUpdateData -> Int -> ReactElementM handler ()+logComp3_ !sc1 !sc2 !sc3 i = viewWithKey logComponentWillUpdateTriple i (sc1, sc2, sc3) mempty+ 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+        ul_ ["id" $= "should-component-update-single"] $ forM_ (zip ds [(0 :: Int)..]) $ \(d,i)  ->+            li_ $ logComp1_ (scu1 d) i+        ul_ ["id" $= "should-component-update-pair"]  $ forM_ (zip ds [(0 :: Int)..]) $ \(d,i)  ->+            li_ $ logComp2_ (scu1 d) (scu2 d) i+        ul_ ["id" $= "should-component-update-triple"]  $ forM_ (zip ds [(0 :: Int)..]) $ \(d, i)  ->+            li_ $ logComp3_ (scu1 d) (scu2 d) (scu3 d) i          button_ ["id" $= "no-change-scu", onClick $ \_ _ -> [SomeStoreAction shouldComponentUpdateStore NoChangeToSCUData]]             "No change to data" -        button_ ["id" $= "change-all-scu", onClick $ \_ _ -> [SomeStoreAction shouldComponentUpdateStore IncrementAllSCUData]]-            "Increment all"+        forM_ [minBound..maxBound] $ \idx -> do+            button_ ["id" @= ("change-all-scu-" ++ show idx), onClick $ \_ _ -> [SomeStoreAction shouldComponentUpdateStore $ IncrementAllSCUData idx]] $+                elemString $ "Increment all " ++ show idx+            button_ ["id" @=("increment-first-scu" ++ show idx), onClick $ \_ _ -> [SomeStoreAction shouldComponentUpdateStore $ IncrementFirstSCUData idx]] $+                elemString $ "Increment first entry's integer" ++ show idx -        button_ ["id" $= "increment-first-scu", onClick $ \_ _ -> [SomeStoreAction shouldComponentUpdateStore IncrementFirstSCUData]]-            "Increment first entry's integer"+--------------------------------------------------------------------------------+--- Callback returning view+-------------------------------------------------------------------------------- +data CallbackViewProps = CallbackViewProps Int String+    deriving (Show, Typeable)++callbackArgsToProps :: Int -> String -> ReturnProps CallbackViewProps+callbackArgsToProps i s = ReturnProps $ CallbackViewProps i s++callbackViewTest :: ReactView CallbackViewProps+callbackViewTest = defineView "callback view props test" $ \(CallbackViewProps i s) ->+    p_ [ "id" $= "callback-view-props-test"] $+        elemString $ "Props are " ++ show i ++ " and " ++ s++foreign import javascript unsafe+    "React['createClass']({'displayName':'callback wrapper', 'render': function() { \+    \ return React['createElement']('div', {}, [React.createElement('p', {}, 'From Callback'), this.props.foo(5, 'Hello World')]); \+    \ }})"+    js_createWrapperClass :: JSVal++callbackViewWrapper :: ReactView ()+callbackViewWrapper = defineView "callback view wrapper" $ \() ->+    div_ ["id" $= "callback-view-wrapper"] $+        foreignClass js_createWrapperClass [ callbackViewWithProps "foo" callbackViewTest callbackArgsToProps ] mempty+ --------------------------------------------------------------------------------+--- Intl+--------------------------------------------------------------------------------++foreign import javascript unsafe+    "{'with_trans': 'message from translation {abc}'}"+    js_translations :: JSVal++intlSpec :: ReactView ()+intlSpec = defineView "intl" $ \() ->+    intlProvider_ "en" (Just js_translations) Nothing $+        view intlSpecBody () mempty++intlSpecBody :: ReactView ()+intlSpecBody = defineView "intl body" $ \() -> div_ ["id" $= "intl-spec"] $ +    ul_ $ do+        li_ ["id" $= "f-number"] $+            formattedNumber_ [ "value" @= (0.9 :: Double), "style" $= "percent" ]+        li_ ["id" $= "f-int"] $ int_ 100000+        li_ ["id" $= "f-double"] $ double_ 40000.2+        li_ ["id" $= "f-number-prop"] $+            input_ [formattedNumberProp "placeholder" (123456 :: Int) []]++        let moon = fromGregorian 1969 7 20+            fullDayF = DayFormat { weekdayF = Just "long", eraF = Just "short", yearF = Just "2-digit", monthF = Just "long", dayF = Just "2-digit" }++        li_ ["id" $= "f-shortday"] $ day_ shortDate moon+        li_ ["id" $= "f-fullday"] $ day_ fullDayF moon+        li_ ["id" $= "f-date"] $ formattedDate_ (Left moon)+                [ "weekday" $= "short", "month" $= "short", "day" $= "numeric", "year" $= "2-digit" ]+        li_ ["id" $= "f-date-prop"] $+            input_ [formattedDateProp "placeholder" (Left moon) []]++        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)+                [ "year" $= "2-digit", "month" $= "short", "day" $= "numeric"+                , "hour" $= "numeric", "minute" $= "2-digit", "second" $= "numeric"+                , "timeZoneName" $= "short"+                , "timeZone" $= "Pacific/Tahiti"+                ]+        li_ ["id" $= "f-time-prop"] $+            input_ [formattedDateProp "placeholder" (Right step)+                    [ "year" `iprop` ("2-digit" :: String)+                    , "month" `iprop` ("short" :: String)+                    , "day" `iprop` ("2-digit" :: String)+                    , "hour" `iprop` ("numeric" :: String)+                    , "timeZone" `iprop` ("Pacific/Tahiti" :: String)+                    ]+                   ]++        li_ ["id" $= "f-relative"] $ relativeTo_ step+        li_ ["id" $= "f-relative-days"] $ formattedRelative_ step [ "units" $= "day" ]++        li_ ["id" $= "f-plural"] $ plural_ [ "value" @= (100 :: Int), "one" $= "plural one", "other" $= "plural other"]+        li_ ["id" $= "f-plural-prop"] $+            input_ [pluralProp "placeholder" (100 :: Int) ["one" `iprop` ("plural one" :: String), "other" `iprop` ("plural other" :: String)]]++        li_ ["id" $= "f-msg"] $+            $(message "photos" "{name} took {numPhotos, plural, =0 {no photos} =1 {one photo} other {# photos}} {takenAgo}.")+                [ "name" $= "Neil Armstrong"+                , "numPhotos" @= (100 :: Int)+                , elementProperty "takenAgo" $ span_ ["id" $= "takenAgoSpan"] "years ago"+                ]++        li_ ["id" $= "f-msg-prop"] $+            input_ [ $(messageProp "placeholder" "photosprop" "{name} took {numPhotos, plural, =0 {no photos} =1 {one photo} other {# photos}}")+                [ "name" `iprop` ("Neil Armstrong" :: String)+                , "numPhotos" `iprop` (100 :: Int)+                ]+            ]++        li_ ["id" $= "f-msg-with-trans"] $+            $(message "with_trans" "this is not used {abc}") ["abc" $= "xxx"]++        li_ ["id" $= "f-msg-with-descr"] $+            $(message' "photos2" "How many photos?" "{name} took {numPhotos, plural, =0 {no photos} =1 {one photo} other {# photos}}.")+                [ "name" $= "Neil Armstrong"+                , "numPhotos" @= (0 :: Int)+                ]++        li_ ["id" $= "f-msg-prop-with-descr"] $+            input_ [$(messageProp' "placeholder" "photosprop2" "How many photos?" "{name} took {numPhotos, number} photos")+                        [ "name" `iprop` ("Neil Armstrong" :: String)+                        , "numPhotos" `iprop` (0 :: Int)+                        ]+                   ]++        li_ ["id" $= "f-html-msg"] $+            $(htmlMsg "html1" "<b>{num}</b> is the answer to life, the universe, and everything")+                [ "num" @= (42 :: Int) ]++        li_ ["id" $= "f-html-msg-with-descr"] $+            $(htmlMsg' "html2" "Hitchhiker's Guide" "{num} is the <b>answer</b> to life, the universe, and everything")+                [ "num" @= (42 :: Int) ]++-------------------------------------------------------------------------------- --- Main -------------------------------------------------------------------------------- @@ -304,7 +483,7 @@         when (s /= "") $             testLifecycle_ s         button_ [ "id" $= "add-app-str"-                , onClick $ \_ _ s' -> ([], Just $ s' ++ "o")+                , onClick $ \_ _ s' -> ([], Just $ s' <> "o")                 ]                 "Add o"         button_ [ "id" $= "clear-app-str"@@ -315,7 +494,16 @@          view cssTransitions ["A", "B"] mempty -        view bootstrapSpec () mempty-         view shouldComponentUpdateSpec () mempty++        view callbackViewWrapper () mempty++        view intlSpec () mempty     }++main :: IO ()+main = reactRender "app" testClient ()++writeIntlMessages (intlFormatJson "test/client/msgs/jsonmsgs.json")+writeIntlMessages (intlFormatJsonWithoutDescription "test/client/msgs/jsonnodescr.json")+writeIntlMessages (intlFormatAndroidXML "test/client/msgs/android.xml")
− test/client/TestClient13.hs
@@ -1,9 +0,0 @@-module Main (main) where--import TestClient-import React.Flux--main :: IO ()-main = do-    initializeTouchEvents-    reactRender "app" testClient ()
− test/client/TestClient14.hs
@@ -1,126 +0,0 @@-{-# LANGUAGE OverloadedStrings, TypeFamilies, ScopedTypeVariables, TemplateHaskell #-}-module Main (main) where--import Data.Time-import React.Flux-import React.Flux.Addons.Intl-import GHCJS.Types (JSVal)-import Data.Aeson ((.=))--import TestClient-------------------------------------------------------------------------------------- Intl-----------------------------------------------------------------------------------foreign import javascript unsafe-    "{'with_trans': 'message from translation {abc}'}"-    js_translations :: JSVal--intlSpec :: ReactView ()-intlSpec = defineView "intl" $ \() ->-    intlProvider_ "en" (Just js_translations) Nothing $-        view intlSpecBody () mempty--intlSpecBody :: ReactView ()-intlSpecBody = defineView "intl body" $ \() -> div_ ["id" $= "intl-spec"] $ -    ul_ $ do-        li_ ["id" $= "f-number"] $-            formattedNumber_ [ "value" @= (0.9 :: Double), "style" $= "percent" ]-        li_ ["id" $= "f-int"] $ int_ 100000-        li_ ["id" $= "f-double"] $ double_ 40000.2-        li_ ["id" $= "f-number-prop"] $-            input_ [formattedNumberProp "placeholder" (123456 :: Int) []]--        let moon = fromGregorian 1969 7 20-            fullDayF = DayFormat { weekdayF = Just "long", eraF = Just "short", yearF = Just "2-digit", monthF = Just "long", dayF = Just "2-digit" }--        li_ ["id" $= "f-shortday"] $ day_ shortDate moon-        li_ ["id" $= "f-fullday"] $ day_ fullDayF moon-        li_ ["id" $= "f-date"] $ formattedDate_ (Left moon)-                [ "weekday" $= "short", "month" $= "short", "day" $= "numeric", "year" $= "2-digit" ]-        li_ ["id" $= "f-date-prop"] $-            input_ [formattedDateProp "placeholder" (Left moon) []]--        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)-                [ "year" $= "2-digit", "month" $= "short", "day" $= "numeric"-                , "hour" $= "numeric", "minute" $= "2-digit", "second" $= "numeric"-                , "timeZoneName" $= "short"-                , "timeZone" $= "Pacific/Tahiti"-                ]-        li_ ["id" $= "f-time-prop"] $-            input_ [formattedDateProp "placeholder" (Right step)-                    [ "year" .= ("2-digit" :: String)-                    , "month" .= ("short" :: String)-                    , "day" .= ("2-digit" :: String)-                    , "hour" .= ("numeric" :: String)-                    , "timeZone" .= ("Pacific/Tahiti" :: String)-                    ]-                   ]--        li_ ["id" $= "f-relative"] $ relativeTo_ step-        li_ ["id" $= "f-relative-days"] $ formattedRelative_ step [ "units" $= "day" ]--        li_ ["id" $= "f-plural"] $ plural_ [ "value" @= (100 :: Int), "one" $= "plural one", "other" $= "plural other"]-        li_ ["id" $= "f-plural-prop"] $-            input_ [pluralProp "placeholder" (100 :: Int) ["one" .= ("plural one" :: String), "other" .= ("plural other" :: String)]]--        li_ ["id" $= "f-msg"] $-            $(message "photos" "{name} took {numPhotos, plural, =0 {no photos} =1 {one photo} other {# photos}} {takenAgo}.")-                [ "name" $= "Neil Armstrong"-                , "numPhotos" @= (100 :: Int)-                , elementProperty "takenAgo" $ span_ ["id" $= "takenAgoSpan"] "years ago"-                ]--        li_ ["id" $= "f-msg-prop"] $-            input_ [ $(messageProp "placeholder" "photosprop" "{name} took {numPhotos, plural, =0 {no photos} =1 {one photo} other {# photos}}")-                [ "name" .= ("Neil Armstrong" :: String)-                , "numPhotos" .= (100 :: Int)-                ]-            ]--        li_ ["id" $= "f-msg-with-trans"] $-            $(message "with_trans" "this is not used {abc}") ["abc" $= "xxx"]--        li_ ["id" $= "f-msg-with-descr"] $-            $(message' "photos2" "How many photos?" "{name} took {numPhotos, plural, =0 {no photos} =1 {one photo} other {# photos}}.")-                [ "name" $= "Neil Armstrong"-                , "numPhotos" @= (0 :: Int)-                ]--        li_ ["id" $= "f-msg-prop-with-descr"] $-            input_ [$(messageProp' "placeholder" "photosprop2" "How many photos?" "{name} took {numPhotos, number} photos")-                        [ "name" .= ("Neil Armstrong" :: String)-                        , "numPhotos" .= (0 :: Int)-                        ]-                   ]--        li_ ["id" $= "f-html-msg"] $-            $(htmlMsg "html1" "<b>{num}</b> is the answer to life, the universe, and everything")-                [ "num" @= (42 :: Int) ]--        li_ ["id" $= "f-html-msg-with-descr"] $-            $(htmlMsg' "html2" "Hitchhiker's Guide" "{num} is the <b>answer</b> to life, the universe, and everything")-                [ "num" @= (42 :: Int) ]-------------------------------------------------------------------------------------- Main------------------------------------------------------------------------------------- | Test a lifecycle view with all lifecycle methods nothing-app :: ReactView ()-app = defineView "main app" $ \() -> do-    view testClient () mempty-    view intlSpec () mempty--main :: IO ()-main = reactRender "app" app ()--writeIntlMessages (intlFormatJson "test/client/msgs/jsonmsgs.json")-writeIntlMessages (intlFormatJsonWithoutDescription "test/client/msgs/jsonnodescr.json")-writeIntlMessages (intlFormatAndroidXML "test/client/msgs/android.xml")
+ test/client/test-client.html view
@@ -0,0 +1,13 @@+<html>+    <head>+        <title>Test Client</title>+    </head>+    <body>+        <div id="app"></div>+        <script src="https://fb.me/react-with-addons-15.0.1.js"></script>+        <script src="https://fb.me/react-dom-15.0.1.js"></script>+        <script src="node_modules/react-intl/dist/react-intl.js"></script>+        <script src="node_modules/react-intl/locale-data/en.js"></script>+        <script src="../../js-build/install-root/bin/test-client.jsexe/all.js"></script>+    </body>+</html>
− test/client/test-client13.html
@@ -1,12 +0,0 @@-<html>-    <head>-        <title>Test Client</title>-    </head>-    <body>-        <div id="app"></div>-        <script src="https://fb.me/react-with-addons-0.13.3.js"></script>-        <script src="node_modules/react-intl/dist/react-intl.js"></script>-        <script src="https://cdnjs.cloudflare.com/ajax/libs/react-bootstrap/0.26.2/react-bootstrap.min.js"></script>-        <script src="../../js-build/install-root/bin/test-client-13.jsexe/all.js"></script>-    </body>-</html>
− test/client/test-client14.html
@@ -1,14 +0,0 @@-<html>-    <head>-        <title>Test Client</title>-    </head>-    <body>-        <div id="app"></div>-        <script src="https://fb.me/react-with-addons-0.14.6.js"></script>-        <script src=" https://fb.me/react-dom-0.14.6.js"></script>-        <script src="node_modules/react-intl/dist/react-intl.js"></script>-        <script src="node_modules/react-intl/locale-data/en.js"></script>-        <script src="https://cdnjs.cloudflare.com/ajax/libs/react-bootstrap/0.28.2/react-bootstrap.min.js"></script>-        <script src="../../js-build/install-root/bin/test-client-14.jsexe/all.js"></script>-    </body>-</html>
test/spec/TestClientSpec.hs view
@@ -35,14 +35,32 @@     p <- findElem (ById "world")     getText p `shouldReturn` (T.pack $ "Current props: " ++ props) -scuShouldBe :: [(Int, String)] -> WD ()-scuShouldBe items = do-    d <- findElem $ ById "should-component-update"+scuShouldBe :: String -> [String] -> WD ()+scuShouldBe ident items = do+    d <- findElem $ ById $ T.pack ident     lis <- findElemsFrom d $ ByTag "li"     length lis `shouldBe` length items-    forM_ (zip lis items) $ \(li,(i, s)) ->-        getText li `shouldReturn` (T.pack $ show i ++ s)+    forM_ (zip lis items) $ \(li,i) ->+        getText li `shouldReturn` T.pack i +scuSingleShouldBe :: [String] -> WD ()+scuSingleShouldBe = scuShouldBe "should-component-update-single"++scuPairShouldBe :: [String] -> WD ()+scuPairShouldBe = scuShouldBe "should-component-update-pair"++scuTripleShouldBe :: [String] -> WD ()+scuTripleShouldBe = scuShouldBe "should-component-update-triple"++scuSingleLogMsg :: String -> String -> [String]+scuSingleLogMsg current new = ["Component will update single", "current props: " ++ current, "new props: " ++ new]++scuPairLogMsg :: String -> String -> [String]+scuPairLogMsg current new = ["Component will update for pair input view", "current props: " ++ current, "new props: " ++ new]++scuTripleLogMsg :: String -> String -> [String]+scuTripleLogMsg current new = ["Component will update for triple input view", "current props: " ++ current, "new props: " ++ new]+ intlSpanShouldBe :: String -> String -> WD () intlSpanShouldBe ident txt = do     e <- findElem (ById $ T.pack ident)@@ -62,11 +80,7 @@         y' = show y  spec :: Spec-spec = do-    describe "React 0.13" $ testClientSpec "test-client13.html"-    describe "React 0.14" $ do-        testClientSpec "test-client14.html"-        intlSpec "test-client14.html"+spec = testClientSpec "test-client.html"  testClientSpec :: String -> Spec testClientSpec filename = session " for the test client" $ using Chrome $ do@@ -95,7 +109,7 @@         [keydown, evt, keyEvt, modState, target, curTarget] <- loadLog         keydown `shouldBe` "keydown"         evt `shouldBeEvent` ("keydown", True, 3)-        keyEvt `shouldBe` "(False,0,False,\"Unidentified\",88,\"\",0,False,False,False,88)"+        keyEvt `shouldBe` "(False,0,False,\"Unidentified\",88,Nothing,0,False,False,False,88)"         modState `shouldBe` "alt modifier: False"         target `shouldBe` "keyinput"         curTarget `shouldBe` "keyinput"@@ -104,9 +118,9 @@         findElem (ById "keyinput") >>= sendKeys "\xE00Ar" -- send Alt-r         -- generates two events, one for alt, one for r         [_, _, keyEvt, modState, _, _, _, _, keyEvt2, modState2, _, _] <- loadLog-        keyEvt `shouldBe` "(True,0,False,\"Alt\",18,\"\",0,False,False,False,18)"+        keyEvt `shouldBe` "(True,0,False,\"Alt\",18,Nothing,0,False,False,False,18)"         modState `shouldBe` "alt modifier: True"-        keyEvt2 `shouldBe` "(True,0,False,\"Unidentified\",82,\"\",0,False,False,False,82)"+        keyEvt2 `shouldBe` "(True,0,False,\"Unidentified\",82,Nothing,0,False,False,False,82)"         modState2 `shouldBe` "alt modifier: True"      it "processes a click event" $ runWD $ do@@ -218,117 +232,195 @@         getText a `shouldReturn` "A"         getText b `shouldReturn` "B" -    describe "bootstrap" $ do--        it "displays and closes alert" $ runWD $ do-            alert <- findElem $ ByCSS "div#bootstrap div.alert"-            (findElemFrom alert (ByTag "p") >>= getText)-                `shouldReturn` "Hello, World!"-            findElemFrom alert (ByTag "button") >>= click-            loadLog `shouldReturn` ["Closing alert"]--        it "switchs nav items" $ runWD $ do-            navUl <- findElem $ ByCSS "div#bootstrap ul"-            i1 <- findElemFrom navUl $ ByCSS "li:first-child a"-            i2 <- findElemFrom navUl $ ByCSS "li:nth-child(2) a"-            i3 <- findElemFrom navUl $ ByCSS "li:nth-child(3) a"-            click i2-            loadLog `shouldReturn` ["Switched to 2"]-            click i3-            loadLog `shouldReturn` ["Switched to 3"]-            click i1-            loadLog `shouldReturn` ["Switched to 1"]+    it "renders a callback returning a view" $ runWD $ do+        e <- findElem $ ById "callback-view-props-test"+        getText e `shouldReturn` "Props are 5 and Hello World"      describe "should component update" $ do -        it "has the initial data" $ runWD $-            scuShouldBe [(1, "Hello"), (2, "World"), (3, "!!!")]--        it "increments just the first entry without re-rendering all entries" $ runWD $ do-            findElem (ById "increment-first-scu") >>= click-            scuShouldBe [(2, "Hello"), (2, "World"), (3, "!!!")]-            loadLog `shouldReturn`-                [ "Component will update"-                , "current props: 1 Hello"-                , "new props: 2 Hello"-                ]+        it "has the initial data" $ runWD $ do+            scuSingleShouldBe ["1Quick Ben", "5Karsa", "9Anomander Rake"]+            scuPairShouldBe ["1Quick Ben2Whiskeyjack", "5Karsa6Tehol", "9Anomander Rake10Iskaral Pust"]+            scuTripleShouldBe ["1Quick Ben2Whiskeyjack3Fiddler", "5Karsa6Tehol7Tayschrenn", "9Anomander Rake10Iskaral Pust11Dujek"]          it "does not update when no change to data" $ runWD $ do             findElem (ById "no-change-scu") >>= click-            scuShouldBe [(2, "Hello"), (2, "World"), (3, "!!!")]+            scuSingleShouldBe ["1Quick Ben", "5Karsa", "9Anomander Rake"]+            scuPairShouldBe ["1Quick Ben2Whiskeyjack", "5Karsa6Tehol", "9Anomander Rake10Iskaral Pust"]+            scuTripleShouldBe ["1Quick Ben2Whiskeyjack3Fiddler", "5Karsa6Tehol7Tayschrenn", "9Anomander Rake10Iskaral Pust11Dujek"]             loadLog `shouldReturn` [] -        it "increments all entries" $ runWD $ do-            findElem (ById "change-all-scu") >>= click-            scuShouldBe [(3, "Hello"), (3, "World"), (4, "!!!")]-            loadLog `shouldReturn`-                [ "Component will update"-                , "current props: 2 Hello"-                , "new props: 3 Hello"-                , "Component will update"-                , "current props: 2 World"-                , "new props: 3 World"-                , "Component will update"-                , "current props: 3 !!!"-                , "new props: 4 !!!"-                ]+        describe "Changing the first scu" $ do +            it "increments just the head of the list without re-rendering all entries" $ runWD $ do+                findElem (ById "increment-first-scuSCU1") >>= click+                scuSingleShouldBe ["2Quick Ben", "5Karsa", "9Anomander Rake"]+                scuPairShouldBe ["2Quick Ben2Whiskeyjack", "5Karsa6Tehol", "9Anomander Rake10Iskaral Pust"]+                scuTripleShouldBe ["2Quick Ben2Whiskeyjack3Fiddler", "5Karsa6Tehol7Tayschrenn", "9Anomander Rake10Iskaral Pust11Dujek"]+                loadLog `shouldReturn` concat+                    [ scuSingleLogMsg "1 Quick Ben"+                                      "2 Quick Ben"+                    , scuPairLogMsg "1 Quick Ben 2 Whiskeyjack"+                                    "2 Quick Ben 2 Whiskeyjack"+                    , scuTripleLogMsg "1 Quick Ben 2 Whiskeyjack 3 Fiddler"+                                      "2 Quick Ben 2 Whiskeyjack 3 Fiddler"+                    ] -    {--    it "inspects the session" $ runWD $ do-        loadLog >>= \x -> liftIO $ putStrLn $ show x-        inspectSession-    -} -intlSpec :: String -> Spec-intlSpec filename = session " for the i18n test client" $ using Chrome $ do-    it "opens the page" $ runWD $ do-        dir <- liftIO $ getCurrentDirectory-        openPage $ "file://" ++ dir ++ "/../client/" ++ filename+            it "increments all entries" $ runWD $ do+                findElem (ById "change-all-scu-SCU1") >>= click+                scuSingleShouldBe ["3Quick Ben", "6Karsa", "10Anomander Rake"]+                scuPairShouldBe ["3Quick Ben2Whiskeyjack", "6Karsa6Tehol", "10Anomander Rake10Iskaral Pust"]+                scuTripleShouldBe ["3Quick Ben2Whiskeyjack3Fiddler", "6Karsa6Tehol7Tayschrenn", "10Anomander Rake10Iskaral Pust11Dujek"]+                loadLog `shouldReturn` concat+                    [ scuSingleLogMsg "2 Quick Ben"+                                      "3 Quick Ben"+                    , scuSingleLogMsg "5 Karsa"+                                      "6 Karsa"+                    , scuSingleLogMsg "9 Anomander Rake"+                                      "10 Anomander Rake"+                    , scuPairLogMsg   "2 Quick Ben 2 Whiskeyjack"+                                      "3 Quick Ben 2 Whiskeyjack"+                    , scuPairLogMsg   "5 Karsa 6 Tehol"+                                      "6 Karsa 6 Tehol"+                    , scuPairLogMsg   "9 Anomander Rake 10 Iskaral Pust"+                                      "10 Anomander Rake 10 Iskaral Pust"+                    , scuTripleLogMsg "2 Quick Ben 2 Whiskeyjack 3 Fiddler"+                                      "3 Quick Ben 2 Whiskeyjack 3 Fiddler"+                    , scuTripleLogMsg "5 Karsa 6 Tehol 7 Tayschrenn"+                                      "6 Karsa 6 Tehol 7 Tayschrenn"+                    , scuTripleLogMsg "9 Anomander Rake 10 Iskaral Pust 11 Dujek"+                                      "10 Anomander Rake 10 Iskaral Pust 11 Dujek"+                    ] -    it "displays the intl formatted data" $ runWD $ do-        "f-number" `intlSpanShouldBe` "90%"-        "f-int" `intlSpanShouldBe` "100,000"-        "f-double" `intlSpanShouldBe` "40,000.2"-        "f-shortday" `intlSpanShouldBe` "Jul 20, 1969"-        "f-fullday" `intlSpanShouldBe` "Sunday, July 20, 69 AD"-        "f-date" `intlSpanShouldBe` "Sun, Jul 20, 69"-        -- f-shorttime and f-fulltime cannot be (easily) tested since they rely on the current timezone-        "f-time" `intlSpanShouldBe` "Jul 19, 69, 4:56:00 PM GMT-10"-        "f-plural" `intlSpanShouldBe` "plural other"+        describe "Changing the second scu" $ do -        today <- liftIO (utctDay <$> getCurrentTime)-        let moon = fromGregorian 1969 7 20-            daysAgo = diffDays today moon-            yearsAgo :: Int = round $ realToFrac daysAgo / (365 :: Double) -- is close enough-        "f-relative" `intlSpanShouldBe` (show (yearsAgo) ++ " years ago")-        "f-relative-days" `intlSpanShouldBe` (showWithComma (daysAgo+1) ++ " days ago")+            it "increments just the head of the list without re-rendering all entries" $ runWD $ do+                findElem (ById "increment-first-scuSCU2") >>= click+                scuSingleShouldBe ["3Quick Ben", "6Karsa", "10Anomander Rake"] -- unchanged+                scuPairShouldBe ["3Quick Ben3Whiskeyjack", "6Karsa6Tehol", "10Anomander Rake10Iskaral Pust"]+                scuTripleShouldBe ["3Quick Ben3Whiskeyjack3Fiddler", "6Karsa6Tehol7Tayschrenn", "10Anomander Rake10Iskaral Pust11Dujek"]+                loadLog `shouldReturn` concat+                    [ scuPairLogMsg "3 Quick Ben 2 Whiskeyjack"+                                    "3 Quick Ben 3 Whiskeyjack"+                    , scuTripleLogMsg "3 Quick Ben 2 Whiskeyjack 3 Fiddler"+                                      "3 Quick Ben 3 Whiskeyjack 3 Fiddler"+                    ] -    it "displays messages" $ runWD $ do-        msg <- findElem $ ById "f-msg"-        getText msg `shouldReturn` "Neil Armstrong took 100 photos years ago."-        takenAgoSpan <- findElemFrom msg $ ById "takenAgoSpan"-        getText takenAgoSpan `shouldReturn` "years ago" -        msg' <- findElem $ ById "f-msg-with-descr"-        getText msg' `shouldReturn` "Neil Armstrong took no photos."+            it "increments all entries" $ runWD $ do+                findElem (ById "change-all-scu-SCU2") >>= click+                scuSingleShouldBe ["3Quick Ben", "6Karsa", "10Anomander Rake"] -- unchanged+                scuPairShouldBe ["3Quick Ben4Whiskeyjack", "6Karsa7Tehol", "10Anomander Rake11Iskaral Pust"]+                scuTripleShouldBe ["3Quick Ben4Whiskeyjack3Fiddler", "6Karsa7Tehol7Tayschrenn", "10Anomander Rake11Iskaral Pust11Dujek"]+                loadLog `shouldReturn` concat+                    [ scuPairLogMsg   "3 Quick Ben 3 Whiskeyjack"+                                      "3 Quick Ben 4 Whiskeyjack"+                    , scuPairLogMsg   "6 Karsa 6 Tehol"+                                      "6 Karsa 7 Tehol"+                    , scuPairLogMsg   "10 Anomander Rake 10 Iskaral Pust"+                                      "10 Anomander Rake 11 Iskaral Pust"+                    , scuTripleLogMsg "3 Quick Ben 3 Whiskeyjack 3 Fiddler"+                                      "3 Quick Ben 4 Whiskeyjack 3 Fiddler"+                    , scuTripleLogMsg "6 Karsa 6 Tehol 7 Tayschrenn"+                                      "6 Karsa 7 Tehol 7 Tayschrenn"+                    , scuTripleLogMsg "10 Anomander Rake 10 Iskaral Pust 11 Dujek"+                                      "10 Anomander Rake 11 Iskaral Pust 11 Dujek"+                    ] -        "f-msg-with-trans" `intlSpanShouldBe` "message from translation xxx"+        describe "Changing the third scu" $ do -        htmlMsg <- findElem $ ById "f-html-msg"-        getText htmlMsg `shouldReturn` "42 is the answer to life, the universe, and everything"-        (findElemFrom htmlMsg (ByTag "b") >>= getText)-            `shouldReturn` "42"+            it "increments just the head of the list without re-rendering all entries" $ runWD $ do+                findElem (ById "increment-first-scuSCU3") >>= click+                scuSingleShouldBe ["3Quick Ben", "6Karsa", "10Anomander Rake"] -- unchanged+                scuPairShouldBe ["3Quick Ben4Whiskeyjack", "6Karsa7Tehol", "10Anomander Rake11Iskaral Pust"] -- unchanged+                scuTripleShouldBe ["3Quick Ben4Whiskeyjack4Fiddler", "6Karsa7Tehol7Tayschrenn", "10Anomander Rake11Iskaral Pust11Dujek"]+                loadLog `shouldReturn` concat+                    [ scuTripleLogMsg "3 Quick Ben 4 Whiskeyjack 3 Fiddler"+                                      "3 Quick Ben 4 Whiskeyjack 4 Fiddler"+                    ] -        htmlMsg' <- findElem $ ById "f-html-msg-with-descr"-        getText htmlMsg' `shouldReturn` "42 is the answer to life, the universe, and everything"-        (findElemFrom htmlMsg' (ByTag "b") >>= getText)-            `shouldReturn` "answer" -    it "displays formatted properties" $ runWD $ do-        "f-number-prop" `intlPlaceholderShouldBe` "123,456"-        "f-date-prop" `intlPlaceholderShouldBe` "7/20/1969"-        "f-time-prop" `intlPlaceholderShouldBe` "Jul 19, 69, 4 PM"-        "f-plural-prop" `intlPlaceholderShouldBe` "other"-        "f-msg-prop" `intlPlaceholderShouldBe` "Neil Armstrong took 100 photos"-        "f-msg-prop-with-descr" `intlPlaceholderShouldBe` "Neil Armstrong took 0 photos"+            it "increments all entries" $ runWD $ do+                findElem (ById "change-all-scu-SCU3") >>= click+                scuSingleShouldBe ["3Quick Ben", "6Karsa", "10Anomander Rake"] -- unchanged+                scuPairShouldBe ["3Quick Ben4Whiskeyjack", "6Karsa7Tehol", "10Anomander Rake11Iskaral Pust"] -- unchanged+                scuTripleShouldBe ["3Quick Ben4Whiskeyjack5Fiddler", "6Karsa7Tehol8Tayschrenn", "10Anomander Rake11Iskaral Pust12Dujek"]+                loadLog `shouldReturn` concat+                    [ scuTripleLogMsg "3 Quick Ben 4 Whiskeyjack 4 Fiddler"+                                      "3 Quick Ben 4 Whiskeyjack 5 Fiddler"+                    , scuTripleLogMsg "6 Karsa 7 Tehol 7 Tayschrenn"+                                      "6 Karsa 7 Tehol 8 Tayschrenn"+                    , scuTripleLogMsg "10 Anomander Rake 11 Iskaral Pust 11 Dujek"+                                      "10 Anomander Rake 11 Iskaral Pust 12 Dujek"+                    ]++        describe "changing the fourth scu" $ do++            it "increments just the head of the list" $ runWD $ do+                findElem (ById "increment-first-scuSCU4") >>= click+                -- unchanged+                scuSingleShouldBe ["3Quick Ben", "6Karsa", "10Anomander Rake"] -- unchanged+                scuPairShouldBe ["3Quick Ben4Whiskeyjack", "6Karsa7Tehol", "10Anomander Rake11Iskaral Pust"] -- unchanged+                scuTripleShouldBe ["3Quick Ben4Whiskeyjack5Fiddler", "6Karsa7Tehol8Tayschrenn", "10Anomander Rake11Iskaral Pust12Dujek"]+                loadLog `shouldReturn` []++            it "increments all entries" $ runWD $ do+                findElem (ById "change-all-scu-SCU4") >>= click+                -- unchanged+                scuSingleShouldBe ["3Quick Ben", "6Karsa", "10Anomander Rake"] -- unchanged+                scuPairShouldBe ["3Quick Ben4Whiskeyjack", "6Karsa7Tehol", "10Anomander Rake11Iskaral Pust"] -- unchanged+                scuTripleShouldBe ["3Quick Ben4Whiskeyjack5Fiddler", "6Karsa7Tehol8Tayschrenn", "10Anomander Rake11Iskaral Pust12Dujek"]+                loadLog `shouldReturn` []++    describe "i18n" $ do+        it "opens the page" $ runWD $ do+            dir <- liftIO $ getCurrentDirectory+            openPage $ "file://" ++ dir ++ "/../client/" ++ filename++        it "displays the intl formatted data" $ runWD $ do+            "f-number" `intlSpanShouldBe` "90%"+            "f-int" `intlSpanShouldBe` "100,000"+            "f-double" `intlSpanShouldBe` "40,000.2"+            "f-shortday" `intlSpanShouldBe` "Jul 20, 1969"+            "f-fullday" `intlSpanShouldBe` "Sunday, July 20, 69 AD"+            "f-date" `intlSpanShouldBe` "Sun, Jul 20, 69"+            -- f-shorttime and f-fulltime cannot be (easily) tested since they rely on the current timezone+            "f-time" `intlSpanShouldBe` "Jul 19, 69, 4:56:00 PM GMT-10"+            "f-plural" `intlSpanShouldBe` "plural other"++            today <- liftIO (utctDay <$> getCurrentTime)+            let moon = fromGregorian 1969 7 20+                daysAgo = diffDays today moon+                yearsAgo :: Int = round $ realToFrac daysAgo / (365 :: Double) -- is close enough+            "f-relative" `intlSpanShouldBe` (show (yearsAgo) ++ " years ago")+            "f-relative-days" `intlSpanShouldBe` (showWithComma (daysAgo+1) ++ " days ago")++        it "displays messages" $ runWD $ do+            msg <- findElem $ ById "f-msg"+            getText msg `shouldReturn` "Neil Armstrong took 100 photos years ago."+            takenAgoSpan <- findElemFrom msg $ ById "takenAgoSpan"+            getText takenAgoSpan `shouldReturn` "years ago"++            msg' <- findElem $ ById "f-msg-with-descr"+            getText msg' `shouldReturn` "Neil Armstrong took no photos."++            "f-msg-with-trans" `intlSpanShouldBe` "message from translation xxx"++            htmlMsg <- findElem $ ById "f-html-msg"+            getText htmlMsg `shouldReturn` "42 is the answer to life, the universe, and everything"+            (findElemFrom htmlMsg (ByTag "b") >>= getText)+                `shouldReturn` "42"++            htmlMsg' <- findElem $ ById "f-html-msg-with-descr"+            getText htmlMsg' `shouldReturn` "42 is the answer to life, the universe, and everything"+            (findElemFrom htmlMsg' (ByTag "b") >>= getText)+                `shouldReturn` "answer"++        it "displays formatted properties" $ runWD $ do+            "f-number-prop" `intlPlaceholderShouldBe` "123,456"+            "f-date-prop" `intlPlaceholderShouldBe` "7/20/1969"+            "f-time-prop" `intlPlaceholderShouldBe` "Jul 19, 69, 4 PM"+            "f-plural-prop" `intlPlaceholderShouldBe` "other"+            "f-msg-prop" `intlPlaceholderShouldBe` "Neil Armstrong took 100 photos"+            "f-msg-prop-with-descr" `intlPlaceholderShouldBe` "Neil Armstrong took 0 photos"
test/spec/stack.yaml view
@@ -1,3 +1,3 @@-resolver: lts-5.6+resolver: lts-6.1 packages: - '.'