DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Coding
  3. Languages
  4. More Scalate Goodness for Play

More Scalate Goodness for Play

Matt Raible user avatar by
Matt Raible
·
Nov. 13, 11 · Interview
Like (0)
Save
Tweet
Share
4.83K Views

Join the DZone community and get the full member experience.

Join For Free

this article is the 6th in a series on about my adventures developing a web application with html5, play scala, coffeescript and jade. previous articles can be found at:

  1. integrating scalate and jade with play 1.2.3
  2. trying to make coffeescript work with scalate and play
  3. integrating html5 boilerplate with scalate and play
  4. developing with html5, coffeescript and twitter's bootstrap
  5. play scala's anorm, heroku and postgresql issues

last week, i wrote about my adventures with anorm and mentioned i'd made some improvements to scalate play interoperability. first of all, i've been using a scalate trait and scalatetemplate class to render jade templates in my application. i described this setup in my first article on scalate and play .

adding sitemesh features and default variables
when i started making my app look good with css, i started longing for a feature i've used in sitemesh. that is, to have a body id or class that can identify the page and allow per-page css rules. to do this with sitemesh, you'd have something like the following in your page:

  
<body id="signup"/>

and then read it in your decorator:

<body<decorator:getproperty property="body.id" writeentireproperty="true"><decorator:getproperty property="body.class" writeentireproperty="true"/>>
</decorator:getproperty>

as i started looking into how to do this, i came across play scala's scalacontroller and how it was populating play's default variables (request, response, flash, params, etc.). based on this newfound knowledge, i added a populaterenderargs() method to set all the default variables and my desired bodyclass variable.

def populaterenderargs(args: (symbol, any)*): map[string, any] = {
  val renderargs = scope.renderargs.current();

  args.foreach {
    o =>
      renderargs.put(o._1.name, o._2)
  }

  renderargs.put("session", scope.session.current())
  renderargs.put("request", http.request.current())
  renderargs.put("flash", scope.flash.current())
  renderargs.put("params", scope.params.current())
  renderargs.put("errors", validationerrors)
  renderargs.put("config", play.configuration)

  // css class to add to body
  renderargs.put("bodyclass", http.request.current().action.replace(".", " ").tolowercase)
  renderargs.data.tomap
}

implicit def validationerrors:map[string,play.data.validation.error] = {
  import scala.collection.javaconverters._
  map.empty[string,play.data.validation.error] ++ 
    validation.errors.asscala.map( e => (e.getkey, e) )
}

after adding this method, i was able to access these values in my templates by defining them at the top:

-@ val bodyclass: string 
-@ val params: play.mvc.scope.params
-@ val flash: play.mvc.scope.flash

and then reading their values in my template:

body(class=bodyclass)
...
- if (flash.get("success") != null) {
  div(class="alert-message success" data-alert="alert")
    a(class="close" href="#") &×
    | #{flash.get("success")}
- }
...
  fieldset
    legend leave a comment →
    div.clearfix
      label(for="author") your name:
      input(type="text" name="author" class="xlarge" value={params.get("author")})
    div.clearfix
      label(for="content") your message:
      textarea(name="content" class="xlarge") #{params.get("content")}
    div.actions
      button(type="submit" class="btn primary") submit your comment
      button(type="reset" class="btn") cancel

for a request like home/index, the body tag is now rendered as:

<body class="home index">

this allows you to group css styles by controller names as well as by method names.

next, i started developing forms and validation logic. i quickly discovered i needed an action() method like the one defined in scalatemplate's templatemagic class.

def action(action: => any) = {
  new play.mvc.results.scalaaction(action).actiondefinition.url
}

since templatemagic is an inner class, i determined that copying the method into my scalatetemplate class was the easiest workaround. after doing this, i was able to import the method and use it in my templates.

-import controllers.scalatetemplate._
...
form(method="post" class="form-stacked" id="commentform"
     action={action(controllers.profile.postcomment(workout._1.id()))})

after getting the proper url written into my form's action attribute, i encountered a new problem. the play scala tutorial explains validation flow as follows:

if (validation.haserrors) {
  show(postid)
} else {
  comment.create(comment(postid, author, content))
  action(show(postid))
}

however, when i had validation errors, i end up with the following error:

could not load resource: [timeline/postcomment.jade]

to fix this, i added logic to my scalate trait that looks for a "template" variable before using http.request.current().action.replace(".", "/") for the name. after making this change, i was able to use the following code to display validation errors.

if (validation.haserrors) {
  renderargs.put("template", "timeline/show")
  show(postid)
} else {
  comment.create(comment(postid, author, content))
  action(show(postid))
}

next, i wanted to give child pages the ability to set content in parent pages. with sitemesh, i could use the <content> tag as follows:

<content tag="underground">
  html goes here
</content>

this html could then be retrieved in the decorator using the <decorator:getproperty> tag:

<decorator:getproperty property="page.underground"/>

with scalate, i found it equally easy using the captureattribute() method. for example, here's how i captured a list of an athlete's workouts for display in a sidebar.

- captureattribute("sidebar")
  - option(older).filternot(_.isempty).map { workouts =>
    .older-workouts
      h3
        | older workouts
        span.from from this app
      - workouts.map { workout =>
        - render("workout.jade", map('workout -> workout, 'mode -> "teaser"))
      - }
  - }
- }

then in my layout, i was able to retrieve this and display it. below is a snippet from the layout i'm using (copied from twitter's bootstrap example ). you can see how the sidebar is included in the .span4 at the end.

-@ val sidebar: string = ""
...
.container
  .content
    .page-header
      h1
        = pageheader
        small
          = pagetagline
    .row
      .span10
        !~~ body
      .span4
        = unescape(sidebar)
  footer

view vs. render in scalate
in the sidebar code above, you might notice the render() call. this is the scalate version of server-side includes . it works well, but there's also a view() shortcut you can use if you want to have templates for rendering your model objects. i quickly discovered it might be difficult to use this feature in my app because my object was option[(models.workout, models.athlete, seq[models.comment])] instead of a simple object. you can read the view vs. render thread on the scalate google group if you're interested in learning more.

scalate as a module
the last enhancement i attempted to make was to put scalate support into a play module . at first, i tried overriding play's template class but ran into compilation issues . then guillaume bort (play's lead developer) recommended i stick with the trait approach and i was able to get everything working. i looked at the outdated play-scalate module to figure out how to add scala support to build.xml and copied its 500.scaml page for error reporting.

in order to get line-precise error reporting working, i had to wrap a try/catch around calling scalate's templateengine.layout() method. again, most of this code was copied from the outdated play-scalate module.

case class template(name: string) {
  
  def render(args: (symbol, any)*) = {
    val argsmap = populaterenderargs(args: _*)
    
    val buffer = new stringwriter()
    var context = new defaultrendercontext(name, scalateengine, new printwriter(buffer))
    
    try {
      val templatepath = new file(play.applicationpath+"/app/views","/"+name).tostring
        .replace(new file(play.applicationpath+"/app/views").tostring,"")
      scalateengine.layout(templatepath + scalatetype, argsmap)
    } catch {
      case ex:templatenotfoundexception => {
        if(ex.issourceavailable) {
          throw ex
        }
        val element = playexception.getinterestingstracktraceelement(ex)
        if (element != null) {
           throw new templatenotfoundexception(name, 
             play.classes.getapplicationclass(element.getclassname()), element.getlinenumber());
        } else {
           throw ex
        }
      }  
      case ex:invalidsyntaxexception => handlespecialerror(context,ex)
      case ex:compilerexception => handlespecialerror(context,ex)
      case ex:exception => handlespecialerror(context,ex)
    } finally {
      if (buffer.tostring.length > 0)
        throw new scalateresult(buffer.tostring,name)
    }
  }
}
...
private def handlespecialerror(context:defaultrendercontext,ex:exception) {
  context.attributes("javax.servlet.error.exception") = ex
  context.attributes("javax.servlet.error.message") = ex.getmessage
  try {
    scalateengine.layout(scalateengine.load(errortemplate), context)
  } catch {
    case ex:exception =>
      // todo use logging api from play here...
      println("caught: " + ex)
      ex.printstacktrace
  }
}

private def errortemplate:string = {
  val fullpath = new file(play.applicationpath,"/app/views/errors/500.scaml").tostring 
  fullpath.replace(new file(play.applicationpath+"/app/views").tostring,"")
}

once i had this in place, error messages from scalate are much better. not only do i see the error in my browser, but i can click on the offending line to open it directly in textmate.

play scalate error reporting

i've published my play-scalate module on github so others can try it out. to give it a whirl, add the following to your dependencies.yml:

 - upgrades -> play-scalate 0.1

repositories:
    - upgrades:
        type: http
        artifact: "http://static.raibledesigns.com/[module]-[revision].zip"
        contains:
            - upgrades -> *

then add with play.modules.scalate.scalate to your controllers and call the render() method.

summary
after using scalate and play for almost 3 months, i'm really enjoying the combination. when i first integrated scalate with a simple trait, the error messages were always in the console. now that i've borrowed some smarts from play's scalacontroller and play-scalate's error reporting, i feel like it's practically a built-in solution. i was easily able to integrate my desired sitemesh features and it even allows reusable template blocks . in the end, it's just scala and scalate does a good job of allowing you to leverage that.

other thoughts:

  • if you're writing a lot of jade and familiar with html, don park's html2jade is a great tool that comes with scalate support.
  • i'm really enjoying writing css with less , particularly the ability to nest rules and have programming features. the only issue i've seen is intellij's less plugin only does code-completion for variables rather than css values.
  • the play framework cookbook is a great reference for learning how to write modules. not only does it explain how to create modules, it has some great real-world examples for doing bytecode enhancement, implementing message queues, using solr and how to do production monitoring.

if this series of articles has intrigued you and you'll be at devoxx next week, you should stop by my talk on thursday afternoon . in addition, there's several other play talks at devoxx and a possible meetup on wednesday. check out the devoxx, anyone? thread for more information.

update : there's one thing i forgot to mention about the play scalate module. when i had scalate integrated in my app with a trait, i only included the scalate-core and scalate-util jars in dependencies.yml:

- org.fusesource.scalate -> scalate-core 1.5.2-scala_2.8.1:
    transitive: false
- org.fusesource.scalate -> scalate-util 1.5.2-scala_2.8.1:
    transitive: false

however, when i created the play-scalate module, i allowed more dependencies.

- org.fusesource.scalate -> scalate-core 1.5.2-scala_2.8.1:
    exclude:
        - javax.servlet -> *
        - com.sun.jersey -> *
        - org.osgi -> *
- org.fusesource.scalate -> scalate-util 1.5.2-scala_2.8.1
because scalate depends on logback , debug messages started showing up in my console. to fix this, i created conf/logback.xml in my project and filled it with the following xml.
<configuration>
  <appender name="stdout" class="ch.qos.logback.core.consoleappender">
      <encoder>
          <pattern>%msg%n</pattern>
      </encoder>
  </appender>

  <root level="info">
    <appender-ref ref="stdout" />
  </root>
</configuration>

this reduces the logging and allows me to increase scalate's logging if i ever have the need.

from http://raibledesigns.com/rd/entry/more_scalate_goodness_for_play

Template Scala (programming language) Web application Trait (computer programming) SiteMesh app Play Framework HTML5 Boilerplate Integration Object (computer science)

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Kubernetes-Native Development With Quarkus and Eclipse JKube
  • Seamless Integration of Azure Functions With SQL Server: A Developer's Perspective
  • Beyond Coding: The 5 Must-Have Skills to Have If You Want to Become a Senior Programmer
  • How To Perform Local Website Testing Using Selenium And Java

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: