Play 2.0 with Scala and Scaml, Part 2: Setup example data at startup and integrating Scalate

In this part I’ll show how to set up the web part. Especially setting up Scalate for the use of Scaml needs some extra effort as it is not part of the Play core. I’ll first show how to create some example data so we have something to see on the website later on. As a prerequisite for the integration of Scalate I’ll shortly describe how to add the needed dependency and get it all correctly setup in IntelliJ. Then I’ll show how to get Scalate with Scaml running within Play.

Setting up a global object for creation of example data

To get some example data in my (still in memory!!!) database, you’ll need some means to get something running exactly once per server startup. In Play this can be done in the global object. The global object is per convention the object with the name Global. If you want an Object named differently to be the global object, you can configure it in the application.config (look for the global object).

Here is how Global might look to set up some very simple data.

object Global extends GlobalSettings {

  override def onStart(app: play.api.Application) {
    val u1 = User.create(new User("u1", "pwd", "name1"))
    val u2 = User.create(new User("u2", "pwd", "name2"))

    Post.create( new Post("title1", new DateTime().toDate, "content number 1", u1))
    Post.create( new Post("title2", new DateTime().toDate, "content number 2", u2))
  }
}

One thing I am still wondering about is that when having the server run, the DB in the test cases also contains the entities set up on startup. If anyone knows why and how to avoid, please let me know.

Adding Scalate to the project and getting IntelliJ working

Adding the Scalate dependency to my project puzzled me a while, so I’ll describe a solution here hoping I can save some time of yours. I am using IntelliJ and for me it’s really important to have the IDE as working aid. The basic workflow for adding dependencies when using IntelliJ is as follows:

  1. Add the dependency to Build.scala
  2. Enter reload in the console
  3. Enter idea in the console

It’s important that there are no compilation errors when adding the dependency. So if you add code for which you need a certain new dependency you’ll have to comment this code out until you have added the dependency successfully.

So to add Scalate just add “org.fusesource.scalate” % “scalate-core” % “1.5.3” to appDependencies in your Build.scala and proceed as described above.

Getting Scalate to work

I have set up the Scalate templating engine in my company recently and am astonished how clean and DRY scaml is. For me as non-weby as well as for our webbies it became a pleasure to delve into our new mobile-page-code written in scaml. Structure and intention really pops out.

As mentioned above, there is no Scalate plugin for 2.0. After some research I found this 1.x example to integrate Scalate in Play. It’s more or less ready to go. You have to change the package for play.Play to play.api.Play and change the call in the controller to something like

Ok( Scalate("first.scaml").render('posts -> Post.findAll())).as(HTML)

But this sucks right? I don’t want to add as(HTML) when I trigger the rendering. But without the as(HTML) the content type is set to text and thus not rendered by the browser. To change this we have to define a Writable – that encodes the content – and a ContentType. Both are defined as implicits and need a type on which they work. I’ve defined a simple ScalateContent that wraps the rendered output and enables the correct application of the implicits.

Scalate format

I have changed the original code so that the format used can be defined in the application.config via¬†scalate.format. Right now this is only important for the default layout, as I explicitly state the format of the template to render in the controller as you can see above. I personally prefer it this way as it seems more explicit what exactly is meant. You can easily extend the apply method if you prefer calls like Scalate(“first”).render.

Default layout

Scalate uses default templates for stuff that layouts all pages. To enable this I added the default layout strategy. The default template can now be used as documented. In the Play project the path is app/views/layouts/default.{format}.

So after all this the code for the Scalate object looked like this:

package controllers

import play.api._
import http.{Writeable, ContentTypeOf, ContentTypes}
import mvc.Codec
import play.api.Play.current
import org.fusesource.scalate.layout.DefaultLayoutStrategy

object Scalate {

  import org.fusesource.scalate._
  import org.fusesource.scalate.util._

  var format = Play.configuration.getString("scalate.format") match {
    case Some(configuredFormat) => configuredFormat
    case _ => "test"
  }

  lazy val scalateEngine = {
    val engine = new TemplateEngine
    engine.resourceLoader = new FileResourceLoader(Some(Play.getFile("/app/views")))
    engine.layoutStrategy = new DefaultLayoutStrategy(engine, Play.getFile("/app/views/layouts/default." + format).getAbsolutePath)
    engine.classpath = Play.getFile("/tmp/classes").getAbsolutePath
    engine.workingDirectory = Play.getFile("tmp")
    engine.combinedClassPath = true
    engine.classLoader = Play.classloader
    engine
  }

  def apply(template: String) = Template(template)

  case class Template(name: String) {

    def render(args: (Symbol, Any)*) = {
      ScalateContent{
        scalateEngine.layout(name, args.map {
          case (k, v) => k.name -> v
        } toMap)
      }
    }

  }

  case class ScalateContent(val cont: String)

  implicit def writeableOf_ScalateContent(implicit codec: Codec): Writeable[ScalateContent] = {
    Writeable[ScalateContent](scalate => codec.encode(scalate.cont))
  }

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

}

Next time

From here on you should be able to use Scalate in Play 2.0, making your web development more fun and of course more efficient. Next time I’ll show how to set up the pages with Scaml, adding some sexieness with Less and some client side logic with¬†CoffeeScript.

Advertisements

ScalaTest with IntelliJ

I just had a couple of problems with IntelliJ and ScalaTest. Under some circumstances, some of the tests didn’t run anymore. I had the problems some times before, but didn’t find anything helpful in the net, yet. I was really going crazy about this, ’cause nothing seems to help. Well I managed to get around it. Sadly without knowing exactly what the problem was. I just creating a completely new project in IntelliJ (project file format .idea (standard) and ‘Java’ not ‘Plugin‘..) copied the sources over to the new one, re-init of maven, and now it seems to work.

I played around with the project structure before the re-init. Might have caused some problems. Hoping everything stays fine now.