[](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(..)
, Plan(..)
, HasPlan(..)
, mkPlan
, Model(..)
, HasModel(..)
, Design
, Frame
, SuperModel
, Widget
, widget
, window
, gadget
) 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 dispatch 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.
## Model
This contains the pure data for state processing logic and rendering (the nouns).
```haskell
data Model = Model
{ _key :: J.JSString
, _componentRef :: J.JSVal
, _frameNum :: Int
, _deferredCommands :: D.DList (Command key itemWidget)
}
makeClassy ''Model
```
`Model`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).
Some common Model 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`.
## 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 :: R.ReactComponent
, _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
### _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
<*> (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 (R.Design Model Plan) where
plan = R.plan
instance HasModel (R.Design Model Plan) where
model = R.model
instance HasPlan (R.SuperModel Model Plan) where
plan = R.design . plan
instance HasModel (R.SuperModel Model Plan) where
model = R.design . model
```
### Synonums to Design, Frame, and SuperModel
```haskell
type Design = R.Design Model Plan
type Frame = R.Frame Model Plan
type SuperModel = R.SuperModel Model Plan
```
### 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 Command Action Model Plan
widget = Widget
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) (ReactMlT Identity) ()
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) (ReactMlT Identity) ()
```
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.GadgetT Action (R.SuperModel Model Plan) Identity (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).