【问题标题】:Scala/Play: load template dynamicallyScala/Play:动态加载模板
【发布时间】:2013-12-14 22:55:31
【问题描述】:

我有这个 Scala/Play 应用程序,我必须通过 AJAX 获取一堆模板。我现在正在做这样的事情:

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

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

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

//etc

但这只是为每个模板创建一个动作。我可以这样做吗:

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

这可以在 Play Framework 上实现吗?如果是那怎么办?

播放框架 2.2.1 / Scala 2.10.3 / Java 8 64bit

更新:我最初的问题可能被误解了。我不想编译模板,我想以更动态的方式获取已编译的模板。

UPDATE2:我想我找到了一些非常接近的东西,如果不是我需要的 on this answer,但它是在 Java 中的,我需要它在 Scala 中。

【问题讨论】:

  • 嗯,'我想获取已经编译的...' 这意味着你的视图不会编译任何东西,它们只是现成的 HTML页面?
  • @biesior 我第二次更新中的链接几乎涵盖了我需要的东西,只是我需要它在 Scala 中,而不是 Java。

标签: scala templates playframework playframework-2.0 playframework-2.2


【解决方案1】:

使用 scala 反射:

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])
 }

 }

【讨论】:

  • 我正在尝试使用它,但出现错误:Execution exception[[IllegalArgumentException: wrong number of arguments]]
  • 此代码假定您的模板确实采用任何参数。如果是,那么您需要将它们传递给 apply 方法
  • 所以我想这种方法只有在所有模板都没有参数或具有相同参数的情况下才有效?您将如何在示例中传递参数?
  • 我想我不清楚。模板被编译成函数对象。因此,如果您的模板采用参数,则需要将其传递给 apply 方法。好的(methodMirror.apply().asInstanceOf[Html])
【解决方案2】:

基于@Nilanjan 的回答和 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]
  }

【讨论】:

    【解决方案3】:

    我们又来了! @Nilanjan 对 Scala 2.12 和 Play 2.6 的回答(因为移至 DI play.api.Play.current,TermName 和声明已被弃用)。对于应用程序,我不得不使用注入器,因为 @Inject()(app: Application) 导致循环依赖

    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])
      }
    }
    

    【讨论】:

      【解决方案4】:

      允许通过任何文本(出于安全原因)进行搜索视图并不是一个好主意,因为这样可以在参数中传递,相反,您可以使用 match 语句很容易地解决这个问题 - 它允许您将请求限制为允许只查看视图,并且也会处理错误请求,可能 Scala 极客可以展示更好的代码,但这对你来说开箱即用:

      def loadTemplate(templateName: String) = Action {
      
        templateName match {
          case "about" => Ok(about())
          case "home" => Ok(home())
          case "contact" => Ok(contact())
          case _ => NotFound(notFoundView())
        }
      
      }
      

      路线:

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

      额外的好处是您可以从请求中获取更多数据并将其传递给已解析的视图,具体取决于 templateName 参数

      【讨论】:

      • 谢谢,我意识到我可以做到这一点,但是我希望有一个更动态的解决方案 - 不需要无数案例陈述的解决方案(在我的情况下)。
      • 如果不重新部署就无法添加新视图,可以吗?即使您要添加视图 zillion+1,您也需要重新启动 prod 应用程序,在这种情况下,将下一行添加到 loadTemplate 操作应该不是问题。另一方面,如果您要使用所有不带参数的视图(就像静态 HTML 页面),为什么不考虑将它们存储在数据库中呢?在这种情况下,您可以创建微型 CMS 来管理 zillion^10,这样您就可以在航班上添加/编辑/删除页面。
      • 我想我们这里有一个误解。我不想编译任何模板——它们在应用程序启动时都已经编译好了。我想要的是一种更动态的方式来获取它们 - 无需对所有这些 case 语句进行硬编码,仅此而已。
      • 这种解决方案是不可持续的,每次需要新的页面案例时都必须编辑和重新编译源代码,当我想根据语言隔离视图时更是如此。
      【解决方案5】:

      在您的模板文件中

      general_template.scala.html

      @(templateName : String = "none")
      
      @templateName match {       
      
       case "about" => { //html elements}
      
       case "home" => {//html elements}
      
       case "contact" => {//html elements}
      
       case _ => { }
      
      }
      

      在你的控制器中

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

      【讨论】:

        猜你喜欢
        • 2017-04-10
        • 1970-01-01
        • 2016-03-29
        • 2011-05-03
        • 1970-01-01
        • 2013-05-19
        • 1970-01-01
        • 1970-01-01
        • 2012-10-09
        相关资源
        最近更新 更多