I have a map representing a table of contents, it contains Chapter
keys and List[Section]
values. Right now I am trying to loop through this in my template like this:
<dl>
@table_of_contents.foreach((e) => {
<dt>
@e._1.title
</dt>
for(section <- e._2){
<dd>
@section.title
</dd>
}
})
</dl>
I currently get no output in the <dl>
however.
I added a println(table_of_contents)
statement to the top of the template to ensure that the map did in fact have data and it printed:
{models.Chapter@1=BeanList size[4] hasMoreRows[false] list[models.Section@1, models.Section@2, models.Section@3, models.Section@4], models.Chapter@2=BeanList size[0] hasMoreRows[false] list[]}
perhaps I need to use an imperative style?
UPDATE:
Still working on this... got this variation to compile but no output.
<dl>
@table_of_contents.foreach{case(a, b) => {
<dt>
@a.title
</dt>
@displaySections(b)
}}
</dl>
...
@displaySections(sections: List[Section]) = {
@for(a_section <- sections) {
<dd>@a_section.title</li>
}
}
A type safe template engine based on Scala Play comes with Twirl, a powerful Scala-based template engine, whose design was inspired by ASP.NET Razor. Specifically it is: compact, expressive, and fluid: it minimizes the number of characters and keystrokes required in a file, and enables a fast, fluid coding workflow.
Razor minimizes the number of characters and keystrokes required when writing a view template, and enables a fast, fluid coding workflow.
tl;dr
The answers given so far (by @wbarksdale, @PlexQ and @Daniel C. Sobral in a comment) are good enough to target the problem described here.
But they're missing a real explanation about why the initial code, using foreach
, doesn't work.
It cannot work because foreach
returns Unit
.
Play concepts
Let me give a quick note/recall about how templates work.
The Scala templating system provided by default in Play Framework 2 is indeed built upon FP concepts and thus it uses a lot of immutable structures and so forth.
Moreover, such Scala template (let's say myTemplate.scala.html
) will be compiled into a regular Scala object
which has an apply
method called. This latter function enables us to call the object as a function with some parameters (those declared in the first line of the template).
This object
is also relying on a construction like BaseScalaTemplate
which is built with an output formatter (Html). This formatter will be able to take stuff (like a String
, Unit
, Seq[Int]
, Map[A,B]
, ...) and render it into HTML code.
Formatting will take place while using the _display_
method of BaseScalaTemplate
, which returns an instance of the formatted output. This display method will be called in the compiled code of the .scala.html
file in the object's apply
method's body.
So the body could end like that:
def apply/*1.2*/(testMap:scala.collection.immutable.Map[String, Int]):play.api.templates.Html =
_display_ {
Seq[Any](
_display_(
Seq[Any](
/*3.2*/testMap/*3.9*/.map/*3.13*/ { e =>
_display_(Seq[Any](_display_(Seq[Any](/*5.3*/e))))
}
)
)
)
}
See? The _display_
calls aren't mutating anything but they are composed in such a way that apply itself will return an instance of the formatted code (Html
)!
That gives us the clue...
yeah blah blah... now why?
After those lightnings given about the Play internals, we can now tackle the real question: why the hell is the ideomatic Scala code provided in the question post isn't working... read, doesn't output anything at all.
It's pretty simple, when using foreach
on a Map
, you're indeed looping over the items and adapting them to Html. But these computation won't be usable by the templating system because they are enclosed in the foreach
's loop. That is foreach
has to be used when side-effects are required for each item in the sequence... And return Unit
when it's done.
Since, the templating system will try to _display_
the result of foreach
on the given Map
it will simply render/format Unit
and thus an empty String
!
To conclude, just use map
which will return a new sequence holding the adapted items, the Html
instance.
Hmmm and what about the for
?
Yeah, you're right... Based on what has been said, why are the answers that proposed to used a for
loop working, since without yielding a value, a for
is equivalent to foreach
!? (Introducing yield
will end in a map
-like behavior)
The answer is in the code... The Template compiler will prepend the yield
keyword to the for
's body -- check this out here. :-D
Et voilà, it works too, since the generated stuffs in the for
's body will be attached to a returned sequence after it has finished.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With