Skip to content

Commit ea8329d

Browse files
committed
Add Callback and CallbackTo, integrate everywhere
Fixes #145
1 parent 79731e6 commit ea8329d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+758
-593
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package japgolly.scalajs.react
2+
3+
import scala.scalajs.js
4+
import js.{undefined, UndefOr, Function0 => JFn0, Function1 => JFn1}
5+
6+
/**
7+
* A callback with no return value. Equivalent to `() => Unit`.
8+
*
9+
* @see CallbackTo
10+
*/
11+
object Callback {
12+
@inline def apply(f: => Unit): Callback =
13+
CallbackTo(f)
14+
15+
@inline def lift(f: () => Unit): Callback =
16+
CallbackTo lift f
17+
18+
@inline def lazily(f: => Callback): Callback =
19+
Callback(f.runNow())
20+
21+
/** A callback that does nothing. */
22+
val empty: Callback =
23+
CallbackTo.pure(())
24+
}
25+
26+
object CallbackTo {
27+
@inline def apply[A](f: => A): CallbackTo[A] =
28+
new CallbackTo(() => f)
29+
30+
@inline def lift[A](f: () => A): CallbackTo[A] =
31+
new CallbackTo(f)
32+
33+
@inline def pure[A](a: A): CallbackTo[A] =
34+
new CallbackTo(() => a)
35+
}
36+
37+
/**
38+
* A function to be executed later, usually by scalajs-react in response to some kind of event.
39+
*
40+
* The purpose of this class is to lift effects into the type system, and use the compiler to ensure safety around
41+
* callbacks (without depending on an external library like Scalaz).
42+
*
43+
* `() => Unit` is replaced by `Callback`.
44+
* Similarly, `ReactEvent => Unit` is replaced by `ReactEvent => Callback`.
45+
*
46+
* @tparam A The type of result produced when the callback is invoked.
47+
*
48+
* @since 0.10.0
49+
*/
50+
final class CallbackTo[A] private[react] (private[CallbackTo] val f: () => A) extends AnyVal {
51+
52+
/**
53+
* Executes this callback, on the current thread, right now, blocking until complete.
54+
*
55+
* In most cases, this type is passed to scalajs-react such that you don't need to call this method yourself.
56+
*
57+
* Exceptions will not be caught. Use [[attempt]] to catch thrown exceptions.
58+
*/
59+
@inline def runNow(): A =
60+
f()
61+
62+
def map[B](g: A => B): CallbackTo[B] =
63+
new CallbackTo(() => g(f()))
64+
65+
def flatMap[B](g: A => CallbackTo[B]): CallbackTo[B] =
66+
new CallbackTo(() => g(f()).f())
67+
68+
def flatten[B](implicit ev: A =:= CallbackTo[B]): CallbackTo[B] =
69+
flatMap(ev)
70+
71+
/**
72+
* Alias for `flatMap`.
73+
*/
74+
@inline def >>=[B](g: A => CallbackTo[B]): CallbackTo[B] =
75+
flatMap(g)
76+
77+
/**
78+
* Same as `flatMap` and `>>=`, but allows arguments to appear in reverse order.
79+
*
80+
* i.e. `f >>= g` is the same as `g =<<: f`
81+
*/
82+
@inline def =<<:[B](g: A => CallbackTo[B]): CallbackTo[B] =
83+
flatMap(g)
84+
85+
/**
86+
* Sequence a callback to run after this, discarding any value produced by this.
87+
*/
88+
def >>[B](runNext: CallbackTo[B]): CallbackTo[B] =
89+
//if (isEmpty_?) runNext else
90+
//flatMap(_ => runNext)
91+
new CallbackTo(() => {f(); runNext.f()})
92+
93+
/**
94+
* Sequence a callback to run before this, discarding any value produced by it.
95+
*/
96+
@inline def <<[B](runBefore: CallbackTo[B]): CallbackTo[A] =
97+
runBefore >> this
98+
99+
/**
100+
* Discard the value produced by this callback.
101+
*/
102+
def void: Callback =
103+
map(_ => ())
104+
105+
def conditionally(cond: => Boolean): CallbackTo[UndefOr[A]] =
106+
CallbackTo(if (cond) f() else undefined)
107+
108+
/**
109+
* Wraps this callback in a try-catch and returns either the result or the exception if one occurs.
110+
*/
111+
def attempt: CallbackTo[Either[Throwable, A]] =
112+
CallbackTo(
113+
try Right(f())
114+
catch { case t: Throwable => Left(t) }
115+
)
116+
117+
/**
118+
* Convenience-method to run additional code after this callback.
119+
*/
120+
def thenRun[B](runNext: => B): CallbackTo[B] =
121+
this >> CallbackTo(runNext)
122+
123+
/**
124+
* Convenience-method to run additional code before this callback.
125+
*/
126+
def precedeWith(runFirst: => Unit): CallbackTo[A] =
127+
this << Callback(runFirst)
128+
129+
/**
130+
* Wraps this callback in a try-finally block such that given code runs after the callback completes, be it in error
131+
* or success.
132+
*/
133+
def finallyRun(runFinally: => Unit): CallbackTo[A] =
134+
CallbackTo(try f() finally runFinally)
135+
136+
@inline def toScalaFunction: () => A =
137+
f
138+
139+
def toJsFunction: JFn0[A] =
140+
f
141+
142+
def toJsFunction1: JFn1[Any, A] =
143+
(_: Any) => f()
144+
145+
def toJsCallback: UndefOr[JFn0[A]] =
146+
unlessEmpty(toJsFunction)
147+
148+
def isEmpty_? : Boolean =
149+
f eq Callback.empty.f
150+
151+
def unlessEmpty[B](b: => B): UndefOr[B] =
152+
if (isEmpty_?)
153+
undefined
154+
else
155+
b
156+
157+
type TypeEv[T] = CallbackTo[A] =:= CallbackTo[T]
158+
159+
private def bool2(b: CallbackTo[Boolean])(op: (() => Boolean, () => Boolean) => Boolean)(implicit ev: TypeEv[Boolean]): CallbackTo[Boolean] = {
160+
val x = ev(this).f
161+
val y = b.f
162+
CallbackTo(op(x, y))
163+
}
164+
165+
/**
166+
* Creates a new callback that returns `true` when both this and the given callback return `true`.
167+
*/
168+
def &&(b: CallbackTo[Boolean])(implicit ev: TypeEv[Boolean]): CallbackTo[Boolean] =
169+
bool2(b)(_() && _())
170+
171+
/**
172+
* Creates a new callback that returns `true` when either this or the given callback return `true`.
173+
*/
174+
def ||(b: CallbackTo[Boolean])(implicit ev: TypeEv[Boolean]): CallbackTo[Boolean] =
175+
bool2(b)(_() || _())
176+
177+
/**
178+
* Negates the callback result (so long as its boolean).
179+
*/
180+
def !(implicit ev: A =:= Boolean): CallbackTo[Boolean] =
181+
map(!_)
182+
}

core/src/main/scala/japgolly/scalajs/react/CompState.scala

+31-14
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package japgolly.scalajs.react
22

3-
import scala.scalajs.js.{Function => JFn, undefined}
4-
53
/**
64
* Generic read & write access to a component's state, (whatever the type of state might be).
75
*/
86
abstract class CompStateAccess[-C, S] {
97
def state(c: C): S
10-
def setState(c: C, s: S, cb: OpCallback): Unit
8+
def setState(c: C, s: S, cb: Callback): Callback
119
}
1210

1311
object CompStateAccess {
@@ -23,13 +21,13 @@ object CompStateAccess {
2321
}
2422

2523
object Focus extends HK[CompStateFocus] {
26-
override def state(c: C) = c.get()
27-
override def setState(c: C, s: S, cb: OpCallback) = c.set(s, cb)
24+
override def state(c: C) = c.get()
25+
override def setState(c: C, s: S, cb: Callback) = c.set(s, cb)
2826
}
2927

3028
object SS extends HK[ComponentScope_SS] {
31-
override def state(c: C) = c._state.v
32-
override def setState(c: C, s: S, cb: OpCallback) = c._setState(WrapObj(s), cb.map[JFn](f => f))
29+
override def state(c: C) = c._state.v
30+
override def setState(c: C, s: S, cb: Callback) = Callback(c._setState(WrapObj(s), cb.toJsCallback))
3331
}
3432

3533
@inline implicit def focus[S]: CompStateAccess[CompStateFocus[S], S] =
@@ -51,20 +49,39 @@ object CompStateAccess {
5149
@inline def state(implicit C: CC): S =
5250
C.state(_c)
5351

54-
@inline def setState(s: S, cb: OpCallback = undefined)(implicit C: CC): Unit =
52+
/**
53+
* Creates a callback that always returns the latest state when run.
54+
*/
55+
def stateCB(implicit C: CC): CallbackTo[S] =
56+
CallbackTo(state)
57+
58+
def setState(s: S, cb: Callback = Callback.empty)(implicit C: CC): Callback =
5559
C.setState(_c, s, cb)
5660

57-
@inline def modState(f: S => S, cb: OpCallback = undefined)(implicit C: CC): Unit =
58-
setState(f(state), cb)
61+
def setStateCB(s: CallbackTo[S], cb: Callback = Callback.empty)(implicit C: CC): Callback =
62+
s >>= (setState(_, cb))
63+
64+
def _setState[I](f: I => S, cb: Callback = Callback.empty)(implicit C: CC): I => Callback =
65+
i => C.setState(_c, f(i), cb)
66+
67+
def modState(f: S => S, cb: Callback = Callback.empty)(implicit C: CC): Callback =
68+
stateCB >>= (s => setState(f(s), cb))
69+
//Callback lazily setState(f(state), cb)
70+
71+
def modStateCB(f: S => CallbackTo[S], cb: Callback = Callback.empty)(implicit C: CC): Callback =
72+
stateCB >>= (s => setStateCB(f(s), cb))
73+
74+
def _modState[I](f: I => S => S, cb: Callback = Callback.empty)(implicit C: CC): I => Callback =
75+
i => modState(f(i), cb)
5976

6077
def lift(implicit C: CC) = new CompStateFocus[S](
6178
() => _c.state,
62-
(a: S, cb: OpCallback) => _c.setState(a, cb))
79+
(a: S, cb: Callback) => _c.setState(a, cb))
6380

6481
/** Zoom-in on a subset of the state. */
6582
def zoom[T](f: S => T)(g: (S, T) => S)(implicit C: CC) = new CompStateFocus[T](
6683
() => f(_c.state),
67-
(b: T, cb: OpCallback) => _c.setState(g(_c.state, b), cb))
84+
(b: T, cb: Callback) => _c.setState(g(_c.state, b), cb))
6885
}
6986
}
7087

@@ -74,9 +91,9 @@ object CompStateAccess {
7491
* @tparam S The type of state.
7592
*/
7693
final class CompStateFocus[S] private[react](val get: () => S,
77-
val set: (S, OpCallback) => Unit)
94+
val set: (S, Callback) => Callback)
7895

7996
object CompStateFocus {
80-
@inline def apply[S](get: () => S)(set: (S, OpCallback) => Unit): CompStateFocus[S] =
97+
@inline def apply[S](get: () => S)(set: (S, Callback) => Callback): CompStateFocus[S] =
8198
new CompStateFocus(get, set)
8299
}

core/src/main/scala/japgolly/scalajs/react/Internal.scala

+6-11
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,17 @@ import scala.scalajs.js._
44

55
private[react] object Internal {
66

7-
final class FnResults[R](aa: => R, bb: => R) {
8-
lazy val a = aa
9-
lazy val b = bb
10-
}
11-
12-
final class FnComposer[R](m: FnResults[R] => R) {
7+
final class FnComposer[R](compose: (=> R, => R) => R) {
138
def apply[A](uf: UndefOr[A => R], g: A => R) =
14-
uf.fold(g)(f => a => m(new FnResults(f(a), g(a))))
9+
uf.fold(g)(f => a => compose(f(a), g(a)))
1510

1611
def apply[A, B](uf: UndefOr[(A, B) => R], g: (A, B) => R) =
17-
uf.fold(g)(f => (a,b) => m(new FnResults(f(a,b), g(a,b))))
12+
uf.fold(g)(f => (a, b) => compose(f(a, b), g(a, b)))
1813

1914
def apply[A, B, C](uf: UndefOr[(A, B, C) => R], g: (A, B, C) => R) =
20-
uf.fold(g)(f => (a,b,c) => m(new FnResults(f(a,b,c), g(a,b,c))))
15+
uf.fold(g)(f => (a, b, c) => compose(f(a, b, c), g(a, b, c)))
2116
}
2217

23-
val fcUnit = new FnComposer[Unit](r => {r.a; r.b})
24-
val fcEither = new FnComposer[Boolean](r => r.a || r.b)
18+
val fcUnit = new FnComposer[Callback] (_ >> _)
19+
val fcEither = new FnComposer[CallbackTo[Boolean]](_ || _)
2520
}

core/src/main/scala/japgolly/scalajs/react/React.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ trait ComponentScope_M[+Node <: TopNode] extends Object {
221221
* Can be invoked on any mounted component when you know that some deeper aspect of the component's state has
222222
* changed without using this.setState().
223223
*/
224-
def forceUpdate(): Unit = js.native
224+
@JSName("forceUpdate") private[react] def _forceUpdate(): Unit = js.native
225225
}
226226

227227
/** Type of an unmounted component's `this` scope. */

0 commit comments

Comments
 (0)