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 layoutrespondInlineView
: responds embedded template (not separate template file), with or without layoutrespondText("hello")
: responds a string without layoutrespondHtml("<html>...</html>")
: same as above, with content type set to “text/html”respondJson(List(1, 2, 3))
: converts Scala object to JSON object then respondsrespondJs("myFunction([1, 2, 3])")
respondJsonP(List(1, 2, 3), "myFunction")
: combination of the above tworespondJsonText("[1, 2, 3]")
respondJsonPText("[1, 2, 3]", "myFunction")
respondBinary
: responds an array of bytesrespondFile
: sends a file directly from disk, very fast because zero-copy (aka send-file) is usedrespondEventSource("data", "event")
Respond template view file¶
Each action may have an associated Scalate template view file. Instead of responding directly in the action with the above methods, you can use a separate view file.
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
!= jsDefaults
title Welcome to Xitrum
body
a(href={url}) Path to the current action
p= currentAction.asInstanceOf[MyAction].hello("World")
!= jsForView
xitrumCss
includes the default CSS for Xitrum. You may remove it if you don’t like.xitrum-frameworkjsDefaults
includes jQuery, jQuery Validate plugin etc. should be put at layout’s <head>.jsForView
contains JS fragments added byjsAddToView
, should be put at layout’s bottom.
In templates you can use all methods of the class xitrum.Action.
Also, you can use utility methods provided by Scalate like unescape
.
See the Scalate doc.
The default Scalate template type is Jade. You can also use Mustache, Scaml, or Ssp. To config the default template type, see xitrum.conf file in the config directory of your Xitrum application.
You can override the default template type by passing “jade”, “mustache”, “scaml”, or “ssp” to respondView.
val options = Map("type" ->"mustache")
respondView(options)
Type casting currentAction¶
If you want to have exactly instance of the current action, cast currentAction
to
the action you wish.
p= currentAction.asInstanceOf[MyAction].hello("World")
If you have multiple lines like above, you can cast only one time:
- val myAction = currentAction.asInstanceOf[MyAction]; import myAction._
p= hello("World")
p= hello("Scala")
p= hello("Xitrum")
Mustache¶
Must read:
You can’t do some things with Mustache like with Jade, because Mustache syntax is stricter.
To pass things from action to Mustache template, you must use at
:
Action:
at("name") = "Jack"
at("xitrumCss") = xitrumCss
Mustache template:
My name is {{name}}
{{xitrumCss}}
Note that you can’t use the below keys for at
map to pass things to Scalate
template, because they’re already used:
- “context”: for Sclate utility object, which contains methods like
unescape
- “helper”: for the current action object
CoffeeScript¶
You can embed CoffeeScript in Scalate template using :coffeescript filter:
body
:coffeescript
alert "Hello, Coffee!"
Output:
<body>
<script type='text/javascript'>
//<![CDATA[
(function() {
alert("Hello, Coffee!");
}).call(this);
//]]>
</script>
</body>
But note that it is slow:
jade+javascript+1thread: 1-2ms for page
jade+coffesscript+1thread: 40-70ms for page
jade+javascript+100threads: ~40ms for page
jade+coffesscript+100threads: 400-700ms for page
You pre-generate CoffeeScript to JavaScript if you need speed.
Layout¶
When you respond a view with respondView
or respondInlineView
, Xitrum
renders it to a String, and sets the String to renderedView
variable. Xitrum
then calls layout
method of the current action, finally Xitrum responds
the result of this method to the browser.
By default layout
method just returns renderedView
itself. If you want
to decorate your view with something, override this method. If you include
renderedView
in the method, the view will be included as part of your layout.
The point is layout
is called after your action’s view, and whatever returned
is what responded to the browser. This mechanism is simple and straight forward.
No magic. For convenience, you may think that there’s no layout in Xitrum at all.
There’s just the layout
method and you do whatever you want with it.
Typically, you create a parent class which has a common layout for many views:
src/main/scala/mypackage/AppAction.scala
package mypackage
import xitrum.Action
trait AppAction extends Action {
override def layout = renderViewNoLayout[AppAction]()
}
src/main/scalate/mypackage/AppAction.jade
!!! 5
html
head
!= antiCsrfMeta
!= xitrumCss
!= jsDefaults
title Welcome to Xitrum
body
!= renderedView
!= 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")
Layout without separate file¶
AppAction.scala
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>
)
}
Pass layout directly to respondView¶
val specialLayout = () =>
DocType.html5(
<html>
<head>
{antiCsrfMeta}
{xitrumCss}
{jsDefaults}
<title>Welcome to Xitrum</title>
</head>
<body>
{renderedView}
{jsForView}
</body>
</html>
)
respondView(specialLayout _)
Inline view¶
Normally, you write view in a Scalate file. You can also write it directly:
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¶
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 callrenderXXX
. - 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()