Skip to content

Commit 505b22d

Browse files
committed
Added ExternalVar
Closes #80
1 parent 78f5f79 commit 505b22d

File tree

6 files changed

+118
-6
lines changed

6 files changed

+118
-6
lines changed

doc/CHANGELOG-0.8.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# 0.8.1
22

33
* Added `ReactComponentB.static` to quickly create components with unchanging content.
4+
* Added `ExternalVar` to `extra` module. ([Live demo](http://japgolly.github.io/scalajs-react/))
45

56
##### Scalatags
67

extra/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ A collection of functionality that provides benefit when using scalajs-react.
66
- [Router](https://github.com/japgolly/scalajs-react/blob/master/extra/ROUTER.md)
77
- Component Mixins:
88
- [Broadcaster and Listenable](#broadcaster-and-listenable)
9+
- [ExternalVar](#externalvar)
910
- [LogLifecycle](#loglifecycle)
1011
- [OnUnmount](#onunmount)
1112
- [SetInterval](#setinterval)
@@ -38,6 +39,14 @@ object HelloBroadcaster extends Broadcaster[String] {
3839
* `Broadcaster`: Provides a `protected def broadcast(a: A): Unit` for easy message broadcasting.
3940

4041

42+
ExternalVar
43+
===========
44+
Provides a component with safe R/W access to an external variable.
45+
46+
A live demo with accompanying code is available on
47+
[http://japgolly.github.io/scalajs-react](http://japgolly.github.io/scalajs-react/).
48+
49+
4150
LogLifecycle
4251
============
4352
This will cause logging to occur at React component lifecycle stages.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package japgolly.scalajs.react.extra
2+
3+
import japgolly.scalajs.react._, ScalazReact._
4+
import monocle.Lens
5+
import scalaz.effect.IO
6+
7+
/**
8+
* Provides a component with safe R/W access to an external variable.
9+
*/
10+
final class ExternalVar[A](val value: A, val set: A => IO[Unit]) {
11+
12+
def mod(f: A => A): IO[Unit] =
13+
set(f(value))
14+
15+
def setL[B](l: Lens[A, B]): B => IO[Unit] =
16+
b => set(l.set(b)(value))
17+
18+
def modL[B](l: Lens[A, B])(f: B => B): IO[Unit] =
19+
set(l.modify(f)(value))
20+
}
21+
22+
23+
object ExternalVar {
24+
@inline def apply[A](value: A)(set: A => IO[Unit]): ExternalVar[A] =
25+
new ExternalVar(value, set)
26+
27+
@inline def state[S]($: ComponentStateFocus[S]): ExternalVar[S] =
28+
new ExternalVar($.state, $.setStateIO(_))
29+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package ghpages.examples
2+
3+
import ghpages.examples.util.SingleSide
4+
import japgolly.scalajs.react._, vdom.prefix_<^._, ScalazReact._, MonocleReact._
5+
import japgolly.scalajs.react.extra.ExternalVar
6+
import monocle.macros._
7+
8+
object ExternalVarExample {
9+
10+
def content = SingleSide.Content(source, Main())
11+
12+
val source =
13+
"""
14+
|@Lenses
15+
|case class Name(firstName: String, surname: String)
16+
|
17+
|val Main = ReactComponentB[Unit]("ExternalVar example")
18+
| .initialState(Name("John", "Wick"))
19+
| .render { $ =>
20+
| val name = $.state
21+
| val firstNameEV = ExternalVar.state($.focusStateL(Name.firstName))
22+
| val surnameEV = ExternalVar.state($.focusStateL(Name.surname))
23+
| <.div(
24+
| <.label("First name:", NameChanger(firstNameEV)),
25+
| <.label("Surname:", NameChanger(surnameEV )),
26+
| <.p(s"My name is ${name.surname}, ${name.firstName} ${name.surname}."))
27+
| }
28+
| .build
29+
|
30+
|val NameChanger = ReactComponentB[ExternalVar[String]]("Name changer")
31+
| .render { evar =>
32+
| def onChange = (event: ReactEventI) => evar.set(event.target.value)
33+
| <.input(
34+
| ^.`type` := "text",
35+
| ^.value := evar.value,
36+
| ^.onChange ~~> onChange)
37+
| }
38+
| .build""".stripMargin
39+
40+
41+
@Lenses
42+
case class Name(firstName: String, surname: String)
43+
44+
val Main = ReactComponentB[Unit]("ExternalVar example")
45+
.initialState(Name("John", "Wick"))
46+
.render { $ =>
47+
val name = $.state
48+
val firstNameEV = ExternalVar.state($.focusStateL(Name.firstName))
49+
val surnameEV = ExternalVar.state($.focusStateL(Name.surname))
50+
<.div(
51+
<.label("First name:", NameChanger(firstNameEV)),
52+
<.label("Surname:", NameChanger(surnameEV )),
53+
<.p(s"My name is ${name.surname}, ${name.firstName} ${name.surname}."))
54+
}
55+
.buildU
56+
57+
val NameChanger = ReactComponentB[ExternalVar[String]]("Name changer")
58+
.render { evar =>
59+
def onChange = (event: ReactEventI) => evar.set(event.target.value)
60+
<.input(
61+
^.`type` := "text",
62+
^.value := evar.value,
63+
^.onChange ~~> onChange)
64+
}
65+
.build
66+
}

gh-pages/src/main/scala/ghpages/pages/ExamplesPage.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ object Example {
1717
case object Animation extends Example("Animation", AnimationExample.content)
1818
case object PictureApp extends Example("AjaxPictureApp", PictureAppExample.content)
1919
case object Scalaz extends Example("Todo List (Scalaz)", ScalazExample.content)
20+
case object ExternalVar extends Example("ExternalVar", ExternalVarExample.content)
2021

21-
val values = Vector[Example](Hello, Timer, Todo, Scalaz, Refs, ProductTable, Animation, PictureApp)
22+
val values = Vector[Example](Hello, Timer, Todo, Scalaz, Refs, ExternalVar, ProductTable, Animation, PictureApp)
2223
}
2324

2425
object ExamplesPage {

project/Build.scala

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ object ScalajsReact extends Build {
8888
def extModuleName(shortName: String): PE =
8989
_.settings(name := s"ext-$shortName")
9090

91+
def macroParadisePlugin =
92+
compilerPlugin("org.scalamacros" % "paradise" % "2.0.1" cross CrossVersion.full)
93+
9194
// ==============================================================================================
9295
lazy val root = Project("root", file("."))
9396
.aggregate(core, test, scalaz71, monocle, extra, ghpages)
@@ -128,21 +131,24 @@ object ScalajsReact extends Build {
128131
lazy val monocle = project
129132
.configure(commonSettings, publicationSettings, extModuleName("monocle"))
130133
.dependsOn(core, scalaz71)
131-
.settings(
132-
libraryDependencies += "com.github.japgolly.fork.monocle" %%% "monocle-core" % "1.0.1")
134+
.settings(libraryDependencies += monocleLib("core"))
135+
136+
def monocleLib(name: String) =
137+
"com.github.japgolly.fork.monocle" %%%! s"monocle-$name" % "1.0.1"
133138

134139
// ==============================================================================================
135140
lazy val extra = project
136141
.configure(commonSettings, publicationSettings)
137-
.dependsOn(core, scalaz71)
138-
.settings(
139-
name := "extra")
142+
.dependsOn(core, scalaz71, monocle)
143+
.settings(name := "extra")
140144

141145
// ==============================================================================================
142146
lazy val ghpages = Project("gh-pages", file("gh-pages"))
143147
.dependsOn(core, scalaz71, extra, monocle)
144148
.configure(commonSettings, useReactJs(), preventPublication)
145149
.settings(
150+
libraryDependencies += monocleLib("macro"),
151+
addCompilerPlugin(macroParadisePlugin),
146152
emitSourceMaps := false,
147153
artifactPath in (Compile, fullOptJS) := file("gh-pages/res/ghpages.js"))
148154
}

0 commit comments

Comments
 (0)