Xitrum学习笔记03 - Action和View

Action:

Xitrum 提供了3种Action:普通Action, FutureAction 和 ActorAction

它们都是Trait,而不是 Class

1. 普通Action:

当请求到来时,Action实现类直接在Netty的 IO线程上运行,不能用普通Action来执行 消耗很长时间的处理,否则Netty就不能接收新的连接或发出响应到客户端

import xitrum.Action
import xitrum.annotation.GET
@GET("hello")
class HelloAction extends Action {
  def execute() {
    respondText("Hello")
  }
}

 

2. FutureAction:

扩展了 Action,FutureAction实现类异步运行在和ActorAction相同的线程池上,这个线程池和Netty线程池是分离的

FutureAction实现类的执行context是 xitrum.Config.actorSystem.dispatcher.

trait FutureAction extends Action

import xitrum.FutureAction
import xitrum.annotation.GET
@GET("hello")
class HelloAction extends FutureAction {
  def execute() {
    respondText("hi")
  }
}

 

3. ActorAction:

trait ActorAction extends Actor with Action

实现类是一个 Akka actor。

当有需求时,一个actor实例被创建。当连接关闭时,或者响应通过 respondText/respondView 等方法被发出时,actor会被停止。

对于分块的响应,actor会在最后一个块儿的响应发出后才被停止。

actor运行在命名为"xiturm"的Akka actor系统的线程池中。

 1 import scala.concurrent.duration._
 2 import xitrum.ActorAction
 3 import xitrum.annotation.GET
 4 @GET("actor")
 5 class HelloAction extends ActorAction {
 6   def execute() {
 7     // See Akka doc about scheduler 9     context.system.scheduler.scheduleOnce(3.seconds, self, System.currentTimeMillis())
10     // See Akka doc about "become"
11     context.become {
12       case pastTime =>
13       respondInlineView(s"It's $pastTime Unix ms 3s ago.")
14     }
15   }
16 }

响应(Response)方式:

1.  对于action来说,要向客户端发出响应,可以调用如下方法

• respondView: responds view template file, with or without layout
• respondInlineView: responds embedded template (not separate template file), with or without layout
• respondText("hello"): responds a string without layout
• respondHtml("<html>...</html>"): same as above, with content type set to “text/html”
• respondJson(List(1, 2, 3)): converts Scala object to JSON object then responds
• respondJs("myFunction([1, 2, 3])")
• respondJsonP(List(1, 2, 3), "myFunction"): combination of the above two
• respondJsonText("[1, 2, 3]")
• respondJsonPText("[1, 2, 3]", "myFunction")
• respondBinary: responds an array of bytes
• respondFile: sends a file directly from disk, very fast because zero-copy (aka send-file) is used
• respondEventSource("data", "event")

2. 响应返回事先创建好的View模板文件内容

要运用这种方式,每个Action scala文件都要对应一个View jade文件。代码示例如下:

scr/main/scala/mypackage/MyAction.scala:

package mypackage
import xitrum.Action
import xitrum.annotation.GET
@GET("myAction")
class MyAction extends Action {
  def execute() {
    respondView()
  }
  def hello(what: String) = "Hello %s".format(what)
}

 

scr/main/scalate/mypackage/MyAction.jade:

- import mypackage.MyAction
!!! 5
html
  head
    != antiCsrfMeta
    != xitrumCss  //包含了Xitrum默认的CSS。这个CSS是可以移除的
    != jsDefaults //包含了 jQuery、jQuery验证插件等等内容,要放在<head>标签中
    title Welcome to Xitrum
  body
    a(href={url}) Path to the current action
    p= currentAction.asInstanceOf[MyAction].hello("World") //将currentAction转换为MyAction,并调用其hello方法
    != jsForView  //包含了由jsAddToView标签添加的JavaScript代码,jsForView标签应该放在整个layout的底部

在View模板中,可以使用 xitrum.Action中定义的所有方法,而且也可以使用由Scalate提供的一些utility方法(参考 Scalate doc)

默认的 Scalate 模板类型是 Jade,其他的有 Mustache, Scaml 和 Ssp。在config/xitrum.conf文件中配置默认模板类型.

  # Comment out if you don't use template engine.
  template {
    "xitrum.view.Scalate" {
      defaultType = jade  # jade, mustache, scaml, or ssp
    }
  }

也可以在Action实现类调用 respondView时指定模板类型

val options = Map("type" ->"mustache")
respondView(options)

如果需要在View中多次调用Action实现类的的方法的话,只需要将currentAction做一次转换,如:

- val myAction = currentAction.asInstanceOf[MyAction]; import myAction._
p= hello("World")
p= hello("Scala")
p= hello("Xitrum")

Layout

利用respondView或respondInlineView提交一个View时,Xitrum会把View内容表示成一个String,然后把这个String设置给renderedView变量。

Xitrum调用当前Action的layout方法,把方法的结果返回给浏览器。如果当前Action的layout方法没被调用,则没有结果返回给浏览器。

默认的layout方法只返回 renderedView 本身,可以通过重写layout方法以添加关于view的其他内容。如果重写的layout方法中包含renderedView,它只是作为layout的一部分内容。

layout方法在action的view生成之后才被调用,layout方法返回的内容就是要响应到浏览器中的内容。

可以通过创建一个继承自Action的Trait,并将其相应的View模板文件定义为通用layout。再让其他Action scala实现这个Trait,这样其他Action的页面就会包含这个通用的layout。

示例:

src/main/scala/mypackage/AppAction.scala

package mypackage
import xitrum.Action
trait AppAction extends Action {
  override def layout = renderViewNoLayout[AppAction]() //将AppAction.jade中的内容作为页面内容的模板
}

src/main/scalate/mypackage/AppAction.jade

!!! 5
html
  head
    != antiCsrfMeta
    != xitrumCss
    != jsDefaults
    title Welcome to Xitrum
  body
    != renderedView  -#如果没有这一句,使用这个模板的其他View的内容不会呈现
    != jsForView

src/main/scala/mypackage/MyAction.scala

package mypackage
import xitrum.annotation.GET
@GET("myAction")
class MyAction extends AppAction {
  def execute() {
    respondView()
  }
  def hello(what: String) = "Hello %s".format(what)
}

scr/main/scalate/mypackage/MyAction.jade:

- import mypackage.MyAction
a(href={url}) Path to the current action
p= currentAction.asInstanceOf[MyAction].hello("World")

此示例验证成功,AppAction.jade中的body需要含有!=renderedView

layout不在View模板里的示例(直接写到 Action Scala文件里)

示例1:

import xitrum.Action
import xitrum.view.DocType
trait AppAction extends Action {
  override def layout = DocType.html5(
    <html>
      <head>
         {antiCsrfMeta}
         {xitrumCss}
         {jsDefaults}
         <title>Welcome to Xitrum</title>
       </head>
       <body>
         {renderedView}
         {jsForView}
       </body>
    </html>
  )
}

示例2:

val specialLayout = () =>
   DocType.html5(
     <html>
       <head>
          {antiCsrfMeta}
          {xitrumCss}
          {jsDefaults}
          <title>Welcome to Xitrum</title>
        </head>
        <body>
          {renderedView}
          {jsForView}
        </body>
      </html>
   )
respondView(specialLayout _)

Inline view

import xitrum.Action
import xitrum.annotation.GET

  @GET("myAction")
  class MyAction extends Action {
    def execute() {
      val s = "World" // Will be automatically HTML-escaped
      respondInlineView(
         <p>Hello <em>{s}</em>!</p>
      )
    }
  }

 

Render fragment 

有两个Jade View文件:

scr/main/scalate/mypackage/MyAction.jade,scr/main/scalate/mypackage/_MyFragment.jade

如果想提交fragment文件到相同路径下的其他Jade文件里,可以使用

renderFragment[MyAction]("MyFragment")

如果在这种情况下,MyAction是当前action,则上面的代码可以写成 renderFragment("MyFragment")

返回其他action的View

使用respondView[ClassName]()和redirectTo[ClassName]()

示例:

package mypackage
import xitrum.Action
import xitrum.annotation.{GET, POST}
@GET("login")
class LoginFormAction extends Action {
  def execute() {
    // Respond scr/main/scalate/mypackage/LoginFormAction.jade
    respondView()
  }
}
@POST("login")
class DoLoginAction extends Action {
  def execute() {
    val authenticated = ...
    if (authenticated)
      redirectTo[HomeAction]()  // 定向到HomeAction的View
    else
       // Reuse the view of LoginFormAction
       respondView[LoginFormAction]()  //定向到LoginForAction的View
    }
}

redirectTo和respondView的区别:

redirectTo:不需要定向到的Action提供相应的模板;默认HttpResponseStatus是302

respondView:需要定向到的Action提供相应的模板;HttpResponseStatus是200

 

也可以从一个Action,定向到多个View

package mypackage
import xitrum.Action
import xitrum.annotation.GET
// These are non-routed actions, for mapping to view template files:
// scr/main/scalate/mypackage/HomeAction_NormalUser.jade
// scr/main/scalate/mypackage/HomeAction_Moderator.jade
// scr/main/scalate/mypackage/HomeAction_Admin.jade
trait HomeAction_NormalUser extends Action
trait HomeAction_Moderator extends Action
trait HomeAction_Admin extends Action
@GET("")
class HomeAction extends Action {
  def execute() {
    val userType = ...
    userType match {
      case NormalUser => respondView[HomeAction_NormalUser]() 
      case Moderator => respondView[HomeAction_Moderator]()
      case Admin => respondView[HomeAction_Admin]()
    }
  }
}

使用respondView[HomeAction_NormalUser]()这样的写法是 类型安全的 写法,也可以使用String参数把scalate的View的位置传给respondView方法

respondView("mypackage/HomeAction_NormalUser")
respondView("mypackage/HomeAction_Moderator")
respondView("mypackage/HomeAction_Admin")

Component

Component是在各个View中复用的组件,和Action有些类似

  • Component没有routes(@GET("")括号中的内容就是route),因此不需要使用execute方法
  • 因为Component只是提交一个View的片段(fragment),而不是发出整个的响应内容,所以在Component要调用renderXXX来代替respondXXX
  • 一个Component可以没有、有一个 或 有多个关联的View模板

示例:

package mypackage
import xitrum.{FutureAction, Component}
import xitrum.annotation.GET
class CompoWithView extends Component {
  def render() = {
    // Render associated view template, e.g. CompoWithView.jade
    // Note that this is renderView, not respondView!
    renderView()
  }
}

class CompoWithoutView extends Component {
   def render() = {
     "Hello World"
   }
}

@GET("foo/bar")
class MyAction extends FutureAction {
   def execute() {
     respondView()
  }
}

MyAction.jade:

- import mypackage._
!= newComponent[CompoWithView]().render()
!= newComponent[CompoWithoutView]().render()

 

posted @ 2017-05-11 18:05  子秦  阅读(438)  评论(0编辑  收藏  举报