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!
Related
I'm a bit stuck and don't understand what's going on.
This one doesn't work
#Entity
#DynamicInsert
#DynamicUpdate
#SelectBeforeUpdate
#Table
class Entity {
#Column(nullable = false)
var owner: String = _
}
val myEntity = new Entity() {
owner = "some owner 1"
}
session.persist(myEntity)
Hibernate throws exception:
java.lang.IllegalArgumentException: Unknown entity:persistence.dao.EntityDaoTest$$anonfun$13$$anonfun$14$$anon$5
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:777)
This one works:
val myEntity = new Entity()
entity.owner = "some owner 1"
session.persist(myEntity)
Why? Why does hibernate don't recognize my Entity instance?
UPD:
#Sheinbergon, thanks, it's clear. I completely forgot that annotations are lost. Is there any possibility to set entity fields with some shortcut?
writing
val myEntity = new MyEntity()
myEntity.owner = "some owner"
myEntity.someOtherProperty = "value"
is super boring
One more question
This one works:
val parent = new Parent
parent.owner = "Our parent"
parent.addChild(new Child() {
name = "First parent's child"
addGrandChild(new GrandChild() {
name = "Grand child name"
addGrandGrandChild(new GrandGrandChild() {
name = "Grand Grand child name"
address = new Address() {
id = 1L
}
})
})
})
Why? Child, GrandChild, GrandGrandChild also created anonymously.
addChild, addGrandChild, addGrandGrandChild are just list mutators.
def addChild(child: Child): Unit = {
if (children == null) {
children = new util.ArrayList[Child]()
}
if (Option(child.parent).isEmpty) {
child.parent = this
}
children.add(child)
}
What you are doing here is instantiating a class anonymously in Scala , and well... that creates an anonymous implementation of your class Entity ( like instantiating an interface anonymously in Java).
you can see it by printing the class name - println(myEntity.getClass) in both cases
Annotations applied to the original class do not apply to the anonymous one (reflection can still find them in the super class, but that's up to the code scanning them) and I guess that's why you're getting the various JPA exceptions
In response to your added sub-questions
Regarding a shortcut - why don't you use companion objects for factories or turn this class into a case class (with defaults), allowing for nicer, more flexible initialization.
Regarding the second object graph(and assuming eachof your classes are annotated) - again it depends on how the reflective code treats the objects it scans. it's possible ( and more likely, given that it won't scan each member of the collection for annotations ) it takes annotation definitions from the erased type ( possible to get it's FQDN class name as ParameterizedType in Java's reflection API) of the collection and not from the actual members of the collection and that's why it works.
I'm not really sure what it does about field definitions though (they are only present in the "super" class), but there's no "magic" here, just plain old reflection scans.
I'm trying to fetch covers of the books that belongs to an author. So far, so good. But it generates a separate query for each book and takes 2 seconds to load a page, I think I'm doing something wrong.
I use eager loading with my comments table (a comment belongs to a user), but since I use polymorphic relations with images table (a image can belong to different kind of other tables, such as user, thing, or group, so I can't use foreign keys in images table since it's not a right convention), I couldn't find a way to achieve the same thing this time.
Image Model
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Image extends Model
{
public function imageable()
{
return $this->morphTo();
}
}
Person Model (Author)
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Person extends Model {
public function books()
{
return $this->belongsToMany('\App\Models\Thing', 'person_thing');
}
Thing Model (Books)
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Thing extends Model {
public function cover() {
return $this->morphMany('\App\Models\Image', 'imageable');
}
}
Controller
$findBooks = Person::with(array('books' => function($query)
{
$query->groupBy('original_name');
}))->find(52957);
$allbooks = $findBooks->books;
return view('frontend.index')->with('allbooks', $allbooks)
}
Current View
#foreach($allbooks as $allBooks)
#foreach($allBooks->cover as $value)
<img class="hund" src="{{$value->link}}" alt="">
#endforeach
#endforeach
Image:
From this post on Laracasts
$query->with([
'child' => function ($q) {
$q->where(’someCol', ’someVal’); //constraint on child
},'child.grandchild' => function ($q) {
$q->where(‘someOtherCol’, ‘someOtherVal’); //constraint on grandchild
}
]);
I don't think your problem is with eager loading or morph to many if your page takes 2s to load, ,Did you install laravel/debugbar to see exactly what takes 2 sec or how many queries u run?
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}]")
}
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
I have an object MainObject with a list of objects, SubObjects, among other things. I am trying to have the user click a link on the View to add a new SubObject to the page. However, I am unable to pass the MainObject I am working with into the Action method. The MainObject I currently receive is empty, with all its values set to null. How do I send my controller action the MainObject that was used to render the View originally?
The relevant section of the view looks like this:
<div class="editor-list" id="subObjectsList">
<%: Html.EditorFor(model => model.SubObjects, "~/Views/MainObject/EditorTemplates/SubObjectsList.ascx")%>
</div>
<%: Ajax.ActionLink("Add Ajax subObject", "AddBlanksubObjectToSubObjectsList", new AjaxOptions { UpdateTargetId = "subObjectsList", InsertionMode = InsertionMode.Replace })%>
The relevant function from the controller looks like this:
public ActionResult AddBlanksubObjectToSubObjectsList(MainObject mainobject)
{
mainobject.SubObjects.Add(new SubObject());
return PartialView("~/Views/MainObject/EditorTemplates/SubObjectsList.acsx", mainobject.SubObjects);
}
I ended up with the following:
View:
<div class="editor-list" id="subObjectsList">
<%: Html.EditorFor(model => model.SubObjects, "~/Views/MainObject/EditorTemplates/SubObjectsList.ascx")%>
</div>
<input type="button" name="addSubObject" value="Add New SubObject" onclick="AddNewSubObject('#SubObjectList')" />
Control:
public ActionResult GetNewSubObject()
{
SubObject subObject= new SubObject();
return PartialView("~/Views/TestCase/EditorTemplates/SubObject.ascx", subObject);
}
And, finally, I added this JQuery script:
function AddNewSubObject(subObjectListDiv) {
$.get("/TestCase/GetNewSubObject", function (data) {
//there is one fieldset per SubObject already in the list,
//so this is the index of the new SubObject
var index = $(subObjectListDiv + " > fieldset").size();
//the returned SubObject prefixes its field namess with "[0]."
//but MVC expects a prefix like "SubObjects[0]" -
//plus the index might not be 0, so need to fix that, too
data = data.replace(/name="\[0\]/g, 'name="SubObject[' + index + "]");
//now append the new SubObject to the list
$(subObjectListDiv).append(data);
});
}
If someone has a better way to do this than kludging the MVC syntax for nested objects onto a returned View using JQuery, please post it; I'd love to believe that there is a better way to do this. For now, I'm accepting my answer.