About FastScala
What is the FastScala framework?
The FastScala web framework allows you to do fast web application development in Scala.
You can very easily create callbacks that you send to the client side, which will be executed in the server and will return back the next action to be executed on the server (rerender part of the page, run some javascript, redirect, alert, anything).
Create a callback
Clicking the button runs the javacript:
window._fs.callback('',"b4e5c4f6fbc35a7f","35667b6a1af8ed24",false,true,true,{});
val callbackJs = fsc.callback(() => JS.alert(s"Current date/time on server: ${new Date().toString}"))
<p>Clicking the button runs the javacript:</p>
<pre>{callbackJs.cmd}</pre>
<button class="btn btn-primary d-block mx-auto" onclick={callbackJs.cmd}>Check time on server</button>
Building on top of the basics
Builing on top of this basic callback funcionality, we can create a great development experience which allows you to build web applications much faster.
See bellow the available library to build Bootstrap buttons easily:
BSBtn().BtnPrimary.lg.lbl("Check time on server")
.ajax(_ => JS.alert(s"Current date/time on server: ${new Date().toString}"))
.btn.m_3.shadow.mx_auto.d_block
Check time on server basic example
Your imagination is the limit - easily control the client side from the client side:
<div id="current-time"><span>{new Date().toString}</span></div> ++
BSBtn().BtnPrimary.sm.lbl("Update time")
.ajax(_ => JS.setContents("current-time", <span>{new Date().toString}</span>)).sm.btn
Check time on server example 2
Building on these fundations we introduce more advanced components which make you go even faster and safer:
val rerenderable = JS.rerenderable(_ => _ => <span>{new Date().toString}</span>)
rerenderable.render() ++
BSBtn().BtnPrimary.sm.lbl("Update time")
.ajax(_ => rerenderable.rerender()).sm.btn.ms_2
Check time on server example 3
JS.rerenderableContents(rerenderer => implicit fsc => {
<span>{new Date().toString}</span> ++
BSBtn().BtnPrimary.sm.lbl("Update time")
.ajax(_ => rerenderer.rerender()).sm.btn.ms_2
}).render()
Variable number of input buttons
Lets create an even more complex scenario:
var numBtns = 3
JS.rerenderableContents(rerenderer => implicit fsc => {
val buttons = (0 until numBtns).map(btnNum => {
val points = btnNum + 1
BSBtn().BtnSecondary.lg.lbl(points.toString)
.onclick(JS.alert(points + " points")).btn.mx_3
})
d_flex.justify_content_center.apply(buttons: _*).mb_2 ++
d_flex.justify_content_center.apply {
BSBtn().BtnPrimary.sm.lbl("-").ajax(_ => {
numBtns -= 1
rerenderer.rerender()
}).btn ++
BSBtn().BtnPrimary.sm.lbl("+").ajax(_ => {
numBtns += 1
rerenderer.rerender()
}).btn.ms_2
}
}).render()
Variable number of input buttons v2
More elegant/functional approach, with JS.rerenderableContentsP
JS.rerenderableContentsP[Int](rerenderer => implicit fsc => numBtns => {
val buttons = (0 until numBtns).map(_ + 1).map(points => {
BSBtn().BtnSecondary.lg.lbl(points.toString)
.onclick(JS.alert(points + " points")).btn.mx_3
})
d_flex.justify_content_center.apply(buttons: _*).mb_2 ++
d_flex.justify_content_center.apply {
BSBtn().BtnPrimary.sm.lbl("-").ajax(_ => {
rerenderer.rerender(math.max(1, numBtns - 1))
}).btn ++
BSBtn().BtnPrimary.sm.lbl("+").ajax(_ => {
rerenderer.rerender(math.min(10, numBtns + 1))
}).btn.ms_2
}
}).render(3)
Easily create advanced forms
We continously build on top of abstractions to create even more complex experiences, that are really simple and low-code to develop:
import DefaultFSDemoBSForm7Renderers.*
val nameField = new F7StringField().label("Name").required(true)
val emailField = new F7StringField().label("Email").inputType("email").required(true)
new DefaultForm7() {
override def postSubmitForm()(implicit fsc: FSContext): Js =
BSModal5.verySimple("Your input data", "Done")(modal => implicit fsc => {
fs_4.apply(s"Your entered the name '${nameField.currentValue}' and email '${emailField.currentValue}'")
})
override lazy val rootField: F7Field = F7VerticalField()(
nameField
, emailField
, new F7SubmitButtonField(implicit fsc => BSBtn().BtnPrimary.lbl("Submit").btn.d_block)
)
}.render()
Advanced interactions
Support advanced interactions with a few lines of code
import DefaultFSDemoBSForm7Renderers.*
case class Definition(definition: Option[String], example: Option[String], synonyms: List[String], antonyms: List[String]) {
def render(): NodeSeq = definition.map(definition => <li><i>{definition}</i>{example.map(": " + _).getOrElse("")}</li>).getOrElse(NodeSeq.Empty)
}
case class Meaning(partOfSpeech: String, definitions: List[Definition]) {
def render(): NodeSeq = <li><i>{partOfSpeech}</i></li> ++
<li>Definitions: <ul class="ms-2">{definitions.flatMap(_.render())}</ul></li>.showIf(definitions.nonEmpty)
}
case class Response(word: String, phonetic: String, origin: Option[String], meanings: List[Meaning]) {
def render(): NodeSeq = <h6>{word}</h6> ++
<ul class="ms-2">
<li>Phonetic: {phonetic}</li>{origin.map(origin => <li>Origin: {origin}</li>).getOrElse(NodeSeq.Empty)}
{<li>Meanings: <ul class="ms-2">{meanings.flatMap(_.render())}</ul></li>.showIf(meanings.nonEmpty)}
</ul>.ms_2
}
implicit val definitionDecoder: Decoder[Definition] = semiauto.deriveDecoder[Definition]
implicit val meaningDecoder: Decoder[Meaning] = semiauto.deriveDecoder[Meaning]
implicit val responseDecoder: Decoder[Response] = semiauto.deriveDecoder[Response]
def renderResponses(responses: List[Response]) = responses.flatMap(_.render())
val resultsRenderer = JS.rerenderableContentsP[Option[String]](_ => implicit fsc => queryOpt => {
queryOpt match {
case Some(query) =>
val url = new URL(s"https://api.dictionaryapi.dev/api/v2/entries/en/${URLEncoder.encode(query)}")
val con = url.openConnection().asInstanceOf[HttpURLConnection]
con.setRequestMethod("GET")
try {
Try(Source.fromInputStream(con.getInputStream).mkString).toEither.flatMap(json => {
io.circe.parser.decode[List[Response]](json)
}) match {
case Right(responses) => renderResponses(responses)
case Left(_: java.io.FileNotFoundException) => span.text_danger.apply("No results found.")
case Left(value) => span.apply("Error when calling the API: " + value)
}
} finally {
Try(con.getInputStream.close())
}
case None => div.apply("...").text_center
}
})
val queryField = new F7StringField().label("Search query").required(true)
resultsRenderer.render(None) ++
new DefaultForm7() {
override def postSubmitForm()(implicit fsc: FSContext): Js = resultsRenderer.rerender(Some(queryField.currentValue))
override lazy val rootField: F7Field = F7VerticalField()(
queryField
, new F7SubmitButtonField(implicit fsc => BSBtn().BtnPrimary.lbl("Submit").btn.d_block)
)
}.render()