4
votes

I have this Scala/Play application and I have to fetch a bunch of templates via AJAX. I'm doing something like this now:

def home = Action {
    Ok(views.html.home())
}

def about = Action {
    Ok(views.html.about())
}

def contact = Action {
    Ok(views.html.contact())
}

//etc

But this is just creating an action for every template. Can I do something like this instead:

def loadTemplate(templateName) = Action {
    //Load template from "views" with name being value of parameter templateName
}

Is this possible on Play Framework? If so then how?

Play Framework 2.2.1 / Scala 2.10.3 / Java 8 64bit

UPDATE: My original question might have been misunderstood. I don't want to compile a template, I want to fetch already compiled one in a more dynamic way.

UPDATE2: I think I found something very close, if not exactly what I need on this answer, but it's in Java and I need it in Scala.

5
Hm, 'I want to fetch already compiled one...' is that mean that your views doesn't compile anything and they are just ready-to-use HTML pages ? - biesior
@biesior the link in my second update pretty much covers what I need, it's just that I need it in Scala, not Java. - Caballero

5 Answers

4
votes

Using scala reflection:

object Application extends Controller {

  import reflect.runtime.universe._
  val currentMirror = runtimeMirror(Play.current.classloader)
  val packageName = "views.html."

  def index(name: String) = Action {
    val templateName = packageName + name

    val moduleMirror = currentMirror.reflectModule(currentMirror.staticModule(templateName))
    val methodSymbol = moduleMirror.symbol.typeSignature.declaration(newTermName("apply")).asMethod

    val instanceMirror = currentMirror.reflect(moduleMirror.instance)    
    val methodMirror = instanceMirror.reflectMethod(methodSymbol)

    Ok(methodMirror.apply().asInstanceOf[Html])
 }

 }
3
votes

Based on @Nilanjan answer and for play-2.4 scala-2.11.7

  def page(page: String) = Action.async { implicit request =>
    Future.successful(Ok(loadTemplate(page)))
  }

  import reflect.runtime.universe._
  import play.api._
  import play.twirl.api.Html
  val currentMirror = runtimeMirror(Play.current.classloader)
  val packageName = "views.html."
  private def loadTemplate(name: String) = {
    val templateName = packageName + name
    val moduleMirror = currentMirror.reflectModule(currentMirror.staticModule(templateName))
    val methodSymbol = moduleMirror.symbol.info.member(TermName("apply")).asMethod
    val instanceMirror = currentMirror.reflect(moduleMirror.instance)
    val methodMirror = instanceMirror.reflectMethod(methodSymbol)
    methodMirror.apply().asInstanceOf[Html]
  }
1
votes

Here we go again! @Nilanjan answer adopted to Scala 2.12 and Play 2.6 (as moving to DI play.api.Play.current, TermName and declaration have been deprecated). For app, I had to use injector because @Inject()(app: Application) was causing circular dependency

class HomeController @Inject()(injector: Injector, cc: ControllerComponents) extends AbstractController(cc) {
  def index(name: String = "index") = Action { implicit request: Request[AnyContent] =>
    import reflect.runtime.universe._

    val app = injector.instanceOf[Application]
    val currentMirror = runtimeMirror(app.classloader)
    val packageName = "views.html."

    val templateName = packageName + name

    val moduleMirror = currentMirror.reflectModule(currentMirror.staticModule(templateName))
    val methodSymbol = moduleMirror.symbol.typeSignature.decl(TermName("apply")).asMethod

    val instanceMirror = currentMirror.reflect(moduleMirror.instance)
    val methodMirror = instanceMirror.reflectMethod(methodSymbol)

    Ok(methodMirror.apply("some", "content").asInstanceOf[Html])
  }
}
0
votes

It's not quite good idea to allow search view by any text (for security reasons) as such can be passed in param, instead, you can resolve this quite easy with match statement - it will allow you restrict request to allowed views only and will handle wrong requests as well, probably Scala geeks can demonstrate nicer code, but this will work for you out of the box:

def loadTemplate(templateName: String) = Action {

  templateName match {
    case "about" => Ok(about())
    case "home" => Ok(home())
    case "contact" => Ok(contact())
    case _ => NotFound(notFoundView())
  }

}

route:

GET  /load-template/:templateName   controllers.Application.loadTemplate(templateName)

Additional benefit is that you can grab additional data from request and pass it to the resolved view depending on templateName param

0
votes

In your Template file

general_template.scala.html

@(templateName : String = "none")

@templateName match {       

 case "about" => { //html elements}

 case "home" => {//html elements}

 case "contact" => {//html elements}

 case _ => { }

}

and in your controller

def loadTemplate(templateName) = Action {
    Ok(views.html.general_template(templateName))
}