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>
}
}
Play in Scala uses the functional nature of Scala very well. Change this to a map that returns the elements, and it should work.
As per suggestion above, with the case, it turns it into a partial function that does what we want rather nicely!
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
returnsUnit
.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 Scalaobject
which has anapply
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 likeBaseScalaTemplate
which is built with an output formatter (Html). This formatter will be able to take stuff (like aString
,Unit
,Seq[Int]
,Map[A,B]
, ...) and render it into HTML code.Formatting will take place while using the
_display_
method ofBaseScalaTemplate
, 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'sapply
method's body.So the body could end like that:
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 aMap
, 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 theforeach
's loop. That isforeach
has to be used when side-effects are required for each item in the sequence... And returnUnit
when it's done.Since, the templating system will try to
_display_
the result offoreach
on the givenMap
it will simply render/formatUnit
and thus an emptyString
!To conclude, just use
map
which will return a new sequence holding the adapted items, theHtml
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, afor
is equivalent toforeach
!? (Introducingyield
will end in amap
-like behavior)The answer is in the code... The Template compiler will prepend the
yield
keyword to thefor
's body -- check this out here. :-DEt voilà, it works too, since the generated stuffs in the
for
's body will be attached to a returned sequence after it has finished.The solution I came up with looked like this. Basically it just avoids using functional programming which I am ok with for the time being, but I would still really like to see a working solution using scala functional style.