Action and view
===============
To be flexible, Xitrum provides 3 kinds of actions:
normal ``Action``, ``FutureAction``, and ``ActorAction``.
Normal action
-------------
::
import xitrum.Action
import xitrum.annotation.GET
@GET("hello")
class HelloAction extends Action {
def execute() {
respondText("Hello")
}
}
Because the action will run on directly Netty's IO thread, it should not do blocking
processing that may take a long time, otherwise Netty can't accept new connections
or send response back to clients.
FutureAction
------------
::
import xitrum.FutureAction
import xitrum.annotation.GET
@GET("hello")
class HelloAction extends FutureAction {
def execute() {
respondText("hi")
}
}
The action will run on the same thread pool for ``ActorAction`` (see below),
separated from the thread pool of Netty.
Actor action
------------
If you want your action to be an Akka actor, extend ``ActorAction``:
::
import scala.concurrent.duration._
import xitrum.ActorAction
import xitrum.annotation.GET
@GET("actor")
class HelloAction extends ActorAction {
def execute() {
// See Akka doc about scheduler
import context.dispatcher
context.system.scheduler.scheduleOnce(3 seconds, self, System.currentTimeMillis())
// See Akka doc about "become"
context.become {
case pastTime =>
respondInlineView(s"It's $pastTime Unix ms 3s ago.")
}
}
}
An actor instance will be created when there's request. It will be stopped when the
connection is closed or when the response has been sent by ``respondText``,
``respondView`` etc. methods. For chunked response, it is not stopped right away.
It is stopped when the last chunk is sent.
The actor will run on the thread pool of the Akka actor system named "xitrum".
Respond to client
-----------------
From an action, to respond something to client, use:
* ``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("...")``: 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
Hello {s}!
) } } Render fragment --------------- Suppose MyAction.jade is at: scr/main/scalate/mypackage/MyAction.jade If you want to render the fragment file in the same directory: scr/main/scalate/mypackage/_MyFragment.jade :: renderFragment[MyAction]("MyFragment") If ``MyAction`` is the current action, you can skip it: :: renderFragment("MyFragment") Respond view of other action ---------------------------- Use the syntax ``respondView[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]() else // Reuse the view of LoginFormAction respondView[LoginFormAction]() } } One action - multiple views ~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you want to have multiple views for one: :: 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]() } } } Using addional non-routed actions like above seems to be tedious, but this way your program will be typesafe. You can also use ``String`` to specify template location: :: respondView("mypackage/HomeAction_NormalUser") respondView("mypackage/HomeAction_Moderator") respondView("mypackage/HomeAction_Admin") Component --------- You can create reusable view components that can be embedded to multiple views. In concept, a component is similar to an action: * But it does not have routes, thus ``execute`` method is not needed. * It does not "responds" a full response, it just "renders" a view fragment. So inside a component, instead of calling ``respondXXX``, please call ``renderXXX``. * Just like an action, a component can have none, one, or multiple associated view templates. :: 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()