[![Hackage](https://img.shields.io/hackage/v/glazier-react-widget.svg)](https://hackage.haskell.org/package/glazier-react-widget) This is a library of reusable composable widget using [`Glazier.React`](https://github.com/louispan/glazier-react). Please help me to add more widgets to this library! # Prerequisite reading ## Glazier Please read the [README.md](https://github.com/louispan/glazier) for a brief overview of glazier. ## Glazier.React Please read the [README.md](https://github.com/louispan/glazier-react) for a brief overview of glazier-react. # Widget best practice The following documents the expected conventions and best practices when defining a `Glazier.React.Widgets` widget. ## Exports All widgets should export at the minimum the following: ```haskell module Glazier.React.Widgets.Input ( Command(..) , Action(..) , AsAction(..) , Design(..) , HasDesign(..) , Plan(..) , HasPlan(..) , Outline , Model , Widget , widget ) where ``` This provides a consistent way to interact and use every widget. Since all widgets export the same names, any widget should be imported qualified. ## Command `Command`s are the result of the `Gadget` stateful processing of `Action`. It is a pure value that is interpreted effectfully. ```haskell data Command = RenderCommand (SuperModel Model Plan) [Property] JSVal | DisposeCommand SomeDisposable | MakerCommand (F (Maker Action) Action) ``` Some common commands are: ### RenderCommand ```haskell RenderCommand (SuperModel Model Plan) [Property] JSVal ``` This is send by `Gadget` when the re-rendering is required. It contains the`SuperModel` of the widget (to swap the latest `Design` into the `Frame`), the new React component state as a list of properties (usually just a sequence number), and the javascript reference to the javascript component. ### DisposeCommand ```haskell DisposeCommand SomeDisposable ``` This contains the list of callbacks to dispose after the next render frame (after [`componentDidUpdate`](https://facebook.github.io/react/docs/react-component.html#componentdidupdate) is called. ### MakerCommand ```haskell MakerCommand (F (Maker Action) Action) ``` This is the command to run the `Maker` instruction in the `Maker` interpreter which results in an `Action` to outcome back tot he gadget. ## Action This contains the events that the widget `Gadget` processes. ```haskell data Action = ComponentRefAction JSVal | RenderAction | ComponentDidUpdateAction makeClassyPrisms ''Action ``` `Action`s should have [`makeClassyPrisms`](https://hackage.haskell.org/package/lens-4.15.1/docs/Control-Lens-TH.html#v:makeClassyPrisms) generated to facilitate embedding it in larger `Gadget` with [`magnify`](https://hackage.haskell.org/package/lens-4.15.1/docs/Control-Lens-Zoom.html#v:magnify). Some common `Action`s are: ### ComponentRefAction ```haskell ComponentRefAction JSVal ``` This action is generated by the [`ref`](https://facebook.github.io/react/docs/refs-and-the-dom.html) event listener and contains a javascript reference to the react component. This ref is used in the `RenderCommand`. ### RenderAction ```haskell RenderAction ``` You can generate this action to force a widget to return the `RenderCommand` to force a re-render. ### ComponentDidUpdateAction ```haskell ComponentDidUpdateAction JSVal ``` This action is generated by the [`componentDidUpdate`](https://facebook.github.io/react/docs/react-component.html#componentdidupdate) event listener. This event is usually used to generate the `DisposeCommand` to dispose callbacks from removed widgets. ## Design This contains the template for pure data for state processing logic (the nouns). ```haskell data Design = Design { _blah :: Foo } makeClassy ''Design type Model = Design type Outline = Design instance ToOutline Model Outline where outline = id mkModel :: Outline -> F (Maker Action) Model mkModel = pure ``` `Design`s should have [`makeClassy`](https://hackage.haskell.org/package/lens-4.15.1/docs/Control-Lens-TH.html#v:makeClassy) generated to facilitate embedding it in larger widget with [`magnify`](https://hackage.haskell.org/package/lens-4.15.1/docs/Control-Lens-Zoom.html#v:magnify) and [`zoom`](https://hackage.haskell.org/package/lens-4.15.1/docs/Control-Lens-Zoom.html#v:zoom). If the `Design` contains child widgets, then it should have use a type parameter and `DesignType` to allow specializations of `Model` and `Outline` ```haskell data Design t = Design { _input :: DesignType t W.Input.Widget , _todos :: DesignType t (W.List.Widget TodosKey TD.Todo.Widget) , _footer :: DesignType t TD.Footer.Widget } type Model = Design WithGizmo type Outline = Design WithOutline instance ToOutline Model Outline where outline (Design a b c) = Design (outline a) (outline b) (outline c) mkModel :: ReactMl () -> Outline -> F (Maker Action) Model mkModel separator (Design a b c) = Design <$> (hoistWithAction InputAction (mkGizmo' W.Input.widget a)) <*> (hoistWithAction TodosAction (mkGizmo' (W.List.widget separator TD.Todo.widget) b)) <*> (hoistWithAction FooterAction (mkGizmo' TD.Footer.widget c)) ``` ## Plan The `Plan` contains the callbacks for integrating with React (the verbs). It also contains a javascript reference to the instance of shim component used for the widget. This reference is used to trigger rendering with [`setState`](https://facebook.github.io/react/docs/react-component.html#setstate). ```haskell data Plan = Plan { _component :: ReactComponent , _key :: J.JSString , _frameNum :: Int , _componentRef :: J.JSVal , _deferredDisposables :: D.DList CD.SomeDisposable , _onRender :: J.Callback (J.JSVal -> IO J.JSVal) , _onComponentRef :: J.Callback (J.JSVal -> IO ()) , _onComponentDidUpdate :: J.Callback (J.JSVal -> IO ()) makeClassy ''Plan ``` `Plan`s should have [`makeClassy`](https://hackage.haskell.org/package/lens-4.15.1/docs/Control-Lens-TH.html#v:makeClassy) generated to allow consistent usage of lens to access `Model` and `Plan` fields. Some common `Plan` fields are ### key ```haskell _key :: JSString ``` `key` is used to ensure a unique [key](https://facebook.github.io/react/docs/lists-and-keys.html) for React's efficient rendering of a list. ### componentRef ```haskell _componentRef :: JSVal ``` `componentRef` is used to store the reference to the instance of the React shim component from the `ComponentRefAction` and used in the `RenderCommand` ### frameNum ```haskell _frameNum :: Int ``` `frameNum` is the sequence number used in `RenderCommand`. ### _deferredDisposables ```haskell _deferredDisposables :: DList SomeDisposable ``` `deferredDisposables` keep the list of disposables to dispose at the next `ComponentDidUpdateAction`. ### _component ```haskell _component :: ReactComponent ``` This contains the reference to the shim `React.PureComponent` class that is used to start the rendering. ### _onRender ```haskell _onRender :: Callback (JSVal -> IO JSVal) ``` The is the callback from the shim component's [`render`](https://facebook.github.io/react/docs/react-component.html#render) handler. It contains a javascript reference to the shim component's state, which is currently not used, but might be in the future. ### _onComponentRef ```haskell _onComponentRef :: Callback (JSVal -> IO ()) ``` The is the callback from the shim component's [`ref`](https://facebook.github.io/react/docs/refs-and-the-dom.html) event listener. The callback is expected to generate the `ComponentRefAction`. ### _onComponentDidUpdate ```haskell _onComponentDidUpdate :: Callback (JSVal -> IO ()) ``` The is the callback from the shim component's [`componentDidUpdate`](https://facebook.github.io/react/docs/react-component.html#componentdidupdate) event listener. The callback is expected to generate the `ComponentDidUpdateAction`. ## mkPlan This is the missing piece required to construct a widget's `SuperModel`. It contains the code to create a widget's `Plan` using the `Maker` DSL. The `Applicative` typeclass makes this easy to define. ```haskell mkPlan :: Frame Model Plan -> F (Maker Action) Plan mkPlan frm = Plan <$> getComponent <*> mkKey <*> pure 0 <*> pure J.nullRef <*> pure mempty <*> (mkRenderer frm $ const render) <*> (mkHandler $ pure . pure . InputRefAction) <*> (mkHandler $ pure . pure . ComponentRefAction) <*> (mkHandler $ pure . pure . const ComponentDidUpdateAction) ``` ## Common code All widgets should have implementation of the following ### Disposing Model and Plan ```haskell instance Disposing Plan instance Disposing Model where disposing _ = DisposeNone ``` ### Link HasPlan and HasModel Link `Glazier.React.Model`'s genericHasPlan/HasModel with this widget's specific `HasPlan`/`HasModel` from generated from `makeClassy` ```haskell instance HasPlan (Scene Model Plan) where plan = plan instance HasDesign (Scene Model Plan) where design = model instance HasPlan (Gizmo Model Plan) where plan = scene . plan instance HasDesign (Gizmo Model Plan) where design = scene . design ``` ### Widget definitions `widget` is a record of functions of the essential functions required to make, render and interact with the widget. By convention, `mkPlan`, `window`, and `gadget` is exported, but sometimes it's convenient to have all three grouped together in a record. ```haskell type Widget = Widget Command Action Model Plan widget :: Widget widget = Widget mkModel mkPlan window gadget ``` `widget` is always an instance of `IsWidget` typeclass, so exporting a type synomym `Widget` will allow generic widget manipulation code. For example, the [`List` widget](https://github.com/louispan/glazier-react-widget/blob/54a771f492b864ff422e31949284ea4b23aa02c6/src/Glazier/React/Widgets/List.hs#L128) uses the `IsWidget` typeclass of the item widgets in order to define the `widget` record value. ## window This is the starting rendering function to start the rendering. It always only renders the shim React component with the specific callbacks: ```haskell window :: WindowT (Design Model Plan) ReactMl () window = do s <- ask lift $ lf (s ^. component . to toJS) [ ("key", s ^. key . to toJS) , ("render", s ^. onRender . to toJS) , ("ref", s ^. onComponentRef . to toJS) , ("componentDidUpdate", s ^. onComponentDidUpdate . to toJS) ] ``` This a a monad transformer stack over `Identity`. This ensures only pure effects are allowed. ## render This is the inner rendering function. React will render the shim component from `window` above, and then call the `Plan`'s `onRender` callback of the shim component, which triggers this rendering function. This contains the widget specific rendering instructions. ```haskell render :: WindowT (Design Model Plan) ReactMl () ``` This a a monad transformer stack over `Identity`. This ensures only pure effects are allowed. ## gadget This contains the state update logic: ```haskell gadget :: G.Gadget () Action (SuperModel Model Plan) (DList Command) ``` This a a monad transformer stack over `Identity`. This ensures only pure effects are allowed. When required, `STM` can always be [`hoist (hoist generalize)`](https://github.com/louispan/glazier-react-examples/blob/32b5b077faa499e7501cb8e5417105b340de9ad3/examples/todo/haskell/app/Main.hs#L92) into the gadget using [`Control.Monad.Morph`](https://hackage.haskell.org/package/mmorph/docs/Control-Monad-Morph.html).