Dynamically add textareas client-side to a form in lift - scala

I've got a form based on the sample in http://simply.liftweb.net/index-4.2.html#toc-Section-4.2 and I was wondering if there's a way of having a button on the page that would add a textarea each time it's clicked, and then in the lift code get that as an array of strings.
What I'm picturing is something like this:
<form class="lift:OnSubmit?form=post">
Name: <input name="name"><br>
Age: <input name="age" value="0"><br>
<span id="somecomments"></span>
<input type="button" onclick="$('#somecomments').append($('<textarea cols=80 rows=10 name=comments>'))" value="Add Comment"/>
<input type="submit" value="Submit">
</form>
//in scala:
object OnSubmit {
def render = {
var name = ""
var age = 0
var comments = List("")​
def process() {
S.notice("Name: "+name)
S.notice("Age: "+age)
S.notice(comments)
S.redirectTo("/")
}
}​
"name=name" #> SHtml.onSubmit(name = _) &
"name=age" #> SHtml.onSubmit(s => asInt(s).foreach(age = _)) &
"name=comments" #> SHtml.onSubmit(comments = _) &
"type=submit" #> SHtml.onSubmitUnit(process)
}
}
But I get the compile error that the comments field is a String so I can't assign it to a List with "name=comments" #> SHtml.onSubmit(comments = _)
What's the best way to make this code work?

for prepending:
"name=comments" #> SHtml.onSubmit(comments ::= _)
for appending:
"name=comments" #> SHtml.onSubmit(comments :+= _)

Here I describe how you can add any number of fields (I have a textarea and a "related" numeric field.
You add them using jQuery and then Lift gets all the data as a json object.
Dynamically adding fields to a Lift application

Related

[Play]Don't use multiple parameters in a route for display a view that takes multiple parameters

I'm writing a Play 2.3.2 application in Scala.
In My application I'm using a form representing an Integer range(start, finish).
The range class is written as following:
case class Range (
startDate: Long,
finishDate: Long)
I've defined a form like the following:
val rangeForm: Form[Range] = Form (
mapping(
"startDate" -> longNumber(min = 0, max = System.currentTimeMillis()),
"finishDate" -> longNumber(min = 0, max = System.currentTimeMillis())
)(Range.apply)(Range.unapply)
)
In my StatisticsController class I've defined three four:
calculateStatisticsOnRange(range: Range): Future[(Double, Int)]: that calculate the number and average of click of the user in a range;
getCalculateStatisticsOnRangeForm that display the form
calculateGoodAdvicesHtml that check if the form has errors
displayStatistics(average: Double, count:Int) that display the result of the search.
My form view is implemented like:
#(rangeForm: Form[recommendationsystem.models.Range])
#recommendationsystem.views.html.main("Insert a range")(recommendationsystem.views.html.nav.navbar("statistics")) {
<h1>Insert here the range</h1>
<div class="container">
#helper.form(action = recommendationsystem.controllers.manager.routes.StatisticsController.calculateGoodAdvicesHtml()) {
<label for="startDate">Start Date:</label>
<input type="number" class="form-control" placeholder = "start"
name="startDate" value="#rangeForm("startDate")" />
<input type="number" class="form-control" placeholder="finish"
name="finishDate" value="#rangeForm("finishDate")" />
<button type="submit" class="btn btn-default">Search</button>
}
</div>
}
My controller methods are implemented like:
def getCalculateStatisticsOnRangeForm = Action {
Ok(recommendationsystem.views.html.manager.statistics.forms.rangeform(rangeForm))
}
def calculateGoodAdvicesHtml = Action.async { implicit request =>
val form = rangeForm.bindFromRequest
form.fold(
errors => Future{BadRequest(recommendationsystem.views.html.manager.statistics.forms.rangeform(errors))},
range => {calculateStatisticsOnRange(range) flatMap {result => Future{Redirect(routes.StatisticsController.displayStatistics(result._1, result._2))}}}
)
}
def displayStatistics(average: Double, count:Int) = Action{
Ok(recommendationsystem.views.html.manager.statistics.goodadvise(average, count))
}
In my route file I've the following routes:
GET /statistics/good #recommendationsystem.controllers.manager.StatisticsController.getCalculateStatisticsOnRangeForm
POST /statistics/good #recommendationsystem.controllers.manager.StatisticsController.calculateGoodAdvicesHtml
POST /statistics/good/json #recommendationsystem.controllers.manager.StatisticsController.calculateGoodAdvicesJson
GET /statistics/:average/:count #recommendationsystem.controllers.manager.StatisticsController.displayStatistics(average: Double, count: Int)
I don't like to pass multiple parameters in the route of /statistics/:average/:count, there is an other way to make that?

how to attach multiple files to email using scala and liftweb

I'm trying to attach multiple files I'm writing REST Service for that but the files are not loading
my code look like below:-
HTML:-
<form action="DemoEmailAttachment" method="post">
To: <input type="text" name="to"><br>
Subject: <input type="text" name="subject"><br>
Body:<textarea rows="4" cols="50" name="body"></textarea><br>
AttachFiles:<input type="file" name="fileupload" multiple="multiple"><br>
<input type="submit" value="Send">
</form>
SCALA:-
object demoAttachment {
def sendAtchMail(request: Req) = {
val obj = request._params
println(obj)
LiftRules.handleMimeFile = OnDiskFileParamHolder.apply
val fileupload = request.uploadedFiles
println(fileupload)
val to = (obj.get("to").get)(0).split(",")
val subject = obj.get("subject").get
val body = obj.get("body").get
case class CSVFile(bytes: Array[Byte],filename:String,mime:String )
var data = Array[Byte]()
println(data.length)
val ls = fileupload.map(c => c match {
case ff:OnDiskFileParamHolder => { ff.fileStream.read(data)
val attach = CSVFile(data,ff.fileName,ff.mimeType)
val msg = XHTMLPlusImages(<p>Please research the enclosed. </p>,PlusImageHolder(attach.filename, attach.mime, attach.bytes))
Mailer.sendMail(
From("536#gmail.com"),
Subject(subject(0)),
To(to(0)),
msg
)
}
case _ => "there are no files"
} )
"success"
}
}
The XHTMLPlusImages constructor takes a vararg for the attachment parameter. So, just pass in multiple attachments, like this:
XHTMLPlusImages(NodeSeq, attach1, attach2, attach3, ...)
Also, you should verify that data actually contains what you are looking send. Unless I am mistaken, the way you are reading the data from the stream will only read in however many bytes your Array[Byte] can hold, which is probably 0. There is a good answer here that discusses reading from the InputStream to a ByteArray.

Lift (Scala) form validation

I have a question for which I couldn't find an answer anywhere in the web. I have a lift-based web application written on Scala. There is a form in my application which is driven by a snippet. I use Mapper for my models, and I use override def validations ... to define model's validation parameters. The template code looks like this:
<form class="lift:GroupManager.addGroup?form=POST&action=add">
<div class="row collapse" style="width: 30em">
<div class="small-8 columns">
<span class="group-name prefix radius"></span>
</div>
<div class="small-4 columns">
<span class="submit button small postfix radius">Add New Group</span>
</div>
</div>
</span>
</form>
The snippet code:
def addGroup = {
object groupName extends RequestVar[String]("")
def doAddGroup() = {
val group = Group.create.createdAt(new java.util.Date).groupName(groupName.is)
group.validate match {
case Nil => {
group.save
S.notice("Group '" + group.groupName.is + "' has been added successfully")
}
case errors:List[FieldError] => {
S.error(errors)
}
}
}
".group-name" #> SHtml.text(groupName.is, groupName(_), "id" -> "group-name") &
".submit" #> SHtml.submit("Add group", doAddGroup)
}
The model's validations override:
object groupName extends MappedString(this, 700) {
override def dbColumnName = "group_name"
override def dbNotNull_? = true
override def validations = valMinLen(1, S.?("Group name should not be empty!")) _ ::
valMaxLen(700, S.?("Group name should not be more than 700 characters long!")) _ ::
valUnique(S.?("Group name should be unique!")) _ ::
super.validations
}
Now everything is perfect except the fact that when invalid data is provided, S.error is used to inform user about problems. What I want to do is to highlight the form element (by applying class="error") in which the data is invalid and add a <span> element with the error message in front of the form element. Is there a simple way to do that with Lift framework? Or I should just traverse through errors:List[FieldError] and fetch field information from that array, which does not seem too elegant to me?

Loop over a list of objects and display it in lift

How do you look over an object and display the results, without having markup in your scala code?
I have the following code:
class User(id: Long, name: String)
class DisplayIt {
def display = {
val users = List(new User(0,"John"), new User(1, "James"))
"#name *" #> users.map(_.name) &
"#id *" #> users.map(_.id.toString)
}
}
//In the html:
<div class="lift:DisplayIt.display">
<div class="one-user">
User <span id="name"> has the id <span id="id">
</div>
</div>
What happens now is that I end with "User John James has the id 0 1", all within one div class="one-user".
How do I loop over it so I have one div class="one-user" for each user?
I know I can write the html/xml in scala code and do it that way, but is there a straightforward way to do it without any xml in the scala code?
Try
def display = {
val users = List(new User(0, "John"), new User(1, "James"))
".one-user *" #> users.map { u =>
"#name *" #> u.name &
"#id *" #> u.id.toString
}
}
Basically, you have to match a surrounding element first and apply a list of transformations to that.
Try
def list = { ".one-user *" #> users.map( n => {
"#name *" #> n.name) &
"#id *" #> n.id.toString)
}
) }

ASP.NET MVC 2 RC: How to use the EditorFor to render correct name attributes for a List<>?

In the MVC RC 2 docs, we find:
Expression-based helpers that render input elements generate correct name attributes when the expression contains an array or collection index. For example, the value of the name attribute rendered by Html.EditorFor(m => m.Orders[i]) for the first order in a list would be Orders[0].
Anyone care to link an example of the C# view code (using a List where the result can bind back to the Model upon post)?
Just as a reference, I use the following code to verify the model binds correctly round trip. It simply shows view that allows change, then displays a view with the edited data upon form submission.
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
var myStudents = new List<Student>();
myStudents.Add(new Student { Name = "Harry" });
myStudents.Add(new Student { Name = "Tom" });
myStudents.Add(new Student { Name = "Richard" });
var myClass = new Classroom {Students = myStudents};
return View(myClass); // EditorFor()
}
[HttpPost]
public ActionResult Index( Classroom myClass)
{
return View("IndexPost", myClass); // DisplayFor()
}
This code:
<% for (int count = 0; count < Model.Students.Count; count++ )
{ %><%=
Html.EditorFor(m => m.Students[count]) %><%
}
%>
Rendered this output:
<input class="text-box single-line" id="Students_0__Name" name="Students[0].Name" type="text" value="Harry" />
<input class="text-box single-line" id="Students_1__Name" name="Students[1].Name" type="text" value="Tom" />
<input class="text-box single-line" id="Students_2__Name" name="Students[2].Name" type="text" value="Richard" />
And when I posted the content, the display was this (because I have a Student.ascx):
<table>
<tr><td><span>Harry</span> </td></tr>
<tr><td><span>Tom</span> </td></tr>
<tr><td><span>Richard</span> </td></tr>
</table>
But that's it (I think). Next question is how to get rid of those name="" tags.