Skip to content

Commit

Permalink
Delete react class spec (#29)
Browse files Browse the repository at this point in the history
* Compile

* Fix not setting default initial state

* Refactor

* Refactor

* Rename Context to Self

* Rename Context to Self

* Update README.md

* Update README.md

* Update README.md

* Update ContainerComponents.scala

* Refactor

* Try Firefox 52.0

* v0.10.0

Close #19
  • Loading branch information
shogowada committed Apr 20, 2017
1 parent 305cdb2 commit 1f742aa
Show file tree
Hide file tree
Showing 20 changed files with 398 additions and 472 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jdk:
- oraclejdk8

addons:
firefox: "latest"
firefox: "52.0"

before_script:
- wget https://github.com/mozilla/geckodriver/releases/download/v0.15.0/geckodriver-v0.15.0-linux64.tar.gz
Expand Down
26 changes: 15 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,32 @@ Optionally include react-router and react-redux facades, too.

```scala
import io.github.shogowada.scalajs.reactjs.VirtualDOM._

class HelloWorld extends StatelessReactClassSpec[HelloWorld.WrappedProps] {
override def render() = <.div(^.id := "hello-world")(s"Hello, ${props.wrapped.name}!")
}
import io.github.shogowada.scalajs.reactjs.{React, ReactDOM}
import org.scalajs.dom

object HelloWorld {
case class WrappedProps(name: String)

def apply() = new HelloWorld()
private lazy val reactClass = React.createClass[WrappedProps, Unit](
render = (self) => <.div(^.id := "hello-world")(s"Hello, ${self.props.wrapped.name}!")
)

def apply() = reactClass
}

val mountNode = dom.document.getElementById("mount-node")
ReactDOM.render(<(HelloWorld())(^.wrapped := HelloWorld.WrappedProps("World"))(), mountNode)
```

You can also use a pure function to render:
You can also render with just a function:

```scala
import io.github.shogowada.scalajs.reactjs.ReactDOM
import io.github.shogowada.scalajs.reactjs.VirtualDOM._
import org.scalajs.dom

object HelloWorld {
def apply(name: String): ReactElement = <.div(^.id := "hello-world")(s"Hello, ${name}!")
def apply(name: String) = <.div(^.id := "hello-world")(s"Hello, ${name}!")
}

val mountNode = dom.document.getElementById("mount-node")
Expand All @@ -44,10 +48,10 @@ ReactDOM.render(HelloWorld("World"), mountNode)
2. Depend on the libraries.
```
libraryDependencies ++= Seq(
"io.github.shogowada" %%% "scalajs-reactjs" % "0.9.1", // For react facade
"io.github.shogowada" %%% "scalajs-reactjs-router-dom" % "0.9.1", // Optional. For react-router-dom facade
"io.github.shogowada" %%% "scalajs-reactjs-redux" % "0.9.1", // Optional. For react-redux facade
"io.github.shogowada" %%% "scalajs-reactjs-redux-devtools" % "0.9.1" // Optional. For redux-devtools facade
"io.github.shogowada" %%% "scalajs-reactjs" % "0.10.0", // For react facade
"io.github.shogowada" %%% "scalajs-reactjs-router-dom" % "0.10.0", // Optional. For react-router-dom facade
"io.github.shogowada" %%% "scalajs-reactjs-redux" % "0.10.0", // Optional. For react-redux facade
"io.github.shogowada" %%% "scalajs-reactjs-redux-devtools" % "0.10.0" // Optional. For redux-devtools facade
)
```

Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ publishArtifact := false
val commonSettings = Seq(
organization := "io.github.shogowada",
name := "scalajs-reactjs",
version := "0.9.2-SNAPSHOT",
version := "0.10.0",
licenses := Seq("MIT" -> url("https://opensource.org/licenses/MIT")),
homepage := Some(url("https://github.com/shogowada/scalajs-reactjs")),
scalaVersion := "2.12.1",
Expand Down
130 changes: 127 additions & 3 deletions core/src/main/scala/io/github/shogowada/scalajs/reactjs/React.scala
Original file line number Diff line number Diff line change
@@ -1,15 +1,139 @@
package io.github.shogowada.scalajs.reactjs

import io.github.shogowada.scalajs.reactjs.classes.ReactClass
import io.github.shogowada.scalajs.reactjs.classes.specs.ReactClassSpec
import io.github.shogowada.scalajs.reactjs.elements.ReactElement
import io.github.shogowada.scalajs.reactjs.utils.Utils

import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport

object React {
def createClass[Props, State](spec: ReactClassSpec[Props, State]): ReactClass =
NativeCreateReactClass(spec.asNative)

type Render[WrappedProps] = Props[WrappedProps] => ReactElement

def renderToNative[WrappedProps](render: Render[WrappedProps]): js.Function1[js.Dynamic, ReactElement] =
(nativeProps: js.Dynamic) => render(Props(nativeProps))

val WrappedProperty = "wrapped"

def wrap[Wrapped](wrapped: Wrapped): js.Dynamic =
js.Dynamic.literal(WrappedProperty -> wrapped.asInstanceOf[js.Any])

def unwrap[Wrapped](nativeWrapped: js.Dynamic): Wrapped =
nativeWrapped.selectDynamic(WrappedProperty).asInstanceOf[Wrapped]

def stateToNative[State](state: State): js.Dynamic = wrap(state)

def stateFromNative[State](nativeState: js.Dynamic): State = unwrap[State](nativeState)

case class Props[Wrapped](native: js.Dynamic) {
def wrapped: Wrapped = native.wrapped.asInstanceOf[Wrapped]
def children: ReactElement = native.children.asInstanceOf[ReactElement]
}

case class Self[WrappedProps, State](native: js.Dynamic) {

def props: Props[WrappedProps] = Props(native.props)
def state: State = stateFromNative(native.state)

def forceUpdate(callback: () => Unit): Unit = native.forceUpdate(callback)

def forceUpdate(): Unit = native.forceUpdate()

def setState(state: State): Unit = native.setState(stateToNative(state))

def setState(mapper: State => State): Unit = {
val nativeMapper = (nativeState: js.Dynamic) => {
stateToNative(mapper(stateFromNative(nativeState)))
}
native.setState(nativeMapper)
}

def setState(mapper: (State, Props[WrappedProps]) => State): Unit = {
val nativeMapper = (nativeState: js.Dynamic, nativeProps: js.Dynamic) => {
stateToNative(mapper(
stateFromNative(nativeState),
Props(nativeProps)
))
}
native.setState(nativeMapper)
}
}

def createClass[WrappedProps, State](
render: Self[WrappedProps, State] => ReactElement,
displayName: String = null,
componentWillMount: Self[WrappedProps, State] => Unit = null,
componentDidMount: Self[WrappedProps, State] => Unit = null,
componentWillReceiveProps: (Self[WrappedProps, State], Props[WrappedProps]) => Unit = null,
shouldComponentUpdate: (Self[WrappedProps, State], Props[WrappedProps], State) => Boolean =
(self: Self[WrappedProps, State], nextProps: Props[WrappedProps], nextState: State) => {
self.props.wrapped != nextProps.wrapped || self.state != nextState ||
!Utils.shallowEqual(self.props.native, nextProps.native, WrappedProperty)
},
componentWillUpdate: (Self[WrappedProps, State], Props[WrappedProps], State) => Unit = null,
componentDidUpdate: (Self[WrappedProps, State], Props[WrappedProps], State) => Unit = null,
componentWillUnmount: Self[WrappedProps, State] => Unit = null,
getInitialState: Self[WrappedProps, State] => State = null
): ReactClass = {
val spec = js.Dynamic.literal(
"shouldComponentUpdate" -> js.ThisFunction.fromFunction3((native: js.Dynamic, nextProps: js.Dynamic, nextState: js.Dynamic) => {
shouldComponentUpdate(Self(native), Props(nextProps), stateFromNative(nextState))
}),
"getInitialState" -> js.ThisFunction.fromFunction1((native: js.Dynamic) => {
if (getInitialState != null) {
stateToNative(getInitialState(Self(native)))
} else {
stateToNative(())
}
}),
"render" -> js.ThisFunction.fromFunction1((native: js.Dynamic) => {
render(Self(native))
})
)

if (displayName != null) {
spec.updateDynamic("displayName")(displayName)
}

if (componentWillMount != null) {
spec.updateDynamic("componentWillMount")(js.ThisFunction.fromFunction1((native: js.Dynamic) => {
componentWillMount(Self(native))
}))
}

if (componentDidMount != null) {
spec.updateDynamic("componentDidMount")(js.ThisFunction.fromFunction1((native: js.Dynamic) => {
componentDidMount(Self(native))
}))
}

if (componentWillReceiveProps != null) {
spec.updateDynamic("componentWillReceiveProps")(js.ThisFunction.fromFunction2((native: js.Dynamic, nextProps: js.Dynamic) => {
componentWillReceiveProps(Self(native), Props(nextProps))
}))
}

if (componentWillUpdate != null) {
spec.updateDynamic("componentWillUpdate")(js.ThisFunction.fromFunction3((native: js.Dynamic, nextProps: js.Dynamic, nextState: js.Dynamic) => {
componentWillUpdate(Self(native), Props(nextProps), stateFromNative(nextState))
}))
}

if (componentDidUpdate != null) {
spec.updateDynamic("componentDidUpdate")(js.ThisFunction.fromFunction3((native: js.Dynamic, prevProps: js.Dynamic, prevState: js.Dynamic) => {
componentDidUpdate(Self(native), Props(prevProps), stateFromNative(prevState))
}))
}

if (componentWillUnmount != null) {
spec.updateDynamic("componentWillUnmount")(js.ThisFunction.fromFunction1((native: js.Dynamic) => {
componentWillUnmount(Self(native))
}))
}

NativeCreateReactClass(spec)
}

def createElement(tagName: String, attributes: js.Any, children: js.Any*): ReactElement =
NativeReact.createElement(tagName, attributes, children: _*)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package io.github.shogowada.scalajs.reactjs

import io.github.shogowada.scalajs.reactjs.React.Render
import io.github.shogowada.scalajs.reactjs.VirtualDOM.VirtualDOMAttributes.Type.AS_IS
import io.github.shogowada.scalajs.reactjs.VirtualDOM.VirtualDOMElements.ReactClassElementSpec
import io.github.shogowada.scalajs.reactjs.classes.ReactClass
import io.github.shogowada.scalajs.reactjs.classes.specs.ReactClassSpec
import io.github.shogowada.scalajs.reactjs.classes.specs.ReactClassSpec.Render
import io.github.shogowada.scalajs.reactjs.elements.{ReactElement, ReactHTMLElement}
import io.github.shogowada.scalajs.reactjs.events._
import io.github.shogowada.statictags.AttributeValueType.AttributeValueType
Expand Down Expand Up @@ -166,9 +165,6 @@ trait EventVirtualDOMAttributes {
trait VirtualDOM extends StaticTags {

class VirtualDOMElements extends Elements {
def apply[Props, State](reactClassSpec: ReactClassSpec[Props, State]): ReactClassElementSpec =
this.apply(React.createClass(reactClassSpec))

def apply(reactClass: ReactClass): ReactClassElementSpec =
ReactClassElementSpec(reactClass)
}
Expand Down Expand Up @@ -218,7 +214,6 @@ trait VirtualDOM extends StaticTags {
}

case class ReactClassAttributeSpec(name: String) extends AttributeSpec {
def :=[Props, State](value: ReactClassSpec[Props, State]): Attribute[ReactClass] = this := React.createClass(value)
def :=(value: ReactClass): Attribute[ReactClass] = Attribute(name, value, AS_IS)
}

Expand All @@ -230,7 +225,7 @@ trait VirtualDOM extends StaticTags {

case class RenderAttributeSpec(name: String) extends AttributeSpec {
def :=[WrappedProps](render: Render[WrappedProps]) = {
val nativeRender = ReactClassSpec.renderToNative(render)
val nativeRender = React.renderToNative(render)
Attribute(name, nativeRender, AS_IS)
}
}
Expand Down
Loading

0 comments on commit 1f742aa

Please sign in to comment.