How would I create a Modal component from the React Bootstrap library using the Scala.js React library?
The fact that a Modal has to change state (show = true/false) to show/hide the dialog made the solution non-trivial. I resolved this by wrapping it in a component that had a Boolean state that could be changed - and when it needs to be shown/hidden I change state with effects impure.
Another issue was that if the Modal has buttons that need to change the state their event handlers need access to this state somehow. I resolved this issue by giving users of the component access to the Backend of the component on creation.
Here is my implementation of the Modal:
class Modal(bs: BackendScope[Unit, Boolean], onHide: => Unit, children: Modal => Seq[ChildArg]) {
def render(show: Boolean) = {
val props = (new js.Object).asInstanceOf[Modal.Props]
props.show = show
props.onHide = () => {
dismiss()
onHide
}
Modal.component(props)(children(this): _*)
}
def dismiss() = {
bs.withEffectsImpure.setState(false)
}
}
object Modal {
@JSImport("react-bootstrap", "Modal")
@js.native
object RawComponent extends js.Object
@js.native
trait Props extends js.Object {
var show: Boolean = js.native
var onHide: js.Function0[Unit] = js.native
}
val component = JsComponent[Props, Children.Varargs, Null](RawComponent)
type Unmounted = Scala.Unmounted[Unit, Boolean, Modal]
def apply(onHide: => Unit = Unit)(children: Modal => Seq[ChildArg]): Unmounted = {
val component = ScalaComponent.builder[Unit]("Modal")
.initialState(true)
.backend(new Modal(_, onHide, children))
.renderBackend
.build
component()
}
}
And a Dialog object that uses it:
object Dialog {
object Response extends Enumeration {
type Response = Value
val OK, CANCEL = Value
}
import Response._
def prompt(title: String, body: String, okText: String): Future[Response] = {
// Add an element into which we render the dialog
val element = dom.document.body.appendChild(div(id := "dialog").render).asInstanceOf[Element]
// Create a promise of a return and a method to send it
val p = Promise[Response]
def respond(ret: Response) = {
// Remove the containing element and return the response
dom.document.body.removeChild(element)
p.success(ret)
}
Modal(respond(Response.CANCEL)) { modal =>
// Function to dismiss the dialog and respond
def quit(ret: Response) = {
modal.dismiss()
respond(ret)
}
// Create the components for our Modal
Seq(
ModalHeader(true,
ModalTitle(title)
),
ModalBody(body),
ModalFooter(
Button(variant = "secondary", onClick = () => { quit(Response.CANCEL) })("Cancel"),
Button(variant = "primary", onClick = () => { quit(Response.OK) })(okText)
)
)
}.renderIntoDOM(element).backend
p.future
}
}