Skip to content

Commit

Permalink
Merge branch 'master' of github.com:ThoughtWorksInc/bindable.scala in…
Browse files Browse the repository at this point in the history
…to template
  • Loading branch information
Atry committed Jan 26, 2023
2 parents b78f28d + 8b4d12f commit 23d7a9d
Show file tree
Hide file tree
Showing 23 changed files with 625 additions and 28 deletions.
3 changes: 3 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# Scala Steward: Reformat with scalafmt 3.1.2
fb6cfb8aea15a1b339e3ed69e1e96acd7df4cae6

# Scala Steward: Reformat with scalafmt 3.7.0
60bbc545deefa9146a980bab48284d4d674c4bc2
19 changes: 0 additions & 19 deletions .github/workflows/scala-steward.yml

This file was deleted.

16 changes: 10 additions & 6 deletions .github/workflows/scala.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ jobs:
strategy:
fail-fast: false
matrix:
scala:
- 2.12.15
include:
- scala: 3.2.1
- scala: 2.13.4
sbt-args: --addPluginSbtFile=project/plugins.sbt.scalajs06
- scala: 2.13.4

steps:
- uses: actions/checkout@v3
Expand All @@ -35,12 +38,13 @@ jobs:
~/.sbt/
~/.coursier/
key: |
${{ runner.os }}-${{matrix.scala}}-${{ hashFiles('**/*.sbt') }}
${{ runner.os }}-${{matrix.scala}}-
${{runner.os}}-${{matrix.scala}}-${{hashFiles('**/*.sbt')}}-${{matrix.sbt-args}}
${{runner.os}}-${{matrix.scala}}-${{hashFiles('**/*.sbt')}}-
${{runner.os}}-${{matrix.scala}}-
- name: Run tests
run: sbt ++${{ matrix.scala }} test
run: sbt ${{matrix.sbt-args}} ++${{matrix.scala}} test
- name: Publish to Maven Central Repository
env:
GITHUB_PERSONAL_ACCESS_TOKEN: ${{secrets.PERSONAL_ACCESS_TOKEN}}
if: ${{ env.GITHUB_PERSONAL_ACCESS_TOKEN != '' && github.event_name != 'pull_request' }}
run: sbt ++${{ matrix.scala }} "set every Seq(sonatypeSessionName := \"${{github.workflow}} ${{github.run_id}}-${{github.run_number}}-${{github.run_attempt}}-$$ ${{ matrix.scala }}\", publishTo := sonatypePublishToBundle.value)" publishSigned sonatypeBundleRelease
run: sbt ${{matrix.sbt-args}} ++${{matrix.scala}} "set every Seq(sonatypeSessionName := \"${{github.workflow}} ${{github.run_id}} ${{github.run_number}} ${{github.run_attempt}} ${{matrix.scala}}\", publishTo := sonatypePublishToBundle.value)" publishSigned sonatypeBundleRelease
4 changes: 2 additions & 2 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
runner.dialect = scala212source3
version = "3.6.1"
runner.dialect = scala3
version = "3.7.1"
maxColumn = 80
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2018 ThoughtWorks Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# bindable.scala
[![Latest version](https://index.scala-lang.org/thoughtworksinc/bindable.scala/bindable/latest.svg)](https://index.scala-lang.org/thoughtworksinc/bindable.scala/bindable)
[![Scaladoc](https://javadoc.io/badge/com.thoughtworks.binding/bindable_sjs0.6_2.13.svg?label=scaladoc)](https://javadoc.io/page/com.thoughtworks.binding/bindable_sjs0.6_2.13/latest/com/thoughtworks/binding/bindable/index.html)
[![Scala CI](https://github.com/ThoughtWorksInc/bindable.scala/actions/workflows/scala.yml/badge.svg)](https://github.com/ThoughtWorksInc/bindable.scala/actions/workflows/scala.yml)

**bindable.scala** is a library of type classes for creating user-friendly [Binding.scala](https://github.com/ThoughtWorksInc/Binding.scala) components.

## Motivation

When creating a component that accepts parameters or “holes”, it is difficult to determine the types of those parameters.

For example, the following component accepts two `Binding` as parameters:

```scala
@dom def myComponent1(title: Binding[String], children: Binding[BindingSeq[Node]]) = {
<div title={title.bind}>
{children.bind}
</div>
}
```

By typing parameters as `Binding`s, `myComponent1` allows partial rendering whenever the value of `title` or `children` is changed. Unfortunately, it is too verbose to use `myComponent1` for simple use cases when the parameters are constants.

```scala
// Does not compile
@dom def myUseCases1 = myComponent1("My Title", <img/>).bind

// Compiles, but too verbose
@dom def myUseCases2 = myComponent1(Constant("My Title"), Constant(Constants(<img/>))).bind
```

In this library, we introduced two type classes, `Bindable` and `BindableSeq`, to allow heterogeneous types of parameters for a component.

## Usage

### Step 1: adding the following settings into your `build.sbt`

```sbt
addCompilerPlugin("org.spire-math" %% "kind-projector" % "latest.release")

libraryDependencies += "com.thoughtworks.binding" %%% "bindable" % "latest.release"
```

### Step 2: creating a component that accepts bindable parameters

```scala
import com.thoughtworks.binding.bindable._
import org.scalajs.dom.raw._
@dom def myComponent2[Title: Bindable.Lt[?, String], Children: BindableSeq.Lt[?, Node]](title: Title, children: Children) = {
<div title={title.bind}>
{children.bindSeq}
</div>
}
```

### Step 3: using the component with any parameters that can be converted to `Binding` or `BindingSeq`


```scala
import com.thoughtworks.binding._, Binding._
@dom def myUseCases3 = myComponent2("My Title", <img/>).bind
@dom def myUseCases4 = myComponent2(Constant("My Title"), Constant(Constants(<img/>))).bind
@dom def myUseCases5 = myComponent2("My Title", <img/><img/>).bind
@dom def myUseCases6 = myComponent2("My Title", Binding(<img/><img/>)).bind
```

Unlike use cases of `myComponent1`, all the above use cases of `myComponent2` compile now, with the help of the `Bindable` and `BindableSeq` type classes.

## Links

* [Documentation](https://javadoc.io/page/com.thoughtworks.binding/bindable_sjs0.6_2.13/latest/com/thoughtworks/binding/bindable/index.html)
* [Binding.scala](https://github.com/ThoughtWorksInc/Binding.scala)
* [Limitations of the component model](https://github.com/ThoughtWorksInc/Binding.scala/issues/128)
9 changes: 9 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType}

Global / parallelExecution := false

publish / skip := true

organization in ThisBuild := "com.thoughtworks.binding"

lazy val bindable = crossProject(JVMPlatform, JSPlatform) in file(".")
1 change: 1 addition & 0 deletions js/build.sbt
22 changes: 22 additions & 0 deletions js/js.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
enablePlugins(ScalaJSBundlerPlugin)

enablePlugins(Example)

libraryDependencies += "com.thoughtworks.binding" %%% "jspromisebinding" % "12.1.1"

libraryDependencies ++= PartialFunction.condOpt(scalaBinaryVersion.value) {
case "2.13" =>
"com.yang-bo" %%% "html" % "2.0.0" % Optional
}

libraryDependencies ++= PartialFunction.condOpt(scalaBinaryVersion.value) {
case "2.13" =>
compilerPlugin("org.typelevel" %% "kind-projector" % "0.10.3")
}

requireJsDomEnv in Test := true

Test / scalacOptions ++= PartialFunction.condOpt(scalaBinaryVersion.value) {
case "2.13" =>
"-Ymacro-annotations"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.thoughtworks.binding.bindable

/** @example
* This type class is internally used in the [[org.lrng.binding.html]]
* annotation, automatically converting any compatible values into
* [[com.thoughtworks.binding.Binding.BindingSeq]], injecting into a HTML
* template.
* {{{
* import org.lrng.binding.html
* import org.scalajs.dom._
* @html
* def myBinding = <span>Single Element</span>
*
* @html
* def myBindingSeq = <span>Element 1</span><span>Element 2</span>
*
* @html
* def myBindingOrBindingSeq(singleElement: Boolean) = {
* if (singleElement) {
* <span>Single Element</span>
* } else {
* <span>Element 1</span><span>Element 2</span>
* }
* }
*
* @html
* def mySection = <section>
* {myBinding.bind}
* {myBinding}
* {myBindingSeq}
* {Binding{myBindingSeq.all.bind.toSeq}}
* {myBindingSeq.all.bind.toSeq}
* {myBindingOrBindingSeq(true)}
* {myBindingOrBindingSeq(false)}
* </section>
*
* val root = document.createElement("span")
* html.render(root, mySection)
*
* root.innerHTML should be(
* """<section>
* <span>Single Element</span>
* <span>Single Element</span>
* <span>Element 1</span><span>Element 2</span>
* <span>Element 1</span><span>Element 2</span>
* <span>Element 1</span><span>Element 2</span>
* <span>Single Element</span>
* <span>Element 1</span><span>Element 2</span>
* </section>"""
* )
* }}}
*/
private[bindable] trait BindableSeqScaladoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.thoughtworks.binding.bindable

private[bindable] trait BindableSeqScaladoc
51 changes: 51 additions & 0 deletions js/src/main/scala/com/thoughtworks/binding/bindable/JvmOrJs.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.thoughtworks.binding.bindable

import scala.scalajs.js
import com.thoughtworks.binding._
import com.thoughtworks.binding.Binding._

private[bindable] object JvmOrJs {

trait LowPriorityJsBindableSeq2 extends LowPriorityBindableSeq2 {

implicit def jsArrayBindableSeq[Value0]
: BindableSeq.Aux[js.Array[Value0], Value0] =
new BindableSeq[js.Array[Value0]] {
type Value = Value0
def toBindingSeq(from: js.Array[Value0]): BindingSeq[Value] =
Binding.Constants(
scalajs.runtime.toScalaVarArgs(from): _*
)
}
}

trait LowPriorityJsBindableSeq0 extends LowPriorityBindableSeq0 {

implicit def bindingJsArrayBindableSeq[Value0]
: BindableSeq.Aux[Binding[js.Array[Value0]], Value0] =
new BindableSeq[Binding[js.Array[Value0]]] {
type Value = Value0
def toBindingSeq(from: Binding[js.Array[Value0]]): BindingSeq[Value] =
Constants(from).flatMapBinding { from =>
Binding {
Constants(scalajs.runtime.toScalaVarArgs(from.bind): _*)
}
}
}
}

trait BindableJS {

implicit def thenableBindable[Value0]
: Bindable.Aux[scala.scalajs.js.Thenable[Value0], Option[
Either[Any, Value0]
]] =
new Bindable[scala.scalajs.js.Thenable[Value0]] {
type Value = Option[Either[Any, Value0]]
def toBinding(from: scala.scalajs.js.Thenable[Value0]): Binding[Value] =
JsPromiseBinding(from)
}

}

}
1 change: 1 addition & 0 deletions jvm/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.thoughtworks.binding.bindable

private[bindable] trait BindableSeqScaladoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.thoughtworks.binding.bindable

private[bindable] object JvmOrJs {
trait LowPriorityJsBindableSeq2 extends LowPriorityBindableSeq2
trait LowPriorityJsBindableSeq0 extends LowPriorityBindableSeq0
trait BindableJS
}
10 changes: 9 additions & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
addSbtPlugin(
"com.thoughtworks.sbt-best-practice" % "sbt-best-practice" % "8.2.4"
"com.thoughtworks.sbt-best-practice" % "sbt-best-practice" % "8.2.5"
)

addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.15")
Expand All @@ -10,4 +10,12 @@ addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")

addSbtPlugin("org.lyranthe.sbt" % "partial-unification" % "1.1.2")

addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.0")

addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.2.0")

addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.21.1")

addSbtPlugin("com.thoughtworks.sbt-scala-js-map" % "sbt-scala-js-map" % "4.1.1")

addSbtPlugin("com.thoughtworks.example" % "sbt-example" % "9.2.1")
28 changes: 28 additions & 0 deletions project/plugins.sbt.scalajs06
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// An optional sbt file to replace Scala.js 1.0 with 0.6
dependencyOverrides += Defaults.sbtPluginExtra(
"org.scala-js" % "sbt-scalajs" % "0.6.33",
sbtBinaryVersion.value,
scalaBinaryVersion.value,
)

dependencyOverrides += Defaults.sbtPluginExtra(
"ch.epfl.scala" % "sbt-scalajs-bundler" % "0.15.0-0.6",
sbtBinaryVersion.value,
scalaBinaryVersion.value,
)

Compile / sourceGenerators += Def.task {
val file = (Compile / sourceManaged).value / "SkipPublishForNonScalaJSProjects.scala"
IO.write(file, """
import scalajscrossproject.ScalaJSCrossPlugin.autoImport._
import sbtcrossproject.CrossPlugin.autoImport._
import sbt._, Keys._
object SkipPublishForNonScalaJSProjects extends AutoPlugin {
override def trigger = allRequirements
override def projectSettings = Seq(
publish / skip := crossProjectPlatform.value != JSPlatform
)
}
""")
Seq(file)
}.taskValue
15 changes: 15 additions & 0 deletions shared/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
libraryDependencies += "org.scalatest" %%% "scalatest" % "3.2.15" % Test

libraryDependencies += "com.thoughtworks.binding" %%% "futurebinding" % "12.1.1"

libraryDependencies += "com.thoughtworks.binding" %%% "binding" % "12.2.0"

libraryDependencies += {
import Ordering.Implicits._
if (VersionNumber(scalaVersion.value).numbers < Seq(3L)) {
Some("org.typelevel" %% "simulacrum" % "1.0.1")
} else {
None
}
}
scalacOptions += "-Ymacro-annotations"
Loading

0 comments on commit 23d7a9d

Please sign in to comment.