Skip to content

Scalajs react #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions client/src/main/scala/ScalaJS/AjaxClient.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package ScalaJS

import autowire.Bounds.None
import org.scalajs.dom.ext.Ajax

import scala.scalajs.js
import scala.scalajs.js.JSON
import scala.concurrent.ExecutionContext.Implicits.global

@js.native
trait randomWordResult extends js.Object {
def word: String
}

object AjaxClient extends autowire.Client[String, None, None] {
override def doCall(req: AjaxClient.Request) = {
Ajax.get("/spa/api/" + req.path.mkString("/")).map { xhr =>
JSON.parse(xhr.responseText).asInstanceOf[randomWordResult].word
}
}

override def read[Result](p: String)(implicit evidence$1: None[Result]) = ???

override def write[Result](r: Result)(implicit evidence$2: None[Result]) = ???
}

trait Api {
def getRandomWord(): String
}
5 changes: 2 additions & 3 deletions client/src/main/scala/ScalaJS/App.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package ScalaJS

import ScalaJS.Component.Main
import org.scalajs.dom.document

import scala.scalajs.js.JSApp

object App extends JSApp {
def main(): Unit = {
println("App starting...")
// document.getElementById("root").textContent = "scalaJs entry point"
Main().renderIntoDOM(document.getElementById("root"))
// Main().renderIntoDOM(document.getElementById("root"))
AppRouter.router().renderIntoDOM(document.getElementById("root"))
}
}
24 changes: 24 additions & 0 deletions client/src/main/scala/ScalaJS/AppRouter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ScalaJS

import ScalaJS.Component.{Main, Show}
import japgolly.scalajs.react.extra.router.{BaseUrl, Redirect, Router, RouterConfigDsl}
import japgolly.scalajs.react.vdom.Implicits._


object AppRouter {
case object Home extends View
// case class Play(game: HangPersonGame) extends View
case object Play extends View

val routerConfig = RouterConfigDsl[View].buildConfig { dsl =>
import dsl._

(emptyRule
| staticRoute(root, Home) ~> renderR(ctl => Main.component(ctl))
| staticRoute("#show", Play) ~> renderR(ctl => Show.component(ctl))
).notFound(redirectToPath("/")(Redirect.Replace))
.verify(Home)
}

val router = Router(BaseUrl.fromWindowOrigin + "/hangperson/spa", routerConfig)
}
7 changes: 7 additions & 0 deletions client/src/main/scala/ScalaJS/Component/Bootstrap.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ScalaJS.Component

object Bootstrap {

@inline private def bss = ???

}
15 changes: 15 additions & 0 deletions client/src/main/scala/ScalaJS/Component/Button.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ScalaJS.Component

import ScalaJS.Styles.Common
import japgolly.scalajs.react
import japgolly.scalajs.react.Callback
import japgolly.scalajs.react.vdom.html_<^._

import scala.language.implicitConversions

object Button {
case class Props(onClick: Callback, style: Common.Value = Common.default)

// val component = react.ScalaComponent.builder[String]("Button")
// .render_P((_, props, children) => <.button(^.cls := "btn-"+ props))
}
15 changes: 11 additions & 4 deletions client/src/main/scala/ScalaJS/Component/Main.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package ScalaJS.Component

import ScalaJS.AppRouter.Play
import ScalaJS.View
import japgolly.scalajs.react.ScalaComponent
import japgolly.scalajs.react.extra.router.RouterCtl
import japgolly.scalajs.react.vdom.html_<^._

object Main {
val component = ScalaComponent.builder[Unit]("Main")
.renderStatic(<.p("ReactJS Hello World"))
val component = ScalaComponent.builder[RouterCtl[View]]("Main")
.render_P( ctl =>
<.div(^.cls := "panel-footer",
<.a(^.cls := "btn btn-primary", ^.id := "newgame", ^.value := "New Game", ctl setOnClick Play, "New Game")
)
)
.build
def apply() = component()
}

def apply() = component
}
38 changes: 38 additions & 0 deletions client/src/main/scala/ScalaJS/Component/Show.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package ScalaJS.Component

import ScalaJS.{AjaxClient, Api, View, randomWordResult}
import helpers.HangPersonGame
import japgolly.scalajs.react.ScalaComponent
import japgolly.scalajs.react.extra.router.RouterCtl
import japgolly.scalajs.react.vdom.html_<^._
import org.scalajs.dom.ext.Ajax

import scala.concurrent.Await
import scala.scalajs.js.JSON
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.language.postfixOps


object Show {
// val getRandomWord: String = AjaxClient[Api].getRandomWord()
val getRandomWord = Ajax.get("/hangperson/spa/api/randomWord")
.map(xhr => JSON.parse(xhr.responseText).asInstanceOf[randomWordResult].word)

val component = ScalaComponent.builder[RouterCtl[View]]("Show")
.render_P(ctl =>
<.div(^.cls := "panel-body",
<.h2("Guess a letter"),
<.p("Wrong Guesses:",
<.span(^.cls := "text-guesses","wrong_guesses")
),
<.p("Word so far:",
<.span(^.cls := "text-word", "word_with_guesses")
),
<.p("error handling is a todo"),
<.p(s"$getRandomWord")
)
)
.build
def apply() = component
}
5 changes: 5 additions & 0 deletions client/src/main/scala/ScalaJS/Styles/Common.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ScalaJS.Styles

object Common extends Enumeration {
val default, primary, success, info, warning, danger = Value
}
5 changes: 5 additions & 0 deletions client/src/main/scala/ScalaJS/View.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ScalaJS

trait View {

}
9 changes: 6 additions & 3 deletions project/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ object Settings {
val scalaDom = "0.9.1"
val scalajsReact = "1.0.1"
// val log4js = "1.4.10"
// val autowire = "0.2.5"
val autowire = "0.2.5"
// val booPickle = "1.2.5"
// val diode = "1.1.0"
// val uTest = "0.4.4"
Expand All @@ -45,14 +45,16 @@ object Settings {
val compass = "1.0.2"
val fontawesome = "4.3.0-1"
val scalajsScripts = "1.0.0"
val specs2Extra = "3.8"
val circe = "0.8"
}

/**
* These dependencies are shared between JS and JVM projects
* the special %%% function selects the correct version for each project
*/
val sharedDependencies = Def.setting(Seq(
// "com.lihaoyi" %%% "autowire" % versions.autowire,
"com.lihaoyi" %%% "autowire" % Versions.autowire
// "me.chrons" %%% "boopickle" % versions.booPickle
))

Expand All @@ -65,7 +67,8 @@ object Settings {
// "com.lihaoyi" %% "utest" % versions.uTest % Test
cache,
ws,
specs2 % Test
specs2 % Test,
"org.specs2" %% "specs2-matcher-extra" % Versions.specs2Extra % "test"
))

/** Dependencies only used by the JS project (note the use of %%% instead of %%) */
Expand Down
20 changes: 19 additions & 1 deletion server/app/controllers/HangPersonSpa.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
package controllers

import javax.inject.Inject

import play.api.libs.json._
import play.api.libs.ws.WSClient
import play.api.mvc._

class HangPersonSpa extends Controller {
class HangPersonSpa @Inject()(val ws: WSClient) extends Controller with RandomWordClient {
def index = Action {
Ok(views.html.HangPerson.spa.index())
}

/**
* TODO: if someone inspect the network tab of the browser can read the word and cheat.
* find a solution to communicate in a secure way the data among client and server.
* try use JWT or just an encrypted one
*
*/
def getRandomWord = Action {
val json: JsValue = JsObject(Seq(
"word" -> JsString(randomWord)
))

Ok(Json.toJson(json))
}
}
2 changes: 1 addition & 1 deletion server/app/views/HangPerson/newActionSnippet.scala.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="panel-footer">
<form class="form-inline" method="post" action=@routes.HangPerson.create method="POST">
<form class="form-inline" action=@routes.HangPerson.create method="POST">
<input type="submit" class="btn btn-primary" id="newgame" value="New Game"/>
</form>
</div>
23 changes: 12 additions & 11 deletions server/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@
# ~~~~

# Home page
GET / controllers.Application.index
GET / controllers.Application.index

GET /hangperson controllers.Default.redirect(to = "/hangperson/new")
GET /hangperson/new controllers.HangPerson.newAction
GET /hangperson/create controllers.Default.redirect(to = "/hangperson/show")
POST /hangperson/create controllers.HangPerson.create
GET /hangperson/show controllers.HangPerson.show
POST /hangperson/guess controllers.HangPerson.guess
GET /hangperson/win controllers.HangPerson.win
GET /hangperson/lose controllers.HangPerson.lose
GET /hangperson/spa controllers.HangPersonSpa.index
GET /hangperson controllers.Default.redirect(to = "/hangperson/new")
GET /hangperson/new controllers.HangPerson.newAction
GET /hangperson/create controllers.Default.redirect(to = "/hangperson/show")
POST /hangperson/create controllers.HangPerson.create
GET /hangperson/show controllers.HangPerson.show
POST /hangperson/guess controllers.HangPerson.guess
GET /hangperson/win controllers.HangPerson.win
GET /hangperson/lose controllers.HangPerson.lose
GET /hangperson/spa controllers.HangPersonSpa.index
GET /hangperson/spa/api/randomWord controllers.HangPersonSpa.getRandomWord

# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
13 changes: 0 additions & 13 deletions server/test/controllers/HanPersonSpaSpec.scala

This file was deleted.

25 changes: 25 additions & 0 deletions server/test/controllers/HangPersonSpaSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package controllers

import org.junit.runner.RunWith
import org.specs2.matcher.JsonMatchers
import org.specs2.runner.JUnitRunner
import play.api.test.{FakeRequest, PlaySpecification, WithApplication, WithBrowser}
import play.api.libs.json._


@RunWith(classOf[JUnitRunner])
class HangPersonSpaSpec extends PlaySpecification with JsonMatchers {
"work from within a browser SPA" in new WithBrowser {
browser.goTo("http://localhost:" + port + "/hangperson/spa")
browser.pageSource must contain("Hang Person SPA")
}

"random word in JSON response format" in new WithApplication {
val Some(response) = route(app, FakeRequest(GET, "/hangperson/spa/randomWord"))
status(response) must equalTo(OK)
contentType(response) must beSome("application/json")

val wordLength = (contentAsJson(response) \ "word").getOrElse(JsString("")).as[String].length()
contentAsString(response) must /("word" -> s"[a-z]{$wordLength}".r)
}
}