How can I output a list of things in a template in Lift?
Let say for example that I have List[User] and I want to output it as a table. In Django, I would use a context variable "users" and iterate through it in the template like so:
//controller
user = User.objects.all()
context = {'users' : users}
return render_to_template('results.html', context}
//view
<table>
{% for user in users %}
<tr><td>{{user.name}}</td>
<td>{{user.email}}</td>
</tr>
{% endfor %}
</table>
I appreciate any help.
PS: Could you also show me an example of the scala side - as I am clueless about how to approach this problem.
Template
<ul>
<lift:UserSnippet.showAll>
<li><foo:userName />: <foo:age /></li>
</lift:UserSnippet.showAll>
</ul>
Snippet Class
I'm assuming users is a List[User].
import scala.xml.NodeSeq
import net.liftweb.util.Helpers
class UserSnippet {
def showAll(in: NodeSeq): NodeSeq = {
users.flatMap { user => Helpers.bind("foo", in, "userName" -> user.name, "age" -> user.age) }
}
}
See the lift wiki articles on designer friendly templates and snippets for more information.
if you're looking to use a pure java list, say an ArrayList from a seperate java call...you can do it this way....
Make sure to import the java conversions, and your java class file where your list is being created
(i'm assuming we have a list of "people" objects that is being returned from your java file, which would include a name, age, and sex properties)
//SCALA Code
import scala.collection.JavaConversions._
import my.java.package.something._
import scala.xml.NodeSeq
import net.liftweb.util.Helpers
class mySnippet {
//You want to run the ".toList" on your java list, this will convert it into a scala list
val myScalaList = my.java.package.something.buildMyList().toList
//This is the function that will bind the list to the html view
def displayPeople(html : NodeSeq) : NodeSeq = {
myScalaList.flatMap{person => bind("info", html,
"name", person.name,
"age", person.age,
"sex", person.sex)}
}
}
//HTML code
<table>
<tr>
<td>Name</td>
<td>Age</td>
<td>Sex</td>
</tr>
<lift:mySnippet.displayPeople>
<tr>
<td><info:name></info:name></td>
<td><info:age></info:age></td>
<td><info:sex></info:sex></td>
</tr>
</lift:mySnippet.displayPeople>
</table>
Hope this helps :)
-kevin
Related
I have a basic java use class object that extends WCMUSE and a simple hashmap method - in the sightly code - I have something like
${item}
${item.key}
${item.value}
does not work - how do I return key/value pair in sightly code
There is an example at Sightly Intro Part 3 and the use of ${item} and ${itemList} as a variables is documented on the AEM Docs Sightly Page. This page also gives the following example for accessing dynamic values:
<dl data-sly-list.child="${myObj}">
<dt>key: ${child}</dt>
<dd>value: ${myObj[child]}</dd>
</dl>
Here is an example with a simple HashMap.
HTML with Sightly:
<div data-sly-use.myClass="com.test.WcmUseSample" data-sly-unwrap>
<ul data-sly-list.keyName="${myClass.getMyHashMap}">
<li>KEY: ${keyName}, VALUE: ${myClass.getMyHashMap[keyName]}</li>
</ul>
</div>
Java:
package com.test;
import java.util.HashMap;
import java.util.Map;
import com.adobe.cq.sightly.WCMUse;
public class WcmUseSample extends WCMUse {
private Map<String, String> myHashMap;
public void activate() throws Exception {
myHashMap = new HashMap<String, String>();
for (int i = 0; i < 10; ++i) {
myHashMap.put(""+i, "Hello "+i);
}
}
public Map<String,String> getMyHashMap() {
return myHashMap;
}
}
You can also try the following (AEM 6.4) :
Note the data-sly-test.hashMap for emptycheck.
<div data-sly-use.pageController="com.corp.wcms.core.pages.anypage.PageController"
<div data-sly-test.hashMap="${pageController.myHashMap}" data-sly-unwrap>
<ul data-sly-list.keyName="${hashMap}">
<li>KEY: ${keyName}, VALUE: ${hashMap[keyName]}</li>
</ul>
</div>
</div>
I have one controller (RegisteredController.java) , and I want that the output of the controller is displayed in the JSP (its name is commment_form.jsp). So I use a forEach tag in the jsp to display a list of comments (the comments which the user has inserted about a given resource). For "resource" I usually mean an image. So there are a list of comments about an "image" and I want that all the comments are all displayed in the bottom page, when a comment is going to be inserted into the comment form. My question is how must be written the code into the controller in order to set the output for the jsp ? Should I use a #ModelAttribute , a put-attribute or something else ? Here is the code of the controller and of the jsp :
The comment_form.jsp is:
<form:form modelAttribute="comments">
<table class="commento">
<tr>
<th/>
<th>ID</th>
<th>Contenuto</th>
</tr>
<c:forEach items = "${comments}" var="comment">
<tr>
<td><c:out value="${comments.content}"></c:out></td>
<td><c:out value="${comments.content}</c:out></td>
</c:forEach>
</table>
</form:form>
The RegisteredController.java is:
#RequestMapping("/comment.do")
public String comment(#ModelAttribute Comment comment, BindingResult
bindingResult, Model model, Locale locale) {
User user=userService.getUserCurrent();
comment.setDatePubblication(SQLUtility.getCurrentDate());
comment.setIdUser(user.getId());
commentService.create(comment);
Object[] args = { comment.getId() };
String message = messageSource.getMessage("message.update", args,locale);
List<Comment> comments =
commentService.findAllCommentByResource(comment.getIdResource());
model.addAttribute("comments", comments);
model.addAttribute("id",comment.getIdResource());
model.addAttribute("message", message);
model.addAttribute("comment", comment);
return "redirect:/registered/comment_start.do";
}
Please any help ? I will appreciate . Thanks you.
In case of redirect pass additional data as redirect attributes.
To carry data across a redirect use RedirectAttributes#addFlashAttribute(key, value).
What Java doc says:
A RedirectAttributes model is empty when the method is called and is never used unless the method returns a redirect view name or a RedirectView.
After the redirect, flash attributes are automatically added to the model of the controller that serves the target URL.
Read more...
One extra note :
In JSP it should be ${comment.content} instead of ${comments.content}
I'm trying to pass a book object from views to routes, and then send it to calculate in a controller. My code is following:
bookList.scala.html
#(books: java.lang.Iterable[Book])
#main("BookList"){
<div class="row">
#for(book <- books.iterator()){
<div class="col-sm-6 col-md-4">
<div class="thumbnail" style="height: 435px">
...
<p><a href="#routes.Application.buy(book)" class="btn btn-primary" role="button"
style="vertical-align:bottom">Order now!</a>
</div>
</div>
</div>
}
</div>
}
routes
...
GET /order controllers.Application.buy(book: models.Book)
...
However, It gave me an error : No QueryString binder found for type models.Book. Try to implement an implicit QueryStringBindable for this type.
I tried to change the routes as :
GET /order controllers.Application.buy(book)
It also returned an error :
type mismatch; found : String required: models.Book
That's not how Play routing works. The Play router parses variables from the URL or query string, and converts them to native types via the QueryBindable typeclass. You should have something more like this:
routes
GET /order/:bookid controllers.Application.buy(bookid: String)
And the action should be like:
Application.scala
def buy(bookid: String) = Action { request =>
// Look up book by id here.
Ok(views.html.thanks("you bought a book!"))
}
And the template like this:
bookList.scala.html
#for(book <- books.iterator()) {
...
<a href="#routes.Application.buy(book.id)" class="btn btn-primary"
}
Of course if your model's ID is other than String you need to modify the route's param type
Update -- alternative using form/POST
A form with a POST method is a better solution, or the user will buy another book each time they click the URL, and the id will be exposed. Check out the forms documentation. Your template would be like this:
#for(book <- books.iterator()) {
...
<form method="post">
<div>#book.name</div>
<input type="hidden" value="#book.id"/><button type="submit">buy</button>
</form>
}
You can't simple pass object (bean) as a url query parameter.
Default you can only define simple types as a parameter types. Please read carefully play's rounting documentation -> http://www.playframework.com/documentation/2.2.x/ScalaRouting especially Parameter types
But play framework has possibility to "learn" how to interpret specific url to bean.
You get info about this in error witch you posted. Responsible for this QueryStringBindable -> http://www.playframework.com/documentation/2.2.1/api/java/play/mvc/QueryStringBindable.html
In short like in documentation, when you define class like this:
class Book implements QueryStringBindable<Book> {
public String title;
public int numpages;
public Option<Pager> bind(String key, Map<String, String[]> data) {
if (data.contains(key + ".title" && data.contains(key + ".numpages") {
try {
title = data.get(key + ".title")[0];
numpages = Integer.parseInt(data.get(key + ".numpages")[0]);
return Some(this);
} catch (NumberFormatException e) {
return None();
}
} else {
return None();
}
}
public String unbind(String key) {
return key + ".title=" + title + "&" + key + ".numpages=" + numpages;
}
public String javascriptUnbind() {
return "function(k,v) {\n" +
" return encodeURIComponent(k+'.title')+'='+v.title+'&'+encodeURIComponent(k+'.numpages')+'='+v.numpages;\n" +
"}";
}
}
Then you can define route like:
GET /order controllers.Application.buy(p: Book)
You can then run in your browser e.g link:
localhost:9000/?p.title=SomeTitle&p.numpages=235
And in buy controller you will get p parameter as a Book class instance.
I did't test this code and this is in java. But you should get the idea.
You can define a custom PathBindable to automatically convert an id to a book object and pass it to buy method in controller. See PathBindable, another example
NOTE: I would define the PathBindable in same file as the Book model.
Simple example I used to parse in UUID's using PathBindable.
I'd rather not have to parse UUID's and ensure they're well formed in all controller/action code, so I use this at the router level. I used a UUIDP (P for param) to not pollute the UUID namespace.
Case Class and Parser
case class UUIDP(key: String, value: String, uuid: UUID)
object UUIDP {
implicit def pathBinder(implicit intBinder: PathBindable[String]) = new PathBindable[UUIDP] {
val uuidRegex = "([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})".r
override def bind(key: String, value: String): Either[String, UUIDP] = {
value match {
// sanity check, prevent errors, is this really a UUID?
case uuidRegex(c) => {
//error check too, binary value can be invalid
try { Right(UUIDP(key,value,UUID.fromString(value))) }
catch {
case ex : IllegalArgumentException => Left("Unparsable UUID")
}
}
case _ => Left("Invalid UUID Format")
}
}
// in case we need to remove this from the request
override def unbind(key: String, user: UUIDP): String = {
intBinder.unbind(key, user.value)
}
}
}
conf/routes
GET /items/:id #com.foo.MyController.getItem(id: UUIDP)
Controller
def getItem(sessionId: UUIDP) = Action(parse.text) { request =>
Ok(s"Fetching item [${sessionId.uuid}]")
}
I'm a newbie in scala. I am trying to build navigation tree like
Parent
Child1
Child2
Child3
Child4
I have a model class for it.
case class Field(id:Long, name:String, icon:String, parentid:Long)
object Field {
def all():List[Field] = {
List(
Field(1,"Parent1","1", 0),
Field(2,"Child1","2",1),
Field(3,"Child2","3",1),
Field(4,"Child3","4",1),
Field(5,"Child4","5",1)
)
}
}
And in my view I have a code for rendering:
#fields.groupBy(_.parentid).map { case ( parentid, tasks) =>
<ul>
<li>#parentid</li>
<ul>
#tasks.map { task =>
<li>#task.name</li>
}
</ul>
</ul>
}
But unfortunately the output is:
1
Child1
Child2
Child3
Child4
0
Parent1
How to build navigation menu, where model has child/parent relationship?
I hope you understand my question, and will help me. Sorry for my bad english
It would be quite tricky to convert the structure that you have to a tree. You would need to define another type to represent a tree node, and you would need a recursive function to process your list of Field.
I suggest the following structure instead:
case class TreeNode(name: String, icon: String, children: TreeNode*)
object Menu {
val tree = TreeNode("parent", "0",
TreeNode("child1", "1"),
TreeNode("child2", "2"),
TreeNode("child3", "3",
TreeNode("grandchild1", "4"))
)
}
You will need a recursive function ("code block" in a template) to render it:
#renderNode(n: TreeNode) = {
<li>#n.name</li>
#if(!n.children.isEmpty) {
<ul>
#for(c <- n.children) {
#renderNode(c)
}
</ul>
}
}
<ul>
#renderNode(Menu.tree)
</ul>
I haven't tried recursive code blocks in a Play template, so I have no idea if this will work!
I have a helper file utils.scala.html which looks like below:
#renderTableRow(columnTag: String, columns: Seq[String]) = {
<tr>
#for(column <- columns) {
<#columnTag>
#column
</#columnTag>
}
</tr>
}
I want to call this helper function from rest of my view files to create table headers.
#import views.html.mycommon.utils
#renderQuotesTable() = {
<table class="table table-bordered table-striped">
<thead>
#utils.renderTableRow("th", Seq("Name", "Date of Birth", "Age"))
</thead>
<tbody>
</tbody>
}
But, I get the following the error
value renderTableRow is not a member of object views.html.mycommon.utils.
what am I missing here?
You cannot import the declared functions of another template. Execute sbt doc and in the generated Scala Doc is no clue of renderTableRow in the util object. "renderTableRow" is wrapped into the apply method as you can see in the generated source for the template: "target/scala-2.10/src_managed/main/views/html/mycommon/utils.template.scala".
For every function you want to use in another template you hava to create a template or a function in a real Scala singleton object.