Search code examples
scalaplayframeworkscalatags

Playframework: Use Scalatags instead of Twirl


I'd prefer to use the former over the latter, but am not sure how to incorporate Scalatags into the playframework.

This is my simple layout:

object Test
{
  import scalatags.Text.all._
  def build =
  {

    html(
      head(
        title := "Test"
      ),
      body(

        h1("This is a Triumph"),
        div(
          "Test"
        )
      )
    )
  }
}

This is how I try to render it:

Ok(views.Test.build.render)

Problem is, that I get it as a plain String, not as HTML.

Now, of course one solution would be to simply append.

Ok(views.Test.build.render).as("text/html")

but is that really the only way? (Without creating a helper method that is)


Solution

  • I assume you want to be able to call Ok(views.Test.build). Play is ignorant of ScalaTags so we're going to have to write something ourselves here.

    Play uses a bit of implicit machinery to generate HTTP responses. When you call Ok(...) you're actually calling apply on the play.api.mvc.Results trait. Let's take a look at its signature:

    def apply[C](content: C)(implicit writeable: Writeable[C]): Result
    

    So we can see that we need an implicit Writeable[scalatags.Text.all.Tag]:

    implicit def writeableOfTag(implicit codec: Codec): Writeable[Tag] = {
      Writeable(tag => codec.encode("<!DOCTYPE html>\n" + tag.render))
    }
    

    Don't forget to include a doctype declaration. ScalaTags doesn't give you one.

    That call to Writeable.apply itself requires another implicit to determine the content type. Here is its signature:

    def apply[A](transform: A => ByteString)(implicit ct: ContentTypeOf[A]): Writeable[A]
    

    So let's write an implicit ContentTypeOf[Tag]:

    implicit def contentTypeOfTag(implicit codec: Codec): ContentTypeOf[Tag] = {
      ContentTypeOf[Tag](Some(ContentTypes.HTML))
    }
    

    This allows us to avoid having to write as("text/html") explicitly and it includes the charset (courtesy of the implicit codec), resulting in a Content-Type header of text/html; charset=utf-8.

    Putting it all together:

    import play.api.http.{ ContentTypeOf, ContentTypes, Writeable }
    import play.api.mvc.Results.Ok
    import scalatags.Text.all._
    
    def build: Tag = {
      html(
        head(
          title := "Test"
        ),
        body(
    
          h1("This is a Triumph"),
          div(
            "Test"
          )
        )
      )
    }
    
    implicit def contentTypeOfTag(implicit codec: Codec): ContentTypeOf[Tag] = {
      ContentTypeOf[Tag](Some(ContentTypes.HTML))
    }
    
    implicit def writeableOfTag(implicit codec: Codec): Writeable[Tag] = {
      Writeable(tag => codec.encode("<!DOCTYPE html>\n" + tag.render))
    }
    
    def foo = Action { implicit request =>
      Ok(build)
    }
    

    You probably want to tuck those implicits somewhere convenient and then import them in your controller(s).