[](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 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.
## 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 R.ToOutline Model Outline where outline = id
mkModel :: Outline -> F (R.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 :: R.DesignType t W.Input.Widget
, _todos :: R.DesignType t (W.List.Widget TodosKey TD.Todo.Widget)
, _footer :: R.DesignType t TD.Footer.Widget
}
type Model = Design R.WithGizmo
type Outline = Design R.WithOutline
instance R.ToOutline Model Outline where
outline (Design a b c) = Design (R.outline a) (R.outline b) (R.outline c)
mkModel :: R.ReactMlT Identity () -> Outline -> F (R.Maker Action) Model
mkModel separator (Design a b c) = Design
<$> (R.hoistWithAction InputAction (R.mkGizmo' W.Input.widget a))
<*> (R.hoistWithAction TodosAction (R.mkGizmo' (W.List.widget separator TD.Todo.widget) b))
<*> (R.hoistWithAction FooterAction (R.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 :: R.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
<$> R.getComponent
<*> R.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 (R.Scene Model Plan) where
plan = R.plan
instance HasDesign (R.Scene Model Plan) where
design = R.model
instance HasPlan (R.Gizmo Model Plan) where
plan = R.scene . plan
instance HasDesign (R.Gizmo Model Plan) where
design = R.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 = R.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) (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).