Skip to content

Instantly share code, notes, and snippets.

@PerWiklander
Created June 24, 2015 19:59
Show Gist options
  • Save PerWiklander/38791f10b9cd6574b0c0 to your computer and use it in GitHub Desktop.
Save PerWiklander/38791f10b9cd6574b0c0 to your computer and use it in GitHub Desktop.
Traits to simplify making scalajs-react widgets
import scalaz.effect.IO
trait ItemSelector[ItemType] extends StatefulWidget[
ItemSelectorProps[ItemType],
ItemSelectorState[ItemType]
]
{
override def initialState = P => ItemSelectorState(P.selected)
}
case class ItemSelectorProps[ItemType](
name: String,
items: Seq[ItemType],
selected: ItemType,
onChange: ItemType => IO[Unit],
asValue: ItemType => String = (item: ItemType) => item.toString,
asLegend: ItemType => String = (item: ItemType) => item.toString,
label: Option[String] = None
)
case class ItemSelectorState[ItemType](
selected: ItemType
)
import japgolly.scalajs.react.ScalazReact._
import japgolly.scalajs.react._
import scalaz.effect.IO
import scalaz.syntax.std.boolean._
trait RadioGroup[ItemType] extends ItemSelector[ItemType] {
def handleChange(changedItem: ItemType)(implicit scope: Scope): (ReactEventI) => IO[Unit] =
(event: ReactEventI) =>
maybeModifyState(
event.target.checked.option(_.copy(selected = changedItem)),
scope
)
override def render = (C, P, S) => {
implicit val scope: Scope = C
<.div(s.grouped.fields)(
P.label map (<.label(_)),
P.items map {item =>
<.div(s.field)(
<.div(
s.ui.radio.checkbox,
item == P.selected ?= s.checked
)(
<.input(
^.tpe := InputType.RADIO,
^.value := P.asValue(item),
^.checked := item == P.selected,
^.name := P.name,
^.onChange ~~> handleChange(item)
),
<.label(P.asLegend(item))
)
)
}
)
}
}
import japgolly.scalajs.react.{ReactEventI, ReactElement, ReactComponentB}
import japgolly.scalajs.react.ScalazReact.ReactS.Fix
import japgolly.scalajs.react.ScalazReact.{ReactS, _}
import japgolly.scalajs.react.extra.OnUnmount.Backend
import scalaz.effect.IO
trait StatefulWidget[Props, State] extends WidgetBase[Props, State, Backend] {
protected override val widget: Widget =
ReactComponentB[Props](name)
.initialStateP(initialState)
.backend(_ => new Backend)
.renderS(render)
.configure(configuration: _*)
.componentWillMount(onComponentWillMount)
.componentDidMount(onComponentDidMount)
.componentWillReceiveProps(onComponentWillReceiveProps)
.componentWillUpdate(onComponentWillUpdate)
.componentDidUpdate(onComponentDidUpdate)
.componentWillUnmount(onComponentWillUnMount)
.build
protected def initialState: Props => State
protected def render: (Scope, Props, State) => ReactElement
protected lazy val stateModifier: Fix[State] = ReactS.Fix[State]
protected def maybeModifyState(
stateModification: Option[State => State],
scope: Scope
): IO[Unit] = stateModification map (modifyState(_, scope)) getOrElse IO(())
protected def modifyState(
stateModification: State => State,
scope: Scope
): IO[Unit] = scope.runState(stateModifier.mod(stateModification))
protected def modifyStateFromEventTarget(
modFromString: String => State => State,
scope: Scope
) = scope._runState({ event: ReactEventI => stateModifier.mod(modFromString(event.target.value))})
}
import japgolly.scalajs.react._
/**
* Create a widget that always displays the same content, never needs to be redrawn, never needs vdom diffing.
*/
trait StaticWidget {
private[StaticWidget] val widget = ReactComponentB.static(name, render).buildU
def apply(children: ReactElement*) = widget(children)
protected def name: String = this.getClass.getSimpleName
protected def render: ReactElement
}
import japgolly.scalajs.react.{ReactElement, ReactComponentB}
trait Widget[Props] extends WidgetBase[Props, Unit, Unit] {
protected override val widget: Widget =
ReactComponentB[Props](name)
.render(render)
.configure(configuration: _*)
.componentWillMount(onComponentWillMount)
.componentDidMount(onComponentDidMount)
.componentWillReceiveProps(onComponentWillReceiveProps)
.componentWillUpdate(onComponentWillUpdate)
.componentDidUpdate(onComponentDidUpdate)
.componentWillUnmount(onComponentWillUnMount)
.build
protected def render: Props => ReactElement
}
import japgolly.scalajs.react._
import japgolly.scalajs.react.ReactComponentC.ReqProps
import scala.scalajs.js
protected trait WidgetBase[Props, State, Backend] {
protected type ComponentB = ReactComponentB[Props, State, Backend]
protected type ComponentU = ReactComponentU[Props, State, Backend, TopNode]
protected type Scope = ComponentScopeU[Props, State, Backend]
protected type ScopeM = ComponentScopeM[Props, State, Backend]
protected type ScopeWU = ComponentScopeWU[Props, State, Backend]
protected type Widget = ReqProps[Props, State, Backend, TopNode]
protected def widget: Widget
def apply(props: Props, children: ReactNode*): ComponentU = widget(props, children)
def withKey(key: js.Any): Widget = widget.set(key = key)
def withRef(ref: String): Widget = widget.set(ref = ref)
protected def name: String = this.getClass.getSimpleName
protected def configuration: Seq[ComponentB => ComponentB] = Seq.empty[ComponentB => ComponentB]
protected def onComponentWillMount: Scope => Unit = scope => {}
protected def onComponentDidMount: ScopeM => Unit = scope => {}
protected def onComponentWillReceiveProps: (ScopeM, Props) => Unit = (scope, props) => {}
protected def onComponentWillUpdate: (ScopeWU, Props, State) => Unit = (scope, props, state) => {}
protected def onComponentDidUpdate: (ScopeM, Props, State) => Unit = (scope, props, state) => {}
protected def onComponentWillUnMount: ScopeM => Unit = scope => {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment