I have a form with 2 fields - empno and name. Both fill up with default value. When display in view, I want empno is read-only and name is editable.
In view creation, I am using #leaveform.value.get.empno to display ready-only and work fine. The problem only occur during insert with error ([NoSuchElementException: None.get]).
Questions:
The problem is return error does not have value property. What else could I use to get the value?
Could I skip #inputText for read-only field?
Refer below my code:
// ***** CONTROLLER *****//
val leaveform = Form[LeaveModel](
mapping(
"empno" -> nonEmptyText,
"name" -> nonEmptyText
)((no, empno) => LeaveModel(empno, name))
((leave: LeaveModel) => Some(leave.empno, leave.name))
)
def create = withAuth { username => implicit request =>
// Define default values
val empno = "STUDENT"
val name = ""
// Set default values
val filledForm = leaveform.fill(LeaveModel(empno,name))
Ok(html.leave.form(filledForm))
}
def insert = Action (
implicit request => {
leaveform.bindFromRequest.fold(
error => {
BadRequest(html.leave.form(error)) // Question 1. Here is the error.
},
leave => {
LeaveModel.insert(leave)
Redirect(routes.indexController.index())
}
)
}
)
// ***** VIEW START***** //
#(leaveform: Form[LeaveModel])
#leaveform.value.get.empno
#helper.form(
action = (routes.LeaveController.update(oid)),
'id -> "leaveform") {
#inputText(leaveform("empno")) // Question 2.
#inputText(leaveform("name"))
}
It is not mandatory to use the form helpers. If you use them you can pass the attribute readonly or style the field with CSS to make it look read only.
Twitter bootstrap disabled by CSS:
#inputText(
editForm("createdOn"),
'id -> "createdOn",
'class -> "input-xlarge disabled",
'_label -> Messages("createdOn"),
'_help -> ""
)
Pass optional attribute: readonly
#inputText(
editForm("createdOn"),
'id -> "createdOn",
'class -> "input-xlarge",
'_label -> Messages("createdOn"),
'readonly -> "readonly",
'_help -> " This is read only"
)
You can also don't resend the field, but display its value:
<span class="date">Created on: #editForm("createdOn").value</span>
Update 2018-01-24
Play field is now returning a Optional, see the docs. This means you can get the value from the field like:
#form("fieldName").getValue.get (can throw a NPE)
#form("fieldName").getValue.getOrElse("defaultValue")
Try using the Flash context when returning a form to the user:
def create = withAuth { username => implicit request =>
// leaveForm initialization as before
// use form data from flash if present
val form = if (flash.get("error").isDefined)
leaveForm.bind(flash.data)
else
leaveForm
Ok(stakeholders.register(form))
}
def insert = Action { implicit request =>
leaveForm.bindFromRequest.fold(
hasErrors = { form =>
Redirect(routes.Leaves.create). // put correct route here
flashing(Flash(form.data) +
("error" -> Messages("validation.errors"))) // customize error message
},
leave => {
LeaveModel.insert(leave)
Redirect(routes.indexController.index())
}
)
}
HTH, Erich
Related
My form looks like:
case class PasswordData(currentPassword: String, newPassword: String, verifyPassword: String)
val passwordForm = Form(
mapping(
)(PasswordData.apply)(PasswordData.unapply) verifying("Passwords do not match", fields => fields match {
case data => (data.newPassword == data.verifyPassword)
})
)
My controller action follows the usual pattern:
passwordForm.bindFromRequest.fold(
error => {},
form => {}
)
The problem I have now is I need to verify if the entered 'currentPassword' is the same as what is on the user's object.
userDao.getById(userId).password == form.currentPassword
But I can't do this because I am not sure how to pass int he userId to my form definition since it is dynamic.
i.e. I can't do it like:
"currentPassword" -> nonEmptyText.verifying(....) // userId not in scope
Update
I am trying to display these errors also using (they don't currently display the error, I only see the ul tags).
#if(form.hasGlobalErrors) {
<ul>
#form.errors.foreach { error =>
<li>#error.message</li>
}
</ul>
}
Nothing is stopping you from making your passwordForm a def, where you can pass in a user model.
def passwordForm(user: User) = Form(
mapping(
"currentPassword" -> nonEmptyText.verifying("Incorrect password.", enteredPassword =>
// some comparison with `enteredPassword` and the user
),
...
)(PasswordData.apply)(PasswordData.unapply)
.verifying("Passwords do not match", data =>
data.newPassword == data.verifyPassword
)
)
passwordForm(user).bindFromRequest.fold(
error => ...,
form => ...
)
Also, to print out the global errors in the view, they are accessed via globalErrors, and not the normal errors field. You should also use map instead of foreach. foreach returns Unit, which will not print anything to the view.
#if(form.hasGlobalErrors) {
<ul>
#form.globalErrors.map { error =>
<li>#error.message</li>
}
</ul>
}
My ultimate goal is to append * to every label where the associated field is mandatory.
The problem is that within the FieldElements.constraints there are no constraints if I have a list of elements.
Here is a code sample showing form with a list of addresses:
object Forms {
val testForm: Form[TestForm] = Form {
val address = mapping(
"addressLine" -> nonEmptyText,
"city" -> nonEmptyText,
"country" -> nonEmptyText.verifying(nonEmpty)
)(Address.apply _)(Address.unapply)
// to form mapping for TestForm
mapping(
"mand_string" -> nonEmptyText,
"non_mand_string" -> optional(text),
"address_history" -> list(address) // repeated values are allowed for addresses
)( TestForm.apply _ )( TestForm.unapply )
}
}
Here is the field constructor logic to add *s to mandatory fields:
object MyHelpers {
implicit val myFields = FieldConstructor( fieldElem => {
Logger.debug( "Label = " + fieldElem.label )
Logger.debug( "Constraints = " + fieldElem.field.constraints )
val new_elem = fieldElem.copy( args = appendAsteriskToMandatoryFields(fieldElem) )
views.html.fs1(new_elem)
})
/** Adds a * to the end of a label if the field is mandatory
*
*/
private def appendAsteriskToMandatoryFields(fieldElem: FieldElements): Map[Symbol,Any] = {
fieldElem.args.map{ case(symbol, any) =>
if(symbol == '_label && isRequiredField(fieldElem)){
(symbol, any + "*")
} else {
symbol -> any
}
}
}
/** Does this element have the constraint that it is required?
*
*/
private def isRequiredField(fieldElem: FieldElements): Boolean = {
fieldElem.field.constraints.exists{case(key, _) => key == "constraint.required"}
}
}
I expect to see *s appended to all form elements except non_mand_string but here is the resulting page: http://s29.postimg.org/5kb5u2hjb/Screen_Shot_2014_08_05_at_1_49_17_PM.png
Only man_string has a * appended. None of the address fields have *s as expected.
Here is the output of the logs:
[debug] application - Label = Mandatory String
[debug] application - Constraints = List((constraint.required,WrappedArray()))
[debug] application - Label = Non-Mandatory String
[debug] application - Constraints = List()
[debug] application - Label = Address Line
[debug] application - Constraints = List()
[debug] application - Label = City
[debug] application - Constraints = List()
[debug] application - Label = Country
[debug] application - Constraints = List()
Is it possible to assign these constraints so I don't have to manually add *s to every list instance in my application?
Thank you in advance.
I've been struggling with this problem today: I'm using #repeat helper and I've found the constraints being tied to the single repeated field's name (i.e. "field_name") instead of each repeated field (i.e. "field_name[0]")
The solution I've found so far is just to rebuild the field, using the right constraints.
#repeat(theForm("repeated_field")) { field =>
#myInputNumber(
Field(
form = theForm, name = "name",
constraints = theForm.constraints.find(_._1 == "repeated_field").get._2,
format = field("field_name").format,
errors = field("field_name").errors,
value = field("field_name").value
))
}
Of course this code is just an example, you should probably check for empty Constraints or existing ones.
Hope to be helpful,
Pietro
I'm trying to get states into my spine application. On click, an item is added to a list. An url is then created and navigated to, and then the list get's rendered. However, when I use the browsers "back" functionality, the list doesn't change back to the previous state. How to get this to work?
This is the relevant code, I left out what seemed irrelevant, but if more is required I'll provided it.
class App extends Spine.Controller
constructor: ->
super
#products = new Products
#filters = new Filters
Filter.bind 'filterAdded', => #navigateAfterFilterChange()
Filter.bind 'filterRemoved', => #navigateAfterFilterChange()
Spine.Route.setup( history: false )
renderAll: ->
#products.render()
#filters.render()
navigateAfterFilterChange: ->
Spine.Route.navigate( encodeURIComponent( JSON.stringify( _.map( Filter.active_filters, ( filter ) -> { t: filters.type, v: filters.value } ) ) ) )
class Filter extends Spine.Model
listen: => #This is used to make the right list item listen to a click event
$("'a[data-cid=\"#{ #id }\"]'").one 'click', #triggerFilter
triggerFilter: (e) =>
e.preventDefault()
filter = Filter.find( $( e.currentTarget ).attr( 'data-cid' ) )
if #active
#active = false
Filter.active_filters = _.reject Filter.active_filters, (x) -> ( x.type is filter.type and x.value is filter.value )
#save()
Filter.trigger 'filterRemoved', #
else
#active = true
Filter.active_filters.push filter
#save()
Filter.trigger 'filterAdded', #
class Filters extends Spine.Controller
constructor: ->
super
#utils = new GeneralScripts
#createFilters()
#listenForTypeActivation()
#routes
"": ( params ) ->
#render()
"*glob": ( params ) ->
#render()
Not sure, but looks like you may need to send params with render()
class Filters extends Spine.Controller
constructor: ->
super
#utils = new GeneralScripts
#createFilters()
#listenForTypeActivation()
#routes
"": ( params ) ->
#render(params)
"*glob": ( params ) ->
#render(params)
you would also need to handle params in the render method of course
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
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