Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play! Framework 2.0 - Looping through a map in a scala template?

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>
  }
}
like image 708
wfbarksdale Avatar asked Mar 30 '12 16:03

wfbarksdale


People also ask

What is twirl template?

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.

Which technique enables a fast coding workflow minimizes the number of characters and keystrokes required in a file?

Razor minimizes the number of characters and keystrokes required when writing a view template, and enables a fast, fluid coding workflow.


1 Answers

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.

like image 154
Andy Petrella Avatar answered Oct 24 '22 07:10

Andy Petrella