How do I handle data entry in a scala.js bootstrap modal dialog? - scala.js

I am using scala.js, plus the bootstrap facade, to collect some data from my user:
def showModal(kopiUser: KopiUser): Unit = {
def modal = Modal().withTitle("modal title").
withBody(
p(s"${kopiUser.displayName}, please enter your data"),
Form(
FormInput.text("fieldlabel")
)
).
withButtons(
Modal.closeButton(),
Modal.button("Submit", Modal.dismiss)
)
modal.show()
}
When the user hits "Submit", I want to get the data that was entered and do something with it.
The dialog is animated, sliding and fading in and out as per bootstrap default, so I can't just register an onClick on the button, right?
How do I do this in a nice way - i.e. preferably some kind of bootstrap/scala.js kind of way, so that I retain my type safety?
I've looked in the bootstrap/scala.js project example, but that just displays an example dialog, it doesn't do anything with the data.
I've intentionally left off the bootstrap tag from this question, I'm looking for how to solve this with scala.js (via the bootstrap facade or not), rather than just using bootstrap itself.

This is modified example of object TestModal from bootstrap/scala.js v1.1.1. There are certainly also other ways to do this but one solution is to move field definitions out of the apply() and out of the form.
This way you can see and use them outside the TestModal2 object.
object TestModal2 {
val modalInputValue = Var("10000000") // Better use string
val radioGroup = FormInput.radioGroup(FormInput.radio("Test1", "modal-title", "First radio"), FormInput.radio("Test2", "modal-title", "Second radio"))
val select = FormInput.simpleSelect("Plain select", "Option 1", "Option 2", "Option 3")
val multipleSelect = FormInput.simpleMultipleSelect("Multiple select", "Option 1", "Option 2", "Option 3")
val inputTextArea = FormInput.textArea("Money text area", rows := 1, modalInputValue.reactiveInput)
val selectedFile = Var("")
val inputFile = FormInput.file("Test file", onchange := Bootstrap.jsInput { input ⇒
val file = input.files.head
selectedFile.update(file.name)
window.alert(s"File selected: ${file.name}")
})
val form = Form(
FormInputGroup(FormInputGroup.label("Money"), FormInputGroup.addon("usd".fontAwesome(FontAwesome.fixedWidth)), FormInputGroup.number(modalInputValue.reactiveInput)),
radioGroup,
select,
multipleSelect,
inputTextArea,
inputFile
)
def apply()(implicit ctx: Ctx.Owner): Modal = {
Modal()
.withTitle(radioGroup.value, " / ", select.selected.map(_.head), " / ", multipleSelect.selected.map(_.mkString(" + ")))
.withBody(p("You won ", modalInputValue, "$"), p(form))
.withButtons(Modal.closeButton(), Modal.button("Take", Modal.dismiss))
}
}
If you put somewhere in the calling code e.g. this
TestModal2.modalInputValue.trigger(
println("modalInputValue = " + TestModal2.modalInputValue.now)
)
TestModal2.select.selected.trigger(
println("select = " + TestModal2.select.selected.now)
)
TestModal2.selectedFile.trigger(
println("selectedFile = " + TestModal2.selectedFile.now)
)
You see the input value changes immediately on the console when they happen on the open dialog.

This is the answer I was given by the maintainer of the scalajs-bootstrap project.
def showModal(kopiUser: KopiUser): Unit = {
val data = Var("")
def modal = Modal().withTitle("modal title").
withBody(
p(s"${kopiUser.displayName}, please enter your data"),
Form(
FormInput.text("fieldlabel", data.reactiveInput)
)
).
withButtons(
Modal.closeButton(),
Modal.button("Submit", Modal.dismiss, onclick := Bootstrap.jsClick { _ ⇒
// Do something with input
window.alert(data.now)
})
)
modal.show()
}
To do what I wanted (including not closing the dialog when data was not entered), I had to additionally hack up the Modal facade class so that I could register onHide, onHidden events to not close the dialog, etc.

Related

How to reset a table

It's possible to reset a table scala swing or remove it from the container after clicking on a button ?
I've tried to create a val with that table but I have always a new table stacked under the old
Here is the code :
// here is the most crucial part when the user click on the button, it will append a new table but if we want to start again, it will append bottom of the old one and I want here a kind of reset or removing of table
contents = new BoxPanel(Orientation.Vertical) {
contents += new Label("Hello, you're welcome")
contents += Button("Query") {
val query: ScrollPane = new ScrollPane(changeCountry())
contents -= query
Try {
contents += query
}.getOrElse(Dialog.showMessage(contents.head, "Incorrect input ! This seems that input isn't in that list, write a different code or country"))
}
// this part will ask to the user to write text to the input to display the table in function of the parameter of my function
def changeCountry(): Table = {
val text = Dialog.showInput(parent = contents.head, message = "Write a code of a country or a country", initial = "test")
text match {
case Some(s) => airportRunwayByCountry(s)
}
}
// this below part creates the table
def airportRunwayByCountry(code : String): Table = {
val headers = Seq("Airport","Runway linked")
val rowData = Functions.findAirportAndRunwayByCountry(code).map(x => x.productIterator.toArray).toArray
val tableAirportRunway = new Table(rowData,headers)
tableAirportRunway}
}
Solved with method "remove" of containers
Here is the code :
Try {
if(contents.length == 3 \\ number of items in my Box) {
\\ at this moment, only add the table because none other table exists
contents += new ScrollPane(changeCountry())
}
else {
contents -= contents.remove(3) \\get the id of the old table and remove it at this position
contents += new ScrollPane(changeCountry()) \\ at this moment, this content will have the id n°2, and the loop can start over without errors
}

Scala/Twirl variables and scope/defining

I'm a front ender by trade but I've been asked to hop on to a Scala project which leverages the Play framework so includes Twirl. I'm trying to create a template which displays slightly different outputs depending on the string which is passed in.
Here is my code:
#(status: String)(implicit messages: Messages)
#{
if(status == "00") {
val pageTitle = "Page title"
val appStatus = "className"
val title = "message"
val subTitle = "message"
val step1Status = "className"
val step2Status = "className"
val step3Status = "className"
val image1 = "/customs/assets/images/image.png"
val image2 = "/customs/assets/images/image.png"
val image3 = "/customs/assets/images/image.png"
val optionalHeading = ""
val optionalParagraph = ""
val listContents = "<li>#Messages('message')</li><li>#Messages('message')</li>"
val optionalLink = "<br /><a class='button' href='#routes.DashboardController.display(custom)' role='button'>#Messages('message')</a>"
}
if(status == "01") {
//Other variables
}
if(status == "04") {
//Etc...
}
}
#layout(${pageTitle}) {
<div class="content__body">
<div class="hero">
<div class="${appStatus}">
<h1 class="bold-large">#Messages(${title})</h1>
<p>
${afterSubTitle}
</p>
<ol class="appstatus-steps">
<li><span class="${step1Status}"><img alt="Complete" title="Complete" src=" + ${image1} + ">#Messages("messages.Received")</span></li>
<li><span class="${step2Status}"><img alt="Complete" title="Complete" src=" + ${image2} + ">#Messages("messages.Processing")</span></li>
<li><span class="${step3Status}"><img alt="Complete" title="Complete" src=" + ${image3} + ">#Messages("messages.Decision")</span></li>
</ol>
</div>
</div>
${optionalHeading}
${optionalParagraph}
<h2>#Messages("messages.next")</h2>
<ul class="list list-bullet">
${listContents}
</ul>
${optionalLink}
</div>
}
So, as you can see, the idea is that this page is called with a code (status) and based on that string, a number of variables are defined which alter the way the page is formed; content/css classes/images etc.
This doesn't work (you may be surprised to learn!)
It's really unclear to me WHY it doesn't though. I thought that you accessed variables using a ${Variable} format, but it's possible that you're supposed to use the #Variable format instead.
Even when I try to change them to the #Variable way, I'm still getting problems and I loosely understand that this is to do with scope in Scala/Twirl and there's a "defining" keyword which can be used. I've read some documentation but I'm not getting it.
Could anyone shed some light on this?
EDIT
Okay, let's just consider the below code instead:
#(status: String)(implicit messages: Messages)
#{
if(status == "00") {
val myClass = "custom-class-1"
}
if (status == "01") {
val myClass = "custom-class-2"
}
#layout("page title") {
<div class="#myClass">This is a div</div>
}
So 'all I want to do' :) is be able to define a variety of variables based on the status and then use those variables in the template of the page.
Does that make sense? I'm so new to Scala so Tuples/Defining is lost on me at the moment, but I am learning!
You seem to be more used to javascript variables and scopes :)
In Scala and Twirl, if you only define a variable (in this case it's a value because it's immutable) inside an if statement, the value is only accessible inside the if block
One solution could be to have the if statement to return a tuple with all the variables, and use the power of assignment operation, like:
val (myA, myB, myC) = if(something) {
val a = "a"
val b = "b"
val c = "c"
(a,b,c) // this will return the 3 variables/values because if statements are an expression, and expressions return something
} else {
val a = "A"
val b = "B"
val c = "C"
(a,b,c)
}
// now you have a,b,c defined in this scope and you can do whatever you need with them
if you want to return different variables depending on the if statement, you will have more complications. a solution would be to make the not needed variables null or an Option
Let me know if you need anything else better explained :)
EDIT: solution for minified edited example
I don't have anything here to check if it compiles, but it should be something like:
#(status: String)(implicit messages: Messages)
#{
val myClass = if(status == "00") {
"custom-class-1"
} else if (status == "01") {
"custom-class-2"
} else {
""
}
#layout("page title") {
<div class="#myClass">This is a div</div>
}
Alternative using pattern matching:
replace the if/else statement with:
val myClass = status match {
case "00" => "custom-class-1"
case "01" => "custom-class-2"
case _ => ""
}
(the case _ is like "in every other case")
The answer by #pedrorijio is almost exactly correct, but the actual code which worked for me in the end was this:
#myClass = #{status match {
case "00" => "custom-class-1"
case "01" => "custom-class-2"
}}
Thank you for the assistance!

Playframework 2.2.1 Scala - Form bindFromRequest error

I'm trying to register a tuple to a DB and upload the image to Amazon S3.
I divided the code to two parts : 1) Working Code and 2) Non Working Code.
Working code means It works as expected. The image file is uploaded sucessfully to S3.
Non working code is always folding to index_error page which I'm trying to solve but I can't figure out where I'm missing.
Thank you for help!
Working Code
def index = Action {
Ok(views.html.upload_test_index("File Upload In Play"))
}
def uploadFile = Action(parse.multipartFormData) { request =>
request.body.file("fileUpload").map { video =>
val newFile = File.createTempFile("temp-uploaded-", video.filename)
video.ref.moveTo(newFile, true)
new S3Sender(newFile, video.filename).send
}.getOrElse {
Redirect(routes.Application.index)
}
Ok("File has been uploaded")
}
Non Working Code - Controller
val anuncioForm = Form(
tuple(
"label" -> nonEmptyText,
"imgName" -> text
)
)
def createAnuncio = Action(parse.multipartFormData) { implicit request =>
anuncioForm.bindFromRequest.fold(
formWithErrors => BadRequest(views.html.index_error(formWithErrors)),
{
case (label, imgName) =>
request.body.file("imgName").map { imgName =>
val newFile = File.createTempFile("temp-uploaded-", imgName.filename)
val fileName = imgName.filename
imgName.ref.moveTo(newFile, true)
new S3Sender(newFile, fileName).send
Anuncio.create(label, fileName)
}
println("criou")
Redirect(routes.Application.anuncios)
}
)
}
def anuncios = Action {
Ok(views.html.index(Anuncio.all(), anuncioForm))
}
def newAnuncio = Action {
Ok(views.html.create(anuncioForm))
}
Non Working Code - Template
#(anuncioForm: Form[(String, String)])
#import helper._
#main("Criar um novo anuncio") {
#form(action = routes.Application.createAnuncio, 'enctype -> "multipart/form-data") {
#inputText(anuncioForm("label"))
#*<input type="file" name="imgName">*#
#inputFile(anuncioForm("imgName"))
<input type="submit" value="Create">
}
}
Edited
I used the code below to know the error message.
formWithErrors => BadRequest(views.html.index_error(formWithErrors.errorsAsJson))
Surprisingly I got the message below. I can't figure out why this message?!
{"imgName":["This field is required"]}
Do you need to have the file contents as a part of the form itself? You declare the imgName as a text in the form in your controller, but you use it as a input type="file" in your view, which I would imagine is contradictory.
A few things you could take a look at:
how to handle fileupload into a table in play framework? which talks about separating the file from the form as well as an (untested?) single submit for both a form and a file, and possible some other clues
How to include a picture type in a form in Play!2 in Scala? which is doing something similar to what you are doing
http://www.playframework.com/documentation/2.1.2/ScalaFileUpload

how to get autocomplete text box value using lift web

I am using Lift web framework.
I am implementing an auto-complete text box. When I enter some value in the box a drop-down list opens. If I select a value from that list, only then I am able to access value of text box. If I write a value by myself then I get an empty value.
My code :
var friend_name=""
"#bdayReminder" #> AutoComplete("",
getAllName _,
value => takeAction(value),
List("minChars" -> "3"))
private def takeAction(str: String) {
friend_name = str
}
Please suggest a solution
Disclaimer: I'm the author of following library.
I think lift-combobox could achieve what you want, since it has a feature that let user created the value on-the fly. It use select2 jQuery plugin, so you will have a nice look and feel for the drop-down menu.
For example if you need to get the user-created value, it will simply as the following, note that we usually using Option[T] to denote that the value may not be presented, for example, the user may not selected any item in drop-menu at all:
var friend_name: Option[String] = None
val friendsMenu = new ComboBox(
default = None,
allowCreate = true
) {
// This is where you build your combox suggestion
override def onSearching(term: String): List[ComboItem] = {
val names = List(
ComboItem("f1", "Brian"), ComboItem("f2", "Alice"),
ComboItem("f3", "Luke"), ComboItem("f4", "Smith"),
ComboItem("f5", "Brandon")
)
names.filter(_.text.contains(term))
}
override def onItemSelected(selected: Option[ComboItem]): JsCmd = {
friend_name = selected
// The returned JsCmd will be executed on client side.
Alert("You selected:" + selected)
}
// What you want to do if user added an item that
// does not exist when allowCreate = true.
override def onItemAdded(text: String): JsCmd = {
friend_name = Some(text)
}
}
"#bdayReminder" #> friendsMenu.combobox

Lift Scala - Redirect not working for Two Phased Form

I have a two stage form which im trying to implement in Lift Scala. I have the first stage of the form working but the second stage is not working as expected.
The first form asks the user for some input, on submission this will take the user to the second form with the details from the first form shown. The user can enter some text in the textfield and then click submit to view the form results. The second form should be made available to any other users so that they can enter a description for the title given by the initial user.
The way it should work is as follows - user 1 adds a title (form phase one), another user can enter a description (form phase two) and then submit the form to view the results. However, when a user enters the description on form phase two and clicks submit - nothing happens. No redirect and no error.
Any help on this is much appreciated.
My code is below, is this the correct approach?
class TwoPhaseForm extends DispatchSnippet with Logger {
def dispatch : DispatchIt = {
case "addformphaseone" => addformphaseone _
case "viewformphaseone" => viewformphaseone _
case "addformphasetwo" => addformphasetwo _
case "viewresults" => viewresults _
}
object currentAccountVar extends RequestVar[Entry]({
Entry.create.author(User.currentUser.open_!)
})
def currentAccount = currentAccountVar.is
//The first part of the form
def addformphaseone (xhtml : NodeSeq) : NodeSeq = {
def doSave () = {
currentAccount.validate match {
case Nil =>
currentAccount.save
S.redirectTo("/viewformphaseone?id=" + currentAccount.id)
case x => S.error(x)
}
}
val acct = currentAccount
bind("entry", xhtml,
"id" -> SHtml.hidden(() => currentAccountVar(acct)),
"title" -> SHtml.text(currentAccount.title.is, currentAccount.title(_)),
"submit" -> SHtml.submit("Submit", doSave))
}
//view the details from form phase one
def viewformphaseone(xhtml : NodeSeq) : NodeSeq = {
val t = Entry.find(S.param("id"))
t.map(t =>
bind("entry", xhtml,
"title" -> t.title.toString,
)) openOr <span>Not found!</span> <b>Not found!</b>
}
//Second phase of the form
def addformphasetwo (xhtml : NodeSeq) : NodeSeq = {
def doSave () = {
currentAccount.validate match {
case Nil =>
currentAccount.save
S.redirectTo("/results")
case x => S.error(x)
}
}
val t = Entry.find(S.param("id"))
t.map(t =>
bind("entry", xhtml,
"desc" -> SHtml.text(currentAccount.desc.is, currentAccount.desc(_)),
"submit" -> SHtml.submit("Submit", doSave)
)) openOr <span>Not found!</span> <b>Not found!</b>
}
//view the results from both forms
def viewresults(xhtml : NodeSeq) : NodeSeq = {
val t = Entry.find(S.param("id"))
t.map(t =>
bind("entry", xhtml,
"title" -> t.title.toString,
"desc" -> t.desc.toString,
)) openOr <span>Not found!</span> <b>Not found!</b>
}
}
a typical school boy error. from the "Exploring Lift book":
If the form attribute is included with a value of either “POST” or “GET”, then an appropriate form tag will be emitted into the XHTML using the specified submission method. If you omit this tag from a snippet that generates a form, the form elements will display but the form won’t submit.
I had missed the form="POST" tag in my html file. Hence the form was not being submited