2

In Play/Scala; I am confused as to why this won't compile:

   @(tabNames: Seq[String])

   @isActive(idx: Int) = {
       @if(idx < 1) {
           "active"
       } else {
           ""
       }
   }

   <ul class="nav nav-tabs">
       @for((tab, idx) <- tabNames.zipWithIndex) {
           @views.html.generic.navLi("tab" + idx.toString, tab, isActive(idx))
       }
   </ul>

The error reads:

found : play.twirl.api.HtmlFormat.Appendable [error] (which expands to) play.twirl.api.Html [error] required: String [error]
@views.html.generic.navLi("tab" + idx.toString, tab, isActive(idx))

It doesn't recognise the call to isActive within the call to the template and I have tried multiple variations, e.g. @isActive(idx), isActive(@idx) ${isActive(idx)} (as suggested here), etc. This template generates a navigation bar, passing in tab names and checking to see if the nav li should be active (configured by class name/JS).

It just seems that the syntax must be different when calling a function within another template call - I can't get it right.

2 Answers2

3

Documentation makes a distinction between

  • reusable code block
  • reusable pure code block

Note the subtle difference in @ usage between

@isActive(idx: Int) = {
  @if(...

@isActive(idx: Int) = @{
  if(...

Reusable pure code block can have arbitrary return type. In your case, to have String as return type, you could write:

   @isActive(idx: Int) = @{
       if(idx < 1) {
           "active"
       } else {
           ""
       }
   }
Mario Galic
  • 41,266
  • 6
  • 39
  • 76
1

The Play documentation is a little light in this area; while it is certainly possible and useful to declare "functions" in a twirl template, the return type seems to be locked to (effectively) Html - i.e. your "block" (as it is referred to in the documentation) must render HTML.

The quickest solution, as suggested in the documentation, is to relocate the logic into a Scala class; for something as simple as this, an object is the easiest way to get this done, i.e.:

package views

object ViewHelper {

  def isActive(idx: Int):String = {
    if(idx < 1) {
      "active"
    } else {
      ""
    }
  }
}

and:

<ul class="nav nav-tabs">
   @for((tab, idx) <- tabNames.zipWithIndex) {
       @views.html.generic.navLi("tab" + idx.toString, tab, ViewHelper.isActive(idx))
   }
</ul>

The advantages of this approach include testability and reusability. Just be careful to avoid getting carried away with what a ViewHelper can do - database lookups etc are a bad idea here! :-)

millhouse
  • 8,542
  • 4
  • 29
  • 39
  • Yes - the documentation definitely is *light* on this topic. Are you sure that the `views` package is the best place to put this `object`? Also I noticed that you didn't import `ViewHelper` - is this right? thanks – jesus g_force Harris Nov 25 '17 at 01:29
  • By placing it in `views` I don't __need__ to import it :-) – millhouse Nov 25 '17 at 03:59