Lift以其伟大的Ajax和Comet支持而闻名,在本章中,我们将探讨这些。
有关Lift的Ajax和Comet功能的简介,请参阅“提升行动”(Perrett,2012,Manning Publications,Co.)第9章“ Simply Lift ”,或观看Diego Medina的视频演示。
本章的源代码在https://github.com/LiftCookbook/cookbook_ajax。
从按钮触发服务器端代码
解
使用ajaxInvoke
:
package
code.snippet
import
net.liftweb.util.Helpers._
import
net.liftweb.http.SHtml
import
net.liftweb.http.js.
{
JsCmd
,
JsCmds
}
import
net.liftweb.common.Loggable
object
AjaxInvoke
extends
Loggable
{
def
callback
()
:
JsCmd
=
{
logger
.
info
(
"The button was pressed"
)
JsCmds
.
Alert
(
"You clicked it"
)
}
def
button
=
"button [onclick]"
#>
SHtml
.
ajaxInvoke
(
callback
)
}
在这个片段中,我们将按钮的点击事件绑定到ajaxInvoke
:当按下按钮时,Lift会排列您ajaxInvoke
执行的功能。
该功能callback
只是记录消息并向浏览器返回JavaScript警报。相应的HTML可能包括:
<div
data-lift=
"AjaxInvoke.button"
>
<button>
点击我</button>
</div>
讨论
您传递给的函数的签名ajaxInvoke
是 Unit => JsCmd
,意味着您可以触发一系列行为:Noop
如果您不想发生任何事情,通过更改DOM元素返回,一直到执行任意JavaScript。
上一个示例使用一个按钮,但可以处理任何具有可绑定事件的元素。我们有约束力,onclick
但它可能是DOM暴露的任何事件。
我们依次看看这些。
onEvent:接收DOM元素的值
您可以使用onEvent
具有属性的任何元素,value
并响应您选择绑定的事件。您提供的功能onEvent
使用元素的值进行调用。例如,我们可以编写一个向用户提出挑战并验证响应的代码段:
package
code.snippet
import
scala.util.Random
import
net.liftweb.util.Helpers._
import
net.liftweb.http.SHtml
import
net.liftweb.http.js.JsCmds.Alert
object
OnEvent
{
def
render
=
{
val
x
,
y
=
Random
.
nextInt
(
10
)
val
sum
=
x
+
y
"p *"
#>
"What is %d + %d?"
.
format
(
x
,
y
)
&
"input [onchange]"
#>
SHtml
.
onEvent
(
answer
=>
if
(
answer
==
sum
.
toString
)
Alert
(
"Correct!"
)
else
Alert
(
"Try again"
)
)
}
}
该片段提示用户添加<p>
标签中显示的两个随机数,并将验证功能绑定到<input>
页面上:
<div
data-lift=
"OnEvent"
>
<p>
问题出现在这里</p>
<input
placeholder=
"Type your answer"
></input>
</div>
何时onchange
被触发(例如按Return或Tab键),输入的文本将onEvent
作为a 发送到我们的函数String
。在服务器端,我们检查答案并发回“正确!” 或者“再试一次”作为JavaScript提醒。
ajaxCall:接收任意的客户端字符串
哪里onEvent
发送this.value
到您的服务器端代码,ajaxCall
允许您指定用于生成值的客户端表达式。
为了演示这一点,我们可以创建一个包含两个元素的模板:一个按钮和一个文本字段。我们将把我们的函数绑定到按钮,但从输入字段读取一个值:
<div
data-lift=
"AjaxCall"
>
<input
id=
"num"
value=
"41"
></input>
<button>
增量</button>
</div>
我们要安排按钮读取该num
字段,增加它并将其返回到输入字段:
package
code.snippet
import
net.liftweb.util.Helpers._
import
net.liftweb.http.SHtml
import
net.liftweb.http.js.JE.ValById
import
net.liftweb.http.js.JsCmds._
object
AjaxCall
{
def
increment
(
in
:
String
)
:
String
=
asInt
(
in
).
map
(
_
+
1
).
map
(
_
.
toString
)
openOr
in
def
render
=
"button [onclick]"
#>
SHtml
.
ajaxCall
(
ValById
(
"num"
),
s
=>
SetValById
(
"num"
,
increment
(
s
))
)
}
第一个参数ajaxCall
是将为我们的函数产生一个值的表达式。它可以是任何JsExp
,我们已经使用ValById
,它通过ID属性查找元素的值。我们可以使用一个常规的jQuery表达式来实现相同的效果JsRaw("$('#num').val()")
。
我们的第二个参数ajaxCall
将JsExp
表达式的值作为一个String
。我们正在使用Lift的JavaScript命令之一替换新值。新值是增加数字的结果(提供它是一个数字)。
最终的结果是你按下按钮和号码更新。应该不用说这些是简单的插图,你可能不想要一个服务器往返来添加一个数字。当在服务器上执行一些有价值的行动时,这些技术就会成为自己的技术。
你可能已经猜到这onEvent
是为了ajaxCall
实现的JsRaw("this.value")
。
jsonCall:接收JSON值
两者ajaxCall
并onEvent
最终评估一个String => JsCmd
功能。相比之下,jsonCall
具有签名JValue => JsCmd
,意味着您可以将更复杂的数据结构从JavaScript传递到Lift应用程序。
为了演示这一点,我们将创建一个要求输入的模板,具有将输入转换为JSON的功能,以及将JSON发送到服务器的按钮:
<div
data-lift=
"JsonCall"
>
<p>
输入一个附加问题:</p>
<div>
<input
id=
"x"
>
+<input
id=
"y"
>
=<input
id=
"z"
>
。</div>
<button>
检查</button>
</div>
<script
type=
"text/javascript"
>
//<![CDATA[
function currentQuestion() {
return {
first: parseInt($('#x').val()),
second: parseInt($('#y').val()),
answer: parseInt($('#z').val())
};
}
// ]]>
该currentQuestion
函数正在创建一个对象,当发送到服务器时将被变成一个JSON字符串。在服务器上,我们会检查这个JSON是否是一个有效的整数加法问题:
package
code.snippet
import
net.liftweb.util.Helpers._
import
net.liftweb.http.SHtml
import
net.liftweb.http.js.
{
JsCmd
,
JE
}
import
net.liftweb.common.Loggable
import
net.liftweb.json.JsonAST._
import
net.liftweb.http.js.JsCmds.Alert
import
net.liftweb.json.DefaultFormats
object
JsonCall
extends
Loggable
{
implicit
val
formats
=
DefaultFormats
case
class
Question
(
first
:
Int
,
second
:
Int
,
answer
:
Int
)
{
def
valid_?
=
first
+
second
==
answer
}
def
render
=
{
def
validate
(
value
:
JValue
)
:
JsCmd
=
{
logger
.
info
(
value
)
value
.
extractOpt
[
Question
].
map
(
_
.
valid_?
)
match
{
case
Some
(
true
)
=>
Alert
(
"Looks good"
)
case
Some
(
false
)
=>
Alert
(
"That doesn't add up"
)
case
None
=>
Alert
(
"That doesn't make sense"
)
}
}
"button [onclick]"
#>
SHtml
.
jsonCall
(
JE
.
Call
(
"currentQuestion"
),
validate
_
)
}
}
从这个代码片段的底部开始,我们看到了一个绑定<button>
到jsonCall
。我们将要处理的值是由JavaScript函数提供的值currentQuestion
。这是在模板页面上定义的。当单击按钮时,将调用JavaScript函数,并将结果值提供给validate
,这是我们的JValue => JsCmd
功能。
所有validate
这些都是记录JSON数据并提醒,如果问题看起来是否正确。为此,我们使用Lift JSON功能将JSON提取到案例类,并调用valid_?
测试来查看数字是否相加。这将评估添加Some(true)
是否有效,Some(false)
如果添加不正确,或None
输入丢失或不是有效整数。
运行代码并在文本字段中输入1,2和3将在服务器日志中产生以下内容:
JObject
(
List
(
JField
(
first
,
JInt
(
1
)),
JField
(
second
,
JInt
(
2
)),
JField
(
answer
,
JInt
(
3
))))
这是JValue
JSON的表示。
也可以看看
“选择选项更改时的呼叫服务器”包含一个示例SHtml.onEvents
,它将功能绑定到某个事件上的多个事件NodeSeq
。
探索Lift,第10章列出了JsExp
可以使用的各种类ajaxCall
。
“Ajax的JSON表单处理”使用JsonHandler
于来自形式向服务器发送JSON数据。
选择选项更改时的呼叫服务器
解
注册一个String => JsCmd
功能SHtml.ajaxSelect
。
在这个例子中,我们将查看用户选择的地球到地球的距离。此查找将在服务器上进行,并使用结果更新浏览器。该接口是:
<div
data-lift=
"HtmlSelectSnippet"
>
<div>
<label
for=
"dropdown"
>
行星:</label>
<select
id=
"dropdown"
></select>
</div>
<div
id=
"distance"
>
距离将出现在这里</div>
</div>
片段代码绑定<select>
元素以将选定的值发送到服务器:
package
code.snippet
import
net.liftweb.common.Empty
import
net.liftweb.util.Helpers._
import
net.liftweb.http.SHtml.ajaxSelect
import
net.liftweb.http.js.JsCmd
import
net.liftweb.http.js.JsCmds.SetHtml
import
xml.Text
class
HtmlSelectSnippet
{
// Our "database" maps planet names to distances:
type
Planet
=
String
type
LightYears
=
Double
val
database
=
Map
[
Planet
,LightYears
](
"Alpha Centauri Bb"
->
4.23
,
"Tau Ceti e"
->
11.90
,
"Tau Ceti f"
->
11.90
,
"Gliese 876 d"
->
15.00
,
"82 G Eridani b"
->
19.71
)
def
render
=
{
// To show the user a blank label and blank value option:
val
blankOption
=
(
""
->
""
)
// The complete list of options includes everything in our database:
val
options
:
List
[(
String
,String
)]
=
blankOption
::
database
.
keys
.
map
(
p
=>
(
p
,
p
)).
toList
// Nothing is selected by default:
val
default
=
Empty
// The function to call when an option is picked:
def
handler
(
selected
:
String
)
:
JsCmd
=
{
SetHtml
(
"distance"
,
Text
(
database
(
selected
)
+
" light years"
))
}
// Bind the <select> tag:
"select"
#>
ajaxSelect
(
options
,
default
,
handler
)
}
}
代码的最后一行是为我们做的工作。它正在生成选项并将选择绑定到调用的函数handler
。handler
使用所选项目的值调用该函数。
我们使用相同的String
(行星名称)作为选项标签和值,但它们可能不同。
讨论
要了解这里发生了什么,请查看Lift产生的HTML:
<select
id=
"dropdown"
onchange=
"liftAjax.lift_ajaxHandler('F470183993611Y15ZJU=' +
this.options[this.selectedIndex].value, null, null, null)"
>
<option
value=
""
></option>
<option
value=
"Tau Ceti e"
>
陶西蒂</option>
...</select>
该handler
功能已由Lift在F470183993611Y15ZJU
(在此特定渲染中)的标识符下存储。一个onchange
事件处理程序被附接到<select>
元件并负责所选择的值输送到服务器,并把一个值返回。该lift_ajaxHandler
JavaScript函数中定义liftAjax.js,其会自动添加到您的网页。
收集表单提交的价值
如果您需要在定期提交表单上另外捕获所选值,则可以使用SHtml.onEvents
。这将事件侦听器附加到一个NodeSeq
事件发生时触发服务器端功能。我们可以使用常规表单与常规选择框,但是当选择更改时,在Ajax中连接到服务器。
为了利用这一点,我们的代码段变化很小:
var
selectedValue
:
String
=
""
"select"
#>
onEvents
(
"onchange"
)(
handler
)
{
select
(
options
,
default
,
selectedValue
=
_
)
}
&
"type=submit"
#>
onSubmitUnit
(
()
=>
S
.
notice
(
"Destination "
+
selectedValue
))
handler
当onchange
事件触发时,我们正在安排调用相同的函数。此事件绑定适用于常规SHtml.select
,它正在存储selectedValue
表单何时提交。我们还将一个提交按钮绑定到一个生成通知的功能,该通知被选中。
相应的HTML也变化不大。我们需要添加一个按钮,并确保代码段被标记为一个表单?form
:
<div
data-lift=
"HtmlSelectFormSnippet?form=post"
>
<div>
<label
for=
"dropdown"
>
行星:</label>
<select
id=
"dropdown"
></select>
</div>
<div
id=
"distance"
>
距离将出现在这里</div>
<input
type=
"submit"
value=
"Book Ticket"
/>
</div>
现在当您更改所选值时,会看到动态更新的距离计算,但按“预订票”按钮也可将值提供给服务器。
也可以看看
“使用具有多个选项的选择框”介绍如何使用类而不是String
选择框的值。
在Scala代码中创建客户端操作
解
将您的JavaScript直接绑定到要运行的事件处理程序。
这里有一个例子,当我们按下按钮缓慢消失时,请注意,我们正在使用我们的服务器端升级代码设置此绑定:
package
code.snippet
import
net.liftweb.util.Helpers._
object
ClientSide
{
def
render
=
"button [onclick]"
#>
"$(this).fadeOut()"
}
在模板中,我们可能会说:
<div
data-lift=
"ClientSide"
>
<button>
点击我</button>
</div>
Lift将渲染页面为:
<button
onclick=
"$(this).fadeOut()"
>
点击我</button>
讨论
Lift包含一个JavaScript抽象,可用于为客户端构建更精细的表达式。例如,您可以组合基本命令:
import
net.liftweb.http.js.JsCmds.
{
Alert
,
RedirectTo
}
def
render
=
"button [onclick]"
#>
(
Alert
(
"Here we go..."
)
&
RedirectTo
(
"http://liftweb.net"
))
这将弹出警报对话框然后发送到http://liftweb.net。HTML将呈现为:
<button
onclick=
"alert("Here we go...");
window.location = "http://liftweb.net";"
>
点击我</button>
另一个选择是使用参数JE.Call
执行JavaScript函数。假设我们定义了这个函数:
function
greet
(
who
,
times
)
{
for
(
i
=
0
;
i
<
times
;
i
++
)
alert
(
"Hello "
+
who
);
}
我们可以将客户端按钮按下这个客户端功能,如下所示:
import
net.liftweb.http.js.JE
def
render
=
"button [onclick]"
#>
JE
.
Call
(
"greet"
,
"World!"
,
3
)
在客户端,我们会看到:
<button
onclick=
"greet("World!",3)"
>
点击我的问候语</button>
请注意,类型String
和Int
呼叫的JavaScript语法一直保存。这是因为JavaScript函数名后面JE.Call
带有可变数量的JsExp
参数。有JavaScript的原始类型包装(JE.Str
,JE.Num
,JsTrue
,JsFalse
)和隐式转换,以节省您不必包裹斯卡拉重视自己。
专注于页面加载的领域
解
用FocusOnLoad
命令包装输入:
package
code.snippet
import
net.liftweb.util.Helpers._
import
net.liftweb.http.js.JsCmds.FocusOnLoad
class
Focus
{
def
render
=
"name=username"
#>
FocusOnLoad
(<
input
type
=
"text"
/>)
}
CSS变换render
将匹配name="username"
HTML中的元素,并将其替换为文本输入字段,该字段将在页面加载时自动关注。
虽然我们专注于内联HTML,但这可能是任何的NodeSeq
,例如由SHtml.text
。
讨论
FocusOnLoad
是一个NodeSeq => NodeSeq
转型的例子。它附加到NodeSeq
需要设置焦点在该领域的JavaScript。
执行焦点的JavaScript只需通过ID查找DOM中的节点,并focus
对其进行调用。虽然以前的示例代码没有指定ID,但是该命令FocusOn
足够聪明,可以为我们自动添加一个。
有两个相关的JsCmd
选择:
- 获取元素ID并设置对元素的重点
-
类似于
Focus
,但需要一个额外的String
值来填充元素
Focus
SetValueAndFocus
如果您需要动态地从Ajax或Comet组件设置焦点,这两个是有用的。
也可以看看
源码FocusOnLoad
值得一试,了解它和相关命令的构造方式。这可能有助于将您自己的JavaScript功能打包到可用于CSS绑定表达式的命令中。
将一个CSS类添加到Ajax窗体
解
通过?class=
查询参数命名类:
<form
data-lift=
"form.ajax?class=boxed"
>
...</form>
讨论
如果需要设置多个CSS类,请在类名之间编写空格(例如,class=boxed+primary
)。
该form.ajax
构造是一个常规的片段调用:Form
片段是少数内置的片段之一,在这种情况下,我们正在调用该ajax
对象的方法。但是,通常,代码段调用不会将属性复制到生成的标记中,但是这个代码段被实现来做到这一点。
通过JavaScript运行模板
解
使用Template
加载模板,SetHtml
页面上的内容放置。
<div>
当按下按钮时,让我们填写网站主页:
<div
data-lift=
"TemplateLoad"
>
<div
id=
"inject"
>
内容将显示在此处</div>
<button>
加载模板</button>
</div>
相应的代码段将是:
package
code.snippet
import
net.liftweb.util.Helpers._
import
net.liftweb.http.
{
SHtml
,
Templates
}
import
net.liftweb.http.js.JsCmds.
{
SetHtml
,
Noop
}
import
net.liftweb.http.js.JsCmd
object
TemplateLoad
{
def
content
:
JsCmd
=
Templates
(
"index"
::
Nil
).
map
(
ns
=>
SetHtml
(
"inject"
,
ns
))
openOr
Noop
def
render
=
"button [onclick]"
#>
SHtml
.
ajaxInvoke
(
content
_
)
}
单击按钮将导致将/index.html的内容加载到inject
元素中。
讨论
Templates
产生Box[NodeSeq]
。在前面的例子中,我们将这个内容映射到JsCmd
将填充的内容inject
<div>
。
如果编写单元测试来访问模板,请注意,您可能需要修改开发或测试环境才能包含webapps文件夹。要为SBT执行此操作,请将以下内容添加到build.sbt中:
unmanagedResourceDirectories
in
Test
<+=
(
baseDirectory
)
{
_
/
"src/main/webapp"
}
为了在IDE中工作,您需要添加webapp作为源文件夹来定位模板。
也可以看看
“从按钮触发服务器端代码”描述ajaxInvoke
和相关方法。
将JavaScript移到页尾
解
使用S.appendJs
,将JavaScript放在结束</body>
标签之前,以及Lift生成的其他JavaScript。
在这个HTML中,我们<script>
在页面的中间放置了一个标签,并标有一个代码片段JavaScriptTail
:
<!DOCTYPE html>
<head>
<meta
content=
"text/html; charset=UTF-8"
http-equiv=
"content-type"
/>
<title>
尾部</title>
</head>
<body
data-lift-content-id=
"main"
>
<div
id=
"main"
data-lift=
"surround?with=default;at=content"
>
<h2>
JavaScript中的JavaScript尾部页面</h2>
<script
type=
"text/javascript"
data-lift=
"JavaScriptTail"
>
</script>
<p>
要运行的JavaScript将被移动
到这个页面的结尾,就在结束之前
身体标签。
</p>
</div>
</body>
</html>
该<script>
内容将通过一个片段产生。它不需要是<script>
标签; 代码片段只是替换内容,但是将该代码段挂在代码上<script>
可以提示代码段的用途:
package
code.snippet
import
net.liftweb.util.Helpers._
import
net.liftweb.http.js.JsCmds.Alert
import
net.liftweb.http.S
import
xml.NodeSeq
class
JavaScriptTail
{
def
render
=
{
S
.
appendJs
(
Alert
(
"Hi"
))
"*"
#>
NodeSeq
.
Empty
}
}
虽然代码片段没有呈现,但它调用S.appendJs
一个JsCmd
。这将在身体结束之前的页面中产生以下内容:
<script
type=
"text/javascript"
>
// <![CDATA[
jQuery
(
document
).
ready
(
function
()
{
alert
(
"Hi"
);
});
// ]]>
</script>
请注意,代码段位于模板的中间,但JavaScript会显示在渲染页面的末尾。
讨论
还有三种其他方法可以解决这个问题。第一个是将JavaScript移动到外部文件,并将其包含在您想要的页面上。对于大量的JavaScript代码,这可能是有道理的。
第二个是一个变体S.appendJs
:S.appendGlobalJs
以相同的方式工作,但不包括ready
JavaScript的JavaScript。这意味着您无法保证在调用函数时DOM加载。
第三个选项是将JavaScript包装在一个<lift:tail>
代码段中:
class
JavascriptTail
{
def
render
=
"*"
#>
<
lift
:
tail>
{
Script
(
OnLoad
(
Alert
(
"
Hi
"
)))}
</lift:tail>
}
请注意,这lift:tail
是一个通用的Lift片段,可用于将各种内容移动到页面的末尾,而不仅仅是JavaScript。
也可以看看
“添加到页面的头部”讨论了一个相关的提升片段,用于将内容移动到页面的头部。
“使用HTML5时找不到代码段”介绍了调用代码段的不同方法,例如<lift:tail>
对data-lift="tail"
。
在彗星会话丢失上运行JavaScript
解
配置您的JavaScript通过LiftRules.noCometSessionCmd
。
例如,我们可以修改标准Lift聊天演示,以保存在会话丢失的情况下键入的消息。在演示的风格中,我们将有一个用于输入消息的Ajax表单和Comet聊天区域,用于显示收到的消息:
<form
data-lift=
"form.ajax"
>
<input
type=
"text"
data-lift=
"ChatSnippet"
id=
"message"
placeholder=
"Type a message"
/>
</form>
<div
data-lift=
"comet?type=ChatClient"
>
<ul>
<li>
一个消息</li>
</ul>
</div>
为此,我们可以添加一个函数,stash
在Comet会话丢失的情况下,我们希望被调用:
<script
type=
"text/javascript"
>
// <![CDATA[
function
stash
()
{
saveCookie
(
"stashed"
,
$
(
'#message'
).
val
());
location
.
reload
();
}
jQuery
(
document
).
ready
(
function
()
{
var
stashedValue
=
readCookie
(
"stashed"
)
||
""
;
$
(
'#message'
).
val
(
stashedValue
);
deleteCookie
(
"stashed"
);
});
// Definition of saveCookie, readCookie, deleteCookie omitted.
</script>
我们的stash
函数将保存所调用的cookie中输入字段的当前值stashed
。我们在页面加载时安排检查该cookie并将值插入到我们的消息字段中。
最后一部分是修改Boot.scala来注册我们的stash
功能:
import
net.liftweb.http.js.JsCmds.Run
LiftRules
.
noCometSessionCmd
.
default
.
set
(
()
=>
Run
(
"stash()"
)
)
以这种方式,如果在撰写聊天消息时会话丢失,浏览器将会隐藏该消息,并且当页面重新加载该消息将被恢复时。
要测试示例,请在消息字段中键入消息,然后重新启动Lift应用程序。等待10秒钟,你会看到效果。
讨论
不改变noCometSessionCmd
,Lift的默认行为是安排浏览器加载由LiftRules.noCometSessionPage
设置控制的主页。这是通过lift_sessionLost
文件cometAjax.js中的JavaScript函数进行的。
通过提供自己的() => JsCmd
功能LiftRules.noCometSessionCmd
,Lift安排调用此功能并将其传递JsCmd
到浏览器,而不是lift_sessionLost
。如果您在浏览器和Lift之间观看HTTP流量,您将看到stash
响应彗星请求返回的函数调用。
这个食谱专注于处理Comet的会话丢失; 对于Ajax,有一个相应的LiftRules.noAjaxSessionCmd
设置。
也可以看看
您将在Simply Lift中找到The Ubiquitous Chat应用程序。
能够调试HTTP流量是了解您的Comet或Ajax应用程序的运行情况的有用方式。有很多插件和产品可以帮助您,比如Firefox的HttpFox插件。
Ajax文件上传
解
将Sebastian Tschan的jQuery文件上传小部件添加到项目中,并实现REST终点来接收文件。
第一步是下载小部件,并将js文件夹拖到应用程序中作为src / main / webapp / js。然后,我们可以在模板中使用JavaScript:
<!DOCTYPE HTML>
<html>
<head>
<meta
charset=
"utf-8"
>
<title>
jQuery文件上传示例</title>
</head>
<body>
<h1>
将文件拖动到此页面上</h1>
<input
id=
"fileupload"
type=
"file"
name=
"files[]"
data-url=
"/upload"
multiple
>
<div
id=
"progress"
style=
"width:20em; border: 1pt solid silver; display: none"
>
<div
id=
"progress-bar"
style=
"background: green; height: 1em; width:0%"
></div>
</div>
<script
src=
"//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"
>
</script>
<script
src=
"js/vendor/jquery.ui.widget.js"
></script>
<script
src=
"js/jquery.iframe-transport.js"
></script>
<script
src=
"js/jquery.fileupload.js"
></script>
<script>
$
(
function
()
{
$
(
'#fileupload'
).
fileupload
({
dataType
:
'json'
,
add
:
function
(
e
,
data
)
{
$
(
'#progress-bar'
).
css
(
'width'
,
'0%'
);
$
(
'#progress'
).
show
();
data
.
submit
();
},
progressall
:
function
(
e
,
data
)
{
var
progress
=
parseInt
(
data
.
loaded
/
data
.
total
*
100
,
10
)
+
'%'
;
$
(
'#progress-bar'
).
css
(
'width'
,
progress
);
},
done
:
function
(
e
,
data
)
{
$
.
each
(
data
.
files
,
function
(
index
,
file
)
{
$
(
'<p/>'
).
text
(
file
.
name
).
appendTo
(
document
.
body
);
});
$
(
'#progress'
).
fadeOut
();
}
});
});
</script>
</body>
</html>
此模板提供文件的输入字段,用作进度指示器的区域,并在页面加载到jQuery $( ... )
块中时配置窗口小部件。这只是JavaScript小部件的常规用法,没有什么特别的Lift特定。
最后一部分是实现一个Lift REST服务来接收文件。服务,网址/上传,设置在data-url
该input
领域,这是我们匹配的地址:
package
code.rest
import
net.liftweb.http.rest.RestHelper
import
net.liftweb.http.OkResponse
object
AjaxFileUpload
extends
RestHelper
{
serve
{
case
"upload"
::
Nil
Post
req
=>
for
(
file
<-
req
.
uploadedFiles
)
{
println
(
"Received: "
+
file
.
fileName
)
}
OkResponse
()
}
}
该实现只需记录接收到的文件的名称,并将200个状态代码的成功传送确认回到窗口小部件。
与所有REST服务一样,它需要在Boot.scala中注册:
LiftRules
.
dispatch
.
append
(
code
.
rest
.
AjaxFileUpload
)
默认情况下,窗口小部件使整个HTML页面成为文件的放置目标,这意味着您可以将文件拖动到页面上,并将立即将其上传到Lift应用程序。
讨论
在这个配方中,我们已经展示了小部件与Lift应用程序的基本集成。该小部件的演示站点显示其他功能,并提供有关如何集成它们的文档。
许多功能只需要JavaScript配置。例如,我们已经使用了插件的add
,progressall
以及done
处理程序来显示,更新,然后淡出的进度条。上传完成后,上传的文件的名称将附加到页面。
在REST服务中,上传的文件可以通过uploadedFiles
请求方法获得。当Lift收到多形式,它会自动提取文件的uploadedFiles
,每一个都是一个FileParamHolder
,让我们进入fileName
,length
,mimeType
,和fileStream
。
默认情况下,上传的文件都保存在内存中,但可以改变(见“讨论”中的“文件上传”)。
在这个配方中,我们返回一个200(OkResponse
)。如果我们想要向窗口小部件发出一个文件被拒绝的信号,我们可以返回另一个代码。例如,也许我们要拒绝除了PNG图像之外的所有文件。在服务器端,我们可以通过替换OkResponse
测试来做到这一点:
import
net.liftweb.http.
{
ResponseWithReason
,
BadResponse
,
OkResponse
}
if
(
req
.
uploadedFiles
.
exists
(
_
.
mimeType
!=
"image/png"
))
ResponseWithReason
(
BadResponse
(),
"Only PNGs"
)
else
OkResponse
()
我们将fail
在客户端使用一个处理程序镜像这个JavaScript:
fail
:
function
(
e
,
data
)
{
alert
(
data
.
errorThrown
);
}
如果我们上传JPEG,浏览器会显示一个提醒对话框,报告“只有PNG”。
格式化有线单元格
解
使用该WiringUI.toNode
方法创建可以根据需要呈现格式化的输出的布线节点。
例如,考虑一个HTML模板来显示正在购买的商品的数量和小计:
<div
data-lift=
"Wiring"
>
<table>
<tbody>
<tr><td>
数量</td><td
id=
"quantity"
>
?</td></tr>
<tr><td>
小计</td><td
id=
"subtotal"
>
?</td></tr>
</tbody>
</table>
<button
id=
"add"
>
添加另一个</button>
</div>
我们希望小计显示为美元。片段将是:
package
code.snippet
import
java.text.NumberFormat
import
java.util.Locale
import
scala.xml.
{
NodeSeq
,
Text
}
import
net.liftweb.util.Helpers._
import
net.liftweb.util.
{
Cell
,
ValueCell
}
import
net.liftweb.http.
{
S
,
WiringUI
}
import
net.liftweb.http.SHtml.ajaxInvoke
import
net.liftweb.http.js.JsCmd
class
Wiring
{
val
cost
=
ValueCell
(
1.99
)
val
quantity
=
ValueCell
(
1
)
val
subtotal
=
cost
.
lift
(
quantity
)(
_
*
_
)
val
formatter
=
NumberFormat
.
getCurrencyInstance
(
Locale
.
US
)
def
currency
(
cell
:
Cell
[
Double
])
:
NodeSeq
=>
NodeSeq
=
WiringUI
.
toNode
(
cell
)((
value
,
ns
)
=>
Text
(
formatter
format
value
))
def
increment
()
:
JsCmd
=
{
quantity
.
atomicUpdate
(
_
+
1
)
S
.
notice
(
"Added One"
)
}
def
render
=
"#add [onclick]"
#>
ajaxInvoke
(
increment
)
&
"#quantity *"
#>
WiringUI
.
asText
(
quantity
)
&
"#subtotal *"
#>
currency
(
subtotal
)
}
我们已经定义了currency
一种使用Java编号格式化功能将subtotal
不是作为Double
货币量格式化的方法。这将导致像“$ 19.90”这样的值,而不是“19.9”。
讨论
主WiringUI
类可以轻松地将单元格绑定为文本。该asText
方法通过将值转换为a String
并将其包装在Text
节点中。这是通过toNode
,但是,我们可以toNode
直接使用该方法来生成一个变换函数,它们都被连接到布线UI中,并使用我们的代码来翻译项目。
该机制是类型安全的。在这个例子中,cost
是一个Double
单元格,quantity
一个Int
单元格,subtotal
被推断为一个单元格Cell[Double]
。这就是为什么我们的格式化函数被传递value
为一个Double
。
请注意,传递给的函数toNode
必须返回NodeSeq
。这样可以提供很大的灵活性,因为您可以返回任何类型的标记NodeSeq
。我们的示例通过在Text
对象中包装文本值来符合此签名。
该WiringUI.toNode
要求(T, NodeSeq) => NodeSeq
。在前面的例子中,我们忽略了NodeSeq
,但是值将是我们绑定到的元素的内容。给出输入:
<td
id=
"subtotal"
>
?</td>
这意味着NodeSeq
传递给我们只是代表“?”的文本节点。使用更丰富的模板,我们可以使用CSS选择器。例如,我们可以修改模板:
<td>
小计</td><td
id=
"subtotal"
>
<i>
值是<b
class=
"amount"
>
?</b></i>
</td>
现在我们可以应用一个CSS选择器来改变amount
元素:
(
value
,
ns
)
=>
(
".amount *"
#>
Text
(
formatter
format
value
))
apply
ns
)
也可以看看
Simply Lift的第6章介绍了Lift的接线机制,并给出了详细的购物示例。