Skip to content

Commit 6e8a4de

Browse files
authored
Merge pull request #2 from pme123/integrate-semantic-ui
moved to sbt-crossproject Integrate semantic ui
2 parents c5fc26a + 7d46118 commit 6e8a4de

File tree

12 files changed

+402
-219
lines changed

12 files changed

+402
-219
lines changed

README.md

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ See the general setup on the starter kit page.
99
This is an example application showing how you can integrate a Play project with a Scala.js, Binding.scala
1010
project - using **Web Sockets**.
1111

12+
It is my personal Starter Kit at the moment.
13+
1214
## Business Case
1315

1416
It's about an automatic process that can be started manually (button).
@@ -20,11 +22,19 @@ that the process is only run once at a time.
2022
Each client sees the LogEntries of the last 'Adapter process' (LogReport) -
2123
or if the process is running - each LogEntry right away.
2224

23-
![play-wsocket-scalajs](https://user-images.githubusercontent.com/3437927/32768876-ec1a891c-c919-11e7-8a64-ec32a1cdc1cc.gif)
25+
You can filter the LogEntries for their Level and/ or their message.
26+
27+
![play-wsocket-scalajs](https://user-images.githubusercontent.com/3437927/33009370-eb51c8b2-cdd6-11e7-8753-383eebbcc191.gif)
28+
29+
# Architecture
30+
- Server: Play with Scala
31+
- Client: Binding.scala with ScalaJS
2432

2533
The web-sockets are created according to the
2634
[Lightbend's Websocket example](https://github.com/playframework/play-scala-websocket-example).
2735

36+
The project is split into 3 modules that are explained in the following chapters.
37+
2838
## Shared
2939
The great thing about a **full-stack Scala app** is that
3040
we only have to define our domain model once for the server and the client.
@@ -72,13 +82,78 @@ case object KeepAliveMsg extends AdapterMsg
7282
The UI is done with
7383
[Binding.scala](https://github.com/ThoughtWorksInc/Binding.scala)
7484

75-
It is more or less HTML-snippets that contain dynamic content provided by Binding.scala:
76-
* `Vars[LogEntry]` a list of LogEntries that from the active- or last Adapter run.
77-
* `Var[Boolean]` is true if the Adapter process is running at the moment.
78-
* `Var[Option[LogLevel]]` the LogLevel of the last Adapter run - used for the title.
85+
The client is split in 3 classes:
86+
87+
### AdapterClient
88+
The whole web page is here composed with `Binding.scala data-binding expressions`.
89+
90+
It is more or less HTML-snippets that contain dynamic content provided by `Binding.scala data sources`:
91+
92+
* `logData: Vars[LogEntry]` a list of LogEntries that from the active- or last Adapter run.
93+
* `isRunning: Var[Boolean]` is true if the Adapter process is running at the moment.
94+
* `filterText: Var[String]` is the filter text from the input field.
95+
* `filterLevel: Var[LogLevel]` is the Level selected from the drop-down.
96+
* `lastLogLevel: Var[Option[LogLevel]]` the LogLevel of the last Adapter run - used for the title.
7997

8098
If you have troubles understanding it, please check out [Binding.scala-Google-Maps](https://github.com/pme123/Binding.scala-Google-Maps), where I explained all the details.
8199

100+
#### ScalaJS Routes
101+
To use Play routes from within the client, we need again something to do.
102+
My solution is taken from here: [github.com/vmunier/play-scalajs.g8](https://github.com/vmunier/play-scalajs.g8/issues/50)
103+
104+
* build.sbt
105+
```scala
106+
// Create a map of versioned assets, replacing the empty versioned.js
107+
DigestKeys.indexPath := Some("javascripts/versioned.js"),
108+
// Assign the asset index to a global versioned var
109+
DigestKeys.indexWriter ~= { writer => index => s"var versioned = ${writer(index)};" }
110+
```
111+
* add `javascripts/versioned.js` to the `public` folder (contains only: `var versioned = {};)
112+
* add to the end of the `routes`-file `-> /webjars webjars.Routes`
113+
* now you can use them like `<img src={"" + g.jsRoutes.controllers.Assets.versioned("images/favicon.png").url}></img>`
114+
Don't forget `import scala.scalajs.js.Dynamic.{global => g}`
115+
### ClientWebsocket
116+
Encapsulates the communication with the server. The internal communication with the AdapterClient
117+
is done via the `Binding.scala data sources`.
118+
119+
### SemanticUI
120+
To have a decent look I integrated with [Semantic-UI](https://semantic-ui.com/usage/layout.html).
121+
122+
The solution is based on this [blog](http://sadhen.com/blog/2017/01/02/binding-with-semantic.html).
123+
124+
To do that we need to add the dependency with webjars:
125+
```scala
126+
// server
127+
, "org.webjars" %% "webjars-play" % "2.6.1"
128+
, "org.webjars" % "Semantic-UI" % semanticV
129+
, "org.webjars" % "jquery" % jQueryV
130+
// client
131+
jsDependencies ++= Seq(
132+
"org.webjars" % "jquery" % jQueryV / "jquery.js" minified "jquery.min.js",
133+
"org.webjars" % "Semantic-UI" % semanticV / "semantic.js" minified "semantic.min.js" dependsOn "jquery.js"
134+
),
135+
...
136+
// jquery support for ScalaJS
137+
"be.doeraene" %%% "scalajs-jquery" % "0.9.1"
138+
139+
```
140+
Here is the documentation: [webjars.org](http://www.webjars.org/documentation)
141+
142+
Monkey Patching: With Semantic-UI you sometimes you have activate the Javascript.
143+
144+
see [correct-way-to-dynamically-add-semantic-ui-controls](http://stackoverflow.com/questions/30531410/correct-way-to-dynamically-add-semantic-ui-controls)
145+
146+
```scala
147+
@js.native
148+
trait SemanticJQuery extends JQuery {
149+
def dropdown(params: js.Any*): SemanticJQuery = js.native
150+
}
151+
152+
implicit def jq2semantic(jq: JQuery): SemanticJQuery = jq.asInstanceOf[SemanticJQuery]
153+
```
154+
This is all - that is needed with ScalaJS.
155+
Thanks to `implicit` Monkey Patching can be done in an elegant way with Scala.
156+
82157
## Server
83158
When you go to [http://localhost:9000](http://localhost:9000)
84159
a web-socket is opened to show you the active- or last LogReport of the Adapter Process.

build.sbt

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
lazy val scalaV = "2.12.2"
1+
// (5) shadow sbt-scalajs' crossProject and CrossType until Scala.js 1.0.0 is released
2+
import sbtcrossproject.{crossProject, CrossType}
23

3-
resolvers += "jitpack" at "https://jitpack.io"
4+
lazy val scalaV = "2.12.3"
5+
lazy val jQueryV = "2.2.4"
6+
lazy val semanticV = "2.2.10"
47

58
lazy val server = (project in file("server")).settings(
69
scalaVersion := scalaV,
@@ -13,12 +16,23 @@ lazy val server = (project in file("server")).settings(
1316
"com.vmunier" %% "scalajs-scripts" % "1.1.1",
1417
guice,
1518
filters,
16-
ws,
19+
ws
20+
// webjars for Semantic-UI
21+
, "org.webjars" %% "webjars-play" % "2.6.1"
22+
, "org.webjars" % "Semantic-UI" % semanticV
23+
, "org.webjars" % "jquery" % jQueryV
24+
,
1725
"com.typesafe.akka" %% "akka-testkit" % "2.5.6" % Test,
1826
"com.typesafe.akka" %% "akka-stream-testkit" % "2.5.6" % Test,
1927
"org.scalatestplus.play" %% "scalatestplus-play" % "3.1.2" % Test,
2028
"org.awaitility" % "awaitility" % "3.0.0" % Test
21-
)
29+
),
30+
// to have routing also in ScalaJS
31+
// Create a map of versioned assets, replacing the empty versioned.js
32+
DigestKeys.indexPath := Some("javascripts/versioned.js"),
33+
// Assign the asset index to a global versioned var
34+
DigestKeys.indexWriter ~= { writer => index => s"var versioned = ${writer(index)};" }
35+
2236
).enablePlugins(PlayScala)
2337
.dependsOn(sharedJvm)
2438

@@ -28,26 +42,35 @@ lazy val client = (project in file("client")).settings(
2842
scalacOptions ++= Seq("-Xmax-classfile-name","78"),
2943
scalaJSUseMainModuleInitializer in Test := false,
3044
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full),
31-
// jsDependencies += "org.webjars" % "flot" % "0.8.3" / "flot.js" minified "flot.min.js",
32-
// jsDependencies += "org.webjars" % "bootstrap" % "3.3.6" / "bootstrap.js" minified "bootstrap.min.js",
45+
jsDependencies ++= Seq(
46+
"org.webjars" % "jquery" % jQueryV / "jquery.js" minified "jquery.min.js",
47+
"org.webjars" % "Semantic-UI" % semanticV / "semantic.js" minified "semantic.min.js" dependsOn "jquery.js"
48+
),
3349
libraryDependencies ++= Seq(
3450
"org.scala-js" %%% "scalajs-dom" % "0.9.3",
3551
"org.scala-lang.modules" %% "scala-xml" % "1.0.6",
3652
"com.typesafe.play" %%% "play-json" % "2.6.1",
3753
"com.thoughtworks.binding" %%% "dom" % "11.0.0-M4",
3854
"com.thoughtworks.binding" %%% "futurebinding" % "11.0.0-M4",
3955
"fr.hmil" %%% "roshttp" % "2.0.2",
40-
"org.scala-js" %%% "scalajs-java-time" % "0.2.2"
56+
// java.time supprot for ScalaJS
57+
"org.scala-js" %%% "scalajs-java-time" % "0.2.2",
58+
// jquery support for ScalaJS
59+
"be.doeraene" %%% "scalajs-jquery" % "0.9.1"
4160
)
42-
).enablePlugins(ScalaJSPlugin, ScalaJSWeb).
61+
).enablePlugins(ScalaJSWeb).
4362
dependsOn(sharedJs)
4463

45-
lazy val shared = (crossProject.crossType(CrossType.Pure) in file("shared"))
64+
lazy val shared = crossProject(JSPlatform, JVMPlatform)
65+
.crossType(CrossType.Pure)
4666
.settings(scalaVersion := scalaV
4767
, libraryDependencies ++= Seq(
4868
"org.julienrf" %%% "play-json-derived-codecs" % "4.0.0"
69+
// logging lib that also works with ScalaJS
4970
, "biz.enef" %%% "slogging" % "0.6.0"
5071
))
72+
.jsSettings(/* ... */) // defined in sbt-scalajs-crossproject
73+
.jvmSettings(/* ... */)
5174
.jsConfigure(_ enablePlugins ScalaJSWeb)
5275

5376
lazy val sharedJvm = shared.jvm

0 commit comments

Comments
 (0)