packages feed

glazier-react-widget-0.4.0.0: README.md

[![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(..)
    , 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).