-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
409 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
Points: | ||
1. Propagation richtung Root | ||
2. How to do toModel() | ||
|
||
Idee: | ||
1. If LVar changed -> Propagate using toModel() Function -> where are the dependencies? | ||
2. Propagate Signals & toViews() -> multiple Points of Change or just from root and evaluate vars multiple times | ||
|
||
Signals vs Lenses | ||
|
||
Signals Lenses | ||
n -> 1 1 <-> 1 | ||
expr(Vars) -> Signal expr1(LVar) -> LVar | ||
LVar <- expr2(LVar) | ||
Signal = Signal {...} LVar[View] = LVar[Model].applyLens(...) / Lens(LVar) z.B. meters = yards.applyLens(new MulLens(0.9144)) | ||
|
||
What happens when an update occurs? | ||
|
||
Signal => all dependencies are reevaluated | ||
Var => inherits from Signal, but updates can be triggered manually | ||
LVar => inherits from Var AND | ||
all LVars connected via Lenses are updated using toView() OR toModel() | ||
|
||
How to decide which method to call? | ||
|
||
1. no change in dependency structure | ||
toView() "trivially" as Signal expression | ||
toModel() if "incoming" is connected via Lens, call toModel() -> How do we know that? & Do Vars have "incoming"? | ||
|
||
2. extend dependencies bidirectional, if LVar in "incoming" && "outgoing" we have found a Lens -> What's the orientation? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
<title>ConversionTest</title> | ||
</head> | ||
<body> | ||
<section class="conversions"> | ||
<noscript><div style="padding:2em;text-align:center;font-size:2em"> | ||
Please enable JavaScript! | ||
</div></noscript> | ||
</section> | ||
|
||
<script type="text/javascript" src="JSPATH"></script> | ||
<script type="text/javascript"> | ||
UnitConversion() | ||
</script> | ||
</body> | ||
</html> |
115 changes: 115 additions & 0 deletions
115
Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package copl | ||
|
||
import org.scalajs.dom.html.{Input, Select} | ||
import org.scalajs.dom.document | ||
import reactives.default.* | ||
import reactives.extra.lenses.* | ||
import reactives.extra.lenses.toSignalLens | ||
import scalatags.JsDom | ||
import scalatags.JsDom.all.* | ||
import scalatags.JsDom.TypedTag | ||
|
||
import scala.scalajs.js.annotation.JSExportTopLevel | ||
|
||
object ConversionTest { | ||
|
||
@JSExportTopLevel("UnitConversion") | ||
def run(): Unit = main(Array.empty[String]) | ||
|
||
def main(args: Array[String]): Unit = { | ||
val temperatureConverter = unitConverter() | ||
document.body.replaceChild(temperatureConverter.render, document.body.firstChild) | ||
() | ||
} | ||
|
||
|
||
/** | ||
* Contains all supported units with their conversion from Celsius. To add support for a unit, simply add it to the | ||
* enum with the lens representing the corresponding conversion | ||
* @param lens the conversion lens from Celsius | ||
*/ | ||
enum TempConversion(val lens: BijectiveLens[Double, Double]): | ||
//All Conversions are given from Celsius | ||
case C extends TempConversion(new NeutralLens) | ||
case K extends TempConversion(new AddLens(274.15)) | ||
case L extends TempConversion(new AddLens(253)) | ||
case F extends TempConversion(new MulLens(1.8).compose(new AddLens(32.0))) | ||
end TempConversion | ||
|
||
/** | ||
* Creates a lens which converts between any supported units | ||
*/ | ||
def conversionLens(from : TempConversion, to : TempConversion): BijectiveLens[Double, Double] = from.lens.inverse.compose(to.lens) | ||
|
||
/** | ||
* A demonstration of reactive lenses using a simple unit converter for temperature units | ||
*/ | ||
def unitConverter() = { | ||
|
||
//Create selection for units and convert selected units to signals | ||
val leftUnitInput: TypedTag[Select] = select(TempConversion.values.map{ unit => option(unit.toString) }) | ||
val (leftUnitEvent: Event[String], renderedLeftUnit: Select) = RenderUtil.dropDownHandler(leftUnitInput, oninput, clear = false) | ||
val leftUnitSignal: Signal[TempConversion] = leftUnitEvent.hold(init = renderedLeftUnit.value).map{TempConversion.valueOf(_)} | ||
|
||
val rightUnitInput: TypedTag[Select] = select(TempConversion.values.map { unit => option(unit.toString) }) | ||
val (rightUnitEvent: Event[String], renderedRightUnit: Select) = RenderUtil.dropDownHandler(rightUnitInput, oninput, clear = false) | ||
val rightUnitSignal: Signal[TempConversion] = rightUnitEvent.hold(init = renderedRightUnit.value).map{TempConversion.valueOf(_)} | ||
|
||
//Create the two LVars containing the left and right value using reactive lenses. | ||
val leftVar = LVar(0.0) | ||
val rightVar = leftVar.applyLens(SignalLens(Signal{conversionLens(leftUnitSignal.value, rightUnitSignal.value)})) | ||
|
||
//Create text fields and input events for the values | ||
val leftValueInput: TypedTag[Input] = input(value := leftVar.now) | ||
val (leftValueEvent: Event[String], renderedLeftValue: Input) = RenderUtil.inputFieldHandler(leftValueInput, oninput, clear = false) | ||
|
||
val rightValueInput: TypedTag[Input] = input(value := rightVar.now) | ||
val (rightValueEvent: Event[String], renderedRightValue: Input) = RenderUtil.inputFieldHandler(rightValueInput, oninput, clear = false) | ||
|
||
//Register input events as source of change for LVars | ||
leftVar.fire(leftValueEvent.map{toDoubleOr0(_)}) | ||
rightVar.fire(rightValueEvent.map{toDoubleOr0(_)}) | ||
|
||
//Observe LVars to update UI | ||
leftVar.observe{ value => RenderUtil.setInputDisplay(renderedLeftValue, value.toString) } | ||
rightVar.observe{ value => RenderUtil.setInputDisplay(renderedRightValue, value.toString) } | ||
|
||
//Combine all UI elements | ||
div(p("Unit Conversion with Lenses"), renderedLeftUnit, renderedRightUnit, br , renderedLeftValue, renderedRightValue) | ||
} | ||
|
||
/** | ||
* Returns the double represented by the string or 0 if no double is represented | ||
*/ | ||
def toDoubleOr0(str : String): Double = { | ||
try { | ||
{str.toDouble} | ||
} catch { | ||
case _ => {0.0} | ||
} | ||
} | ||
|
||
/** | ||
* A demonstration of the effect of declaration order on event execution if an event effects multiple LVars in the same | ||
* cluster. When inverting the definition of b and c, the output changes. | ||
*/ | ||
def raceConditionTest(): Unit = { | ||
val a = LVar(0) | ||
val b = a.applyLens(new AddLens(1)) | ||
val c = a.applyLens(new AddLens(2)) | ||
|
||
println("a is " + a.now.toString) | ||
println("b is " + b.now.toString) | ||
println("c is " + c.now.toString) | ||
|
||
val e = Evt[Int]() | ||
b.fire(e) | ||
c.fire(e) | ||
e.fire(3) | ||
println("\n Fire event...") | ||
println("a is " + a.now.toString) | ||
println("b is " + b.now.toString) | ||
println("c is " + c.now.toString) | ||
} | ||
|
||
} |
45 changes: 45 additions & 0 deletions
45
Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package copl | ||
|
||
import org.scalajs.dom.UIEvent | ||
import org.scalajs.dom.html.{Input, Select} | ||
import scalatags.JsDom.all.* | ||
import reactives.operator.* | ||
import scalatags.JsDom.{Attr, TypedTag} | ||
|
||
object RenderUtil { | ||
def inputFieldHandler(tag: TypedTag[Input], attr: Attr, clear: Boolean = true): (Event[String], Input) = { | ||
val handler = Event.fromCallback(tag(attr := Event.handle[UIEvent])) | ||
val todoInputField: Input = handler.data.render | ||
|
||
// observer to prevent form submit and empty content | ||
handler.event.observe { (e: UIEvent) => | ||
e.preventDefault() | ||
if (clear) todoInputField.value = "" | ||
} | ||
|
||
// note that the accessed value is NOT a reactive, there is a name clash with the JS library :-) | ||
val inputFieldText = handler.event.map { _ => todoInputField.value.trim } | ||
|
||
(inputFieldText, todoInputField) | ||
} | ||
|
||
def dropDownHandler(tag: TypedTag[Select], attr: Attr, clear: Boolean = true): (Event[String], Select) = { | ||
val handler = Event.fromCallback(tag(attr := Event.handle[UIEvent])) | ||
val todoInputField: Select = handler.data.render | ||
|
||
// // observer to prevent form submit and empty content | ||
// handler.event.observe { (e: UIEvent) => | ||
// e.preventDefault() | ||
// if (clear) todoInputField.value = "" | ||
// } | ||
// | ||
// // note that the accessed value is NOT a reactive, there is a name clash with the JS library :-) | ||
val inputFieldText = handler.event.map { _ => todoInputField.value.trim } | ||
|
||
(inputFieldText, todoInputField) | ||
} | ||
|
||
def setInputDisplay(in : Input, text : String): Unit = { | ||
in.value = text | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.