生成HTML通常是Web应用程序的主要组件。本章涉及Lift的View First方法和CSS选择器的使用。后面的章节更加专注于表单处理,REST Web服务,JavaScript,Ajax和Comet。
本章的代码是https://github.com/LiftCookbook/cookbook_html。
测试和调试CSS选择器
问题
你想以交互方式探索或调试CSS选择器。
解
您可以使用Scala REPL运行您的CSS选择器。
这里有一个例子,我们测试一个将href链接添加属性的CSS选择器。从SBT开始,使用console命令进入REPL:
> console
[info] Starting scala interpreter...
[info]
Welcome to Scala version 2.9.1.final
Type in expressions to have them evaluated.
Type :help for more information.
scala>
import
net.liftweb.util.Helpers._
import
net.liftweb.util.Helpers._
scala>
val
f
=
"a [href]"
#>
"http://example.org"
f
:
net.liftweb.util.CssSel
=
(
Full
(a
[href]),
Full
(
ElemSelector
(a,
Full
(
AttrSubNode
(href)))))
scala>
val
in
=
<a>click
me</a>
in
:
scala.xml.Elem
=
<a>click
me</a>
scala>
f(in)
res0
:
scala.xml.NodeSeq
=
NodeSeq
(<a
href="http://example.org">click
me</a>)
该Helpers._进口带来的CSS选择器的功能,这是我们再创建一个选择,锻炼f,一个非常简单的模板调用它,in和观察的结果,res0。
讨论
CSS选择器转换是Lift的显着特征之一。他们简洁地描述了您的模板中的一个节点(左侧),并给出了一个替换(操作,右侧)。他们确实需要一点时间才能习惯,所以能够在Scala REPL测试他们是有用的。
它可能有助于知道,在CSS选择器之前,Lift代码片段通常通过一个函数来定义,该函数通常通过一个调用的方法取代NodeSeq并返回。电梯会拿你的模板,这将是输入,应用功能,并返回一个新的。你不会经常看到这种用法,但原则是一样的。NodeSeqbindNodeSeqNodeSeq
Lift中的CSS选择器CssSel功能为您提供了一个功能NodeSeq => NodeSeq。我们通过构造一个输入NodeSeq(被调用in),然后创建一个CSS函数(被调用f)在前面的例子中利用了这一点 。因为我们知道这CssSel 被定义为一个NodeSeq =>NodeSeq,执行选择器的自然方式是提供in一个参数,这给了我们答案res0。
如果您使用支持一个IDE 工作表,其中Eclipse和IntelliJ IDEA的做到,那么你也可以运行在一个工作表中的转换。
也可以看看
选择器的语法最好在Simply Lift中描述。
有关如何使用Eclipse和IntelliJ IDEA,请参阅“使用Eclipse开发”和“使用IntelliJIDEA开发”。
排序CSS选择器操作
问题
您希望您的CSS选择器绑定适用于早期绑定表达式的结果。
解
使用andThen而不是&组合您的选择器表达式。
例如,假设我们要替换<divid="foo"/>, <div id="bar">barcontent</div>但由于某些原因,我们需要bar在选择器表达式中将div作为单独的步骤生成:
sbt> console
[info] Starting scala interpreter...
[info]
Welcome to Scala version 2.9.1.final (Java 1.7.0_05).
Type in expressions to have them evaluated.
Type :help for more information.
scala> importnet.liftweb.util.Helpers._
importnet.liftweb.util.Helpers._
scala> def render = "#foo" #> <div id="bar"/>andThen "#bar *" #> "bar content"
render: scala.xml.NodeSeq => scala.xml.NodeSeq
scala>render(<div id="foo"/>)
res0: scala.xml.NodeSeq = NodeSeq(<div id="bar">barcontent</div>)
讨论
使用&时,请考虑CSS选择器一如以往地应用于原始模板,无论您要组合什么其他表达式。这是因为&在应用它们之前将选择器聚合在一起。相比之下,andThen是所有Scala函数的一种方法,它组合两个函数,第一个在第二个之前被调用。
比较前面的例子,如果我们更改andThen为 &:
scala> def render = "#foo" #> <div id="bar" />& "#bar *" #> "bar content"
render: net.liftweb.util.CssSel
scala>render(<div id="foo"/>)
res1 : scala.xml.NodeSeq = NodeSeq (<div id="bar"></div>)第二个表达式将不匹配,因为它应用于<div id="foo"/>选择器的原始输入#bar将不匹配id=foo,因此对结果没有增加render。
也可以看看
CSS选择器的LiftWiki页面也描述了这种使用andThen。
设置元标签内容
问题
您要从代码段设置HTML元标记的内容。
解
使用@CSS绑定名称选择器。例如,给定:
<meta name="keywords"content="words, here, please"/>
以下代码段将更新content属性的值:
"@keywords[content]" #> "words, we, really, want"
讨论
所述@选择器选择具有给定名称的所有元素。在这种情况下更改<meta name="keyword">标签很有用,但您也可能会看到它在其他地方使用。例如,在一个HTML表单中,可以选择输入字段,诸如<inputname="address">用"@address"。
该[content]部分是可以跟随选择器的替换规则的示例。也就是说,它不是特定于@选择器,可以与其他选择器一起使用。在这个例子中,它代替名为“content”的属性的值。如果元标记没有“内容”属性,则会被添加。
还有另外两个替代规则用于操作属性:
· [content!] 删除具有匹配值的属性。
· [content+] 追加价值。
这些例子将是:
scala> importnet.liftweb.util.Helpers._
importnet.liftweb.util.Helpers._
scala> val in = <meta name="keywords" content="words,here, please" />
in: scala.xml.Elem = <metaname="keywords" content="words, here,please"></meta>
scala> val remove = "@keywords [content!]" #> "words,here, please"
remove: net.liftweb.util.CssSel = CssBind(Full(@keywords [content!]),
Full(NameSelector(keywords,Full(AttrRemoveSubNode(content)))))
scala>remove(in)
res0: scala.xml.NodeSeq = NodeSeq(<metaname="keywords"></meta>)
和:
scala> valadd = "@keywords [content+]" #> ", thank you"
add:net.liftweb.util.CssSel = CssBind(Full(@keywords [content+]),
Full(NameSelector(keywords,Full(AttrAppendSubNode(content)))))
scala>add(in)
res1:scala.xml.NodeSeq = NodeSeq(<meta content="words, here, please, thankyou"
name="keywords"></meta>)
附加到类属性
虽然与meta标签不直接相关,但您应该意识到,附加一个属性有一个方便的特殊情况。如果属性是class,一个空格与你的类值一起添加。作为一个示范,这里是附加了一个名为类的例子btn-primary为div:
scala> defrender = "div [class+]" #> "btn-primary"
render:net.liftweb.util.CssSel
scala>render(<div class="btn"/>)
res0:scala.xml.NodeSeq = NodeSeq(<div class="btnbtn-primary"></div>)
也可以看看
选择器的语法最好在Simply Lift中描述。
有关如何从REPL运行选择器,请参阅“测试和调试CSS选择器”。
设置页面标题
问题
您要从<title>Lift片段中设置页面。
解
选择title元素的内容并将其替换为您想要的文本:
"title*" #> "I am different"
假设您<title>的模板中有标签,则会导致:
<title>I am different</title>
讨论
此示例使用元素选择器,它在HTML模板中选择标签并替换内容。请注意,我们使用"title *"来选择内容的的title标签。如果我们已经离开了*,整个title标签将被替换为文本。
作为替代方案,也可以从内容中设置页面标题SiteMap,这意味着使用的标题将是您在站点地图中分配给页面的标题。要做到这一点,Menu.title直接在你的模板中使用:
<title data-lift="Menu.title"></title>
该Menu.title代码追加到标题中的任何现有的文本。这意味着以下内容将"Site Title - "在标题中使用短语,后跟页面标题:
<title data-lift="Menu.title">Site Title - </title>
如果您需要更多的控制,您当然可以<title>使用常规代码段进行绑定。此示例使用自定义代码段将网站标题放在页面标题之后:
<title data-lift="MyTitle"></title>
object MyTitle {
def render = <title><lift:Menu.title /> - Site Title</title>
}
请注意,我们的代码段正在返回另一个代码段<lift:Menu.title/>。这在Lift中是一件非常正常的事情,而从代码片段返回的片段调用将由Lift正常处理。
也可以看看
“使用HTML5时找不到代码段”介绍了引用代码段的不同方式,例如data-lift和<lift: ... />。
在Assembla网站上,有更多关于SiteMap和Menu片段。
HTML条件注释
问题
您想使用Internet Explorer HTML条件注释在你的模板
解
将标记放在代码段中,并将该片段包含在您的页面或模板中。
例如,假设我们要包含HTML5 Shiv(又名HTML5 Shim)JavaScript,以便我们可以将HTML5元素与旧版IE浏览器结合使用。为了做到这一点,我们的代码片段将是:
package code.snippet
import scala.xml.Unparsed
object Html5Shiv {
def render =Unparsed("""<!--[if lt IE 9]>
<scriptsrc="http://html5shim.googlecode.com/svn/trunk/html5.js">
</script><![endif]-->""")
}然后,我们将引用页面中的代码段<head>,甚至可以通过templates-hidden / default.html在所有页面中引用该片段:
<script data-lift="Html5Shiv"></script>
讨论
Lift使用的HTML5解析器不会将来自源的注释传递到呈现页面。如果您只是尝试将html5shim标记粘贴到您的模板中,那么您将在渲染页面中找到它。
我们通过从代码段生成未解析的标记来处理此问题。如果你看着, Unparsed担心,你的直觉是正确的。通常,Lift会导致标记被转义,但在这种情况下,我们确实希望在输出中包含解析XML内容(注释标签)。
如果您发现您经常使用IE条件注释,则可能需要创建一个更通用的代码段。例如:
package code.snippet
import xml.{NodeSeq, Unparsed}
import net.liftweb.http.S
object IEOnly {
private def condition : String =
S.attr("cond") openOr"IE"
def render(ns: NodeSeq) : NodeSeq =
Unparsed("<!--[if " + condition +"]>") ++ ns ++ Unparsed("<![endif]-->")
}
它会像这样使用:
<div data-lift="IEOnly">
A div just for IE
</div>
并产生如下输出:
<!--[ifIE]><div>
A div just for IE
</div><![endif]-->
请注意,condition测试默认为IE,但首先尝试查找一个称为的属性cond。这可以让你写:
<div data-lift="IEOnly?cond=lt+IE+9">
您正在使用IE 8或更早版本
</div>
的+符号为空间,导致URL编码:
<!--[iflt IE 9]><div>
You're using IE 8 or earlier
</div><![endif]-->
也可以看看
这个IEOnly例子是从AntonioSalazar Cardozo的邮件列表中发贴的。
该html5shim项目可从其下载谷歌代码的网站。
返回代码段标记不变
问题
您希望代码段返回与代码段调用相关联的原始标记。
解
使用PassThru变换。
假设你有一个片段,当满足某些条件时执行变换,但是如果不满足条件,则希望代码段返回原始标记。
从原始标记开始:
<h2>通过例子</h2>
<p>有50:50的机会看到“再试一次”或“恭喜!”:</p>
<div data-lift="PassThruSnippet">
再试一次 - 这是模板内容。
</div>
我们可以单独留下或使用此代码段进行更改:
package code.snippet
import net.liftweb.util.Helpers._
import net.liftweb.util.PassThru
import scala.util.Random
import xml.Text
class PassThruSnippet {
private def fiftyFifty =Random.nextBoolean
def render =
if (fiftyFifty) "*" #> Text("Congratulations! The content waschanged")
else PassThru
}
讨论
PassThru是类型的身份功能NodeSeq => NodeSeq。它返回给出的输入:
object PassThru extends Function1 [NodeSeq, NodeSeq] {
def apply(in: NodeSeq): NodeSeq = in
}
一个相关的例子是ClearNodes,定义为:
object ClearNodes extends Function1[NodeSeq, NodeSeq] {
def apply(in: NodeSeq): NodeSeq = NodeSeq.Empty
}
一个NodeSeq转换的模式是简单的,但也足够强大,让你摆脱大多数情况,因为你可以随时重写NodeSeq。
使用HTML5时找不到代码段
问题
您正在使用Lift与HTML5解析器,其中一个代码段将呈现“找不到类”错误。甚至发生了<lift:HelloWorld.howdy />。
解
切换到设计师友好的片段调用机制。例如:
<div data-lift="HellowWorld.howdy"></div>
讨论
在本菜谱中,我们使用了在Boot.scala中设置的HTML5解析器:
// UseHTML5 for rendering
LiftRules.htmlProperties.default.set( (r: Req) =>
new Html5Properties(r.userAgent) )
HTML5解析器和传统的Lift XHTML解析器具有不同的行为。特别地,HTML5解析器在查找代码段时将元素和属性名称转换为小写。这意味着Lift会采取<lift:HelloWorld.howdy/>和寻找一个被叫helloworld而不是HelloWorld“Class Not Found”错误的原因。
切换到设计师友好的机制是这里的解决方案,您将获得验证HTML作为奖金。
有四种引用代码段的流行方式:
作为HTML5数据属性: data-lift="MySnippet"
这是我们在本书中使用的风格,是有效的HTML5标记。
通过CSS类: class="lift:MySnippet"
同样有效的HTML5,但您必须包含Lift的“lift”前缀才能将其识别为代码段。
使用lift属性,如:lift="MySnippet"
这不会严格验证HTML5,但您可能会看到它使用。
XHTML命名空间版本: <lift:MySnippet />
由于与HTML5解析器的交互方式,这个标签在模板中的使用将会下降。但是,它在模板之外工作得很好,例如在将代码片段调用嵌入到服务器端代码中时(“设置页面标题”包含一个示例Menu.title)。
也可以看看
邮件列表中列出了XHTML和HTML5解析器之间的主要区别。
避免CSS和JavaScript缓存
问题
您已经在应用程序中修改了CSS或JavaScript,但是Web浏览器已经缓存了您的资源,并且使用的是旧版本。您希望避免浏览器缓存。
解
将with-resource-id属性添加到脚本或链接标签:
<script data-lift="with-resource-id" src="/myscript.js"
type="text/javascript"></script>
添加此属性将导致Lift将资源ID附加到您的src(或href)中,并且由于每次Lift启动时,此资源ID都会更改,因此会破坏浏览器缓存。
结果HTML可能是:
<script src="/myscript.js?F619732897824GUCAAN=_"
type="text/javascript" ></script>
讨论
当您的Lift应用程序启动时,会计算附加到资源的随机值。这意味着应用程序的版本之间应该是稳定的。
如果您需要一些其他的行为with-resource-id,您可以指定类型的新功能String=> String来LiftRules.attachResourceId。以前显示的默认实现在示例中使用资源名称/myscript.js,并返回附加了ID的资源名称。
您还可以在<lift:with-resource-id>...<lift:with-resource-id>块内包装多个标签 。但是,请避免在<head>您的页面中执行此操作,因为HTML5解析器将标签移动到头部之外。
使用代理
请注意,一些代理可能选择不使用查询参数缓存资源。如果这对您有影响,可以编写自定义资源ID方法来将随机资源ID从查询参数中移出并进入路径。
这是一个这样做的方法。而不是生成看起来像/assets/style.css?F61973的 JavaScript和CSS链接,我们将生成/cache/F61973/assets/style.css。然后,我们将需要告诉我们的容器或Web服务器采取看起来像这种新格式的请求,并删除/ cache / F61973/ part。
更改链接创建方式的代码:
package code.lib
import net.liftweb.util._
import net.liftweb.http._
object CustomResourceId {
def init() : Unit = {
// The random number we're using to avoid caching
val resourceId =Helpers.nextFuncName
// Prefix with-resource-id links with "/cache/{resouceId}"
LiftRules.attachResourceId = (path: String) => {
"/cache/" + resourceId + path
}
}
}
这将在Boot.scala中初始化:
CustomResourceId.init()
或者您可以将所有代码粘贴到Boot.scala中,如果您愿意的话。
使用代码,例如,我们可以修改templates-hidden / default.html并将资源ID类添加到jQuery中:
<script id="jquery" data-lift="with-resource-id"
src="/classpath/jquery.js" type="text/javascript"></script>
在运行时,这将以HTML形式呈现:
<script type="text/javascript" id="jquery"
src="/cache/F352555437877UHCNRW/classpath/jquery.js"></script>
最后,我们需要一种将URL重写为原始路径的方法。也就是删除/ cache / ...部分。有几种方法可以实现这一点。如果您在Lift应用程序前使用nginx或Apache,则可以将这些Web服务器配置为在到达Lift之前执行重写。
也可以看看
http://bit.ly/14BfNYJ显示了默认的实现attachResourceId。
Google的“优化缓存”说明是浏览器行为的一个很好的信息来源。
有一个支持URL重写,描述在Lift维基。很少使用重写,只适用于特殊情况。它不适合这种复兴,如发布到Stackoverflow中所概述的。许多问题看起来像重写问题更好的解决了一个菜单参数。
Grunt和类似的工具可以修改路径。Diego Medina的“ 使用Grunt和Bower与Lift”的帖子是一个很好的起点。
添加到页面的头
问题
您使用模板进行布局,但在一个特定页面上,您需要向该<head>部分添加内容。
解
使用head片段,因此Lift知道将内容与<head>页面合并。例如,假设您在templates-hidden/ default.html中有以下内容:
<html lang="en" xmlns:lift="http://liftweb.net/">
<head>
<meta charset="utf-8"></meta>
<title data-lift="Menu.title">App:</title>
<script id="jquery" src="/classpath/jquery.js"
type="text/javascript"></script>
<script id="json" src="/classpath/json.js"
type="text/javascript"></script>
</head>
<body>
<divid="content">主要内容将在这里绑定</div>
</body>
</html>
还假设你有index.html,你要包括red-titles.css来改变这个页面的样式。
通过将CSS包含在将被处理的页面部分中,并使用该head代码段进行标记:
<!DOCTYPEhtml>
<html>
<head>
<title>特别CSS </title>
</head>
<bodydata-lift-content-id="main">
<divid="main" data-lift="surround?with=default;at=content">
<linkdata-lift="head" rel="stylesheet"
href="red-titles.css" type="text/css" />
<h2>你好</h2>
</div>
</body>
</html>
请注意,这个index.html页面是经过验证的HTML5,并会在<head>标签中产生自定义CSS的结果,如下所示:
<!DOCTYPEhtml>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>应用程序:特殊CSS </title>
<script type="text/javascript"
src="/classpath/jquery.js" id="jquery"></script>
<script type="text/javascript"
src="/classpath/json.js" id="json"></script>
<linkrel="stylesheet"href="red-titles.css" type="text/css">
</head>
<body>
<divid="main">
<h2>你好</h2>
</div>
<script type="text/javascript" src="/ajax_request/liftAjax.js"></script>
<script type="text/javascript">
// <![CDATA[
var lift_page = "F557573613430HI02U4";
// ]]>
</script>
</body>
</html>
讨论
如果您发现标签未显示在该<head>部分中,请检查模板和页面中的HTML是否有效。
您还可以使用<lift:head>...</lift:head>包装多个表达式,并将<head_merge>...</head_merge>在代码示例中使用,作为替代<lift:head>。
您可能会看到另一个变体class="lift:head",作为替代data-lift="head"。
该head片段是内置的片段,但与您可能编写的任何片段无关。代码段所做的是发出一个<head>块,其中包含您想要的元素。这些可以是<title>,<link>,<meta>,<style>,<script>,或<base>标签。<head>该head代码片段生成的块如何最终<head>在页面的主要部分内?当Lift处理您的模板时,它会自动将所有<head>标签合并到<head>页面的主要部分。
你可能怀疑你可以<head>在你的模板的任何地方放一个简单的旧部分。您可以,但这不一定是有效的HTML5标记。
还有tail一个类似的方式,除了标记这个代码段的东西被移动到body标签关闭之前。
也可以看看
“将JavaScript移动到页面末尾”介绍如何使用该tail代码段将JavaScript移动到页面末尾。
该W3CHTML验证是跟踪可能导致与内容问题被移动到页面的头部HTML标记问题的有用工具。
自定义404页面
问题
您想要显示一个自定义的“404”(未找到)页面。
解
在Boot.scala中,添加以下内容:
import net.liftweb.util._
import net.liftweb.http._
LiftRules.uriNotFound.prepend(NamedPF("404handler"){
case (req,failure) =>
NotFoundAsTemplate(ParsePath(List("404"),"html",true,false))
})
现在将为src / main / webapp / 404.html文件提供未知资源的请求。
讨论
该uriNotFound电梯的规则需要返回一个NotFound在回复 Req和Box[Failure]。这允许您根据请求和故障类型自定义响应。
有三种类型NotFound:
NotFoundAsTemplate
有用的从a调用Lift模板处理设施 ParsePath
NotFoundAsResponse
允许你返回一个特定的 LiftResponse
NotFoundAsNode
NodeSeq将电梯 包裹起来,转化为404回应
在示例中,我们匹配任何未找到的情况,不管请求和失败,并将其评估为资源ParsePath。我们使用的路径是/404.html。
如果你想知道,过去两年true和false参数,以ParsePath 表明我们已经给了是绝对的,在一个斜线没有结束的路径。 ParsePath是URI路径的表示形式,如果路径是绝对路径,或者以斜线结尾,那么它们是用于匹配的有用标志,但在这种情况下,它们是不相关的。
请注意,404页面以这种方式呈现时,在站点地图中不会有位置。这是因为我们没有将404.html文件包含在站点地图中,我们不需要,因为我们通过渲染NotFoundAsTemplate而不是将重定向发送到/404.html。但是,这意味着如果您使用包含Menu.builder或类似的模板(作为templates-hidden / default.html)显示错误页面,则会看到“无导航定义”。在这种情况下,您可能希望在404页面上使用其他模板。
或者,您可以将404页面添加到您的站点地图中,但是当通过以下方式显示站点地图时,将其隐藏Menu.builder:
Menu.i("404") / "404" >>Hidden
也可以看看
“捕捉任何异常”显示如何捕获从代码抛出的任何异常。
其他自定义状态页面
问题
您希望显示某个HTTP状态代码的自定义页面。
解
使用LiftRules.responseTransformers来匹配响应并提供一个替代方案。
例如,假设我们要提供一个在我们的Lift应用程序中创建的403(“Forbidden”)状态的自定义页面。此外,假设此页面可能包含片段,因此需要通过Lift渲染流程。
要在Boot.scala中执行此操作,我们定义LiftResponse我们要生成并使用响应,当403状态即将由Lift生成时:
def my403 : Box[LiftResponse] =
for {
session <- S.session
req <- S.request
template =Templates("403":: Nil)
response <- session.processTemplate(template, req,req.path, 403)
} yield response
LiftRules.responseTransformers.append {
case resp if resp.toResponse.code == 403=> my403 openOr resp
case resp => resp
}
该文件的src /主/ web应用/ 403.html现在将服役生成403个状态码的请求。其他非403回应保持不变。
讨论
LiftRules.responseTransformers允许您LiftResponse=> LiftResponse在HTTP处理周期结束时提供 功能来更改响应。这是一个非常通用的机制:在这个例子中,我们是匹配一个状态代码,但是我们可以匹配任何暴露的东西LiftResponse。
在食谱中,我们用模板回应,但是您可能会发现其他类型的响应有意义的情况,例如InMemoryResponse。
你甚至可以简化这个例子:
LiftRules.responseTransformers.append {
case resp if resp.toResponse.code == 403=>RedirectResponse("/403.html")
case resp => resp
}
在Lift 3中,responseTransformers将被修改为部分功能,这意味着您可以离开case resp => resp此示例的最后部分。
该重定向将正常工作,唯一的缺点是HTTP状态代码发回到Web浏览器不会是403代码。
一个更通用的方法是,如果要自定义多个页面,则将定义要自定义的状态代码,为每个页面创建一个页面,然后仅在这些页面上进行匹配:
LiftRules.responseTransformers.append {
case Customised(resp) => resp
case resp => resp
}
object Customised {
// The pages we have customised: 403.html and 500.html
val definedPages =403 :: 500 :: Nil
def unapply(resp: LiftResponse) : Option[LiftResponse] =
definedPages.find(_ == resp.toResponse.code).flatMap(toResponse)
def toResponse(status: Int) : Box[LiftResponse] =
for {
session <- S.session
req <- S.request
template =Templates(status.toString:: Nil)
response <- session.processTemplate(template, req,req.path, status)
} yield response
}
约定Customised是,我们在src / main / webapp中有一个与我们想要显示的状态代码匹配的HTML文件,但当然可以通过在参数中使用不同的模式来更改它Templates。
测试前面的例子的一种方法是将以下内容添加到Boot.scala中,以使所有请求/ secret返回403:
valProtected=If(() => false, () =>ForbiddenResponse("No!"))
val entries =List(
Menu.i("Home") / "index",
Menu.i("secret") / "secret" >> Protected,
// rest of your site map here...
)
如果您要求/秘密,则会触发403响应,这将与响应变压器匹配,显示403.html模板的内容。
也可以看看
“自定义404页”解释了自定义404消息的内置支持。
“捕捉任何异常”显示如何捕获从代码抛出的任何异常。
通知中的链接
问题
你想在你的可点击的链接S.error,S.notice或 S.warning消息。
解
NodeSeq在您的通知中包含一个链接:
S.error("checkPrivacyPolicy",
<span>See our <ahref="/policy">privacy policy</a></span>)
您可以将其与模板中的以下内容配对:
<span data-lift="Msg?id=checkPrivacyPolicy"></span>
讨论
您可能会更加熟悉S.error(String)Lift声明的签名,而不是NodeSeq采用参数的String版本,但是版本只是将String参数转换为scala.xml.Text一种NodeSeq。
也可以看看
链接到下载数据
问题
您想要一个按钮或链接,当被点击时,将触发浏览器中的下载,而不是访问一个页面。
解
创建一个链接SHtml.link,提供一个函数返回一个LiftResponse,并将响应包在一个ResponseShortcutException。
例如,我们将创建一个片段,向用户显示一首诗,并提供一个链接,将该诗作为文本文件下载。此代码段的模板将呈现由以下内容分隔的诗歌的每一行<br>:
<h1>一首诗</h1>
<div data-lift="DownloadLink">
<blockquote>
<spanclass="poem">
<spanclass="line">line here here这里</span><br/>
</span>
</blockquote>
<ahref="">下载链接</a>
</div>
该片段本身将呈现该诗并替换下载链接,该链接将发送浏览器会将其解释为要下载的文件的响应:
package code.snippet
import net.liftweb.util.Helpers._
import net.liftweb.http._
import xml.Text
class DownloadLink {
val poem =
"Roses are red," ::
"Violets are blue," ::
"Lift rocks!" ::
"And so do you." :: Nil
def render =
".poem" #> poem.map(line => ".line" #> line) &
"a" #> downloadLink
def downloadLink =
SHtml.link("/notused",
() =>throw new ResponseShortcutException(poemTextFile),
Text("Download") )
def poemTextFile : LiftResponse =
InMemoryResponse(
poem.mkString("\n").getBytes("UTF-8"),
"Content-Type" ->"text/plain; charset=utf8" ::
"Content-Disposition" ->"attachment; filename=\"poem.txt\"" :: Nil,
cookies=Nil, 200)
}
回想一下,SHtml.link生成一个链接,然后在链接之前执行您提供的功能。
这里的诀窍是,包装LiftResponse在一个ResponseShortcutException意味着将指示提升响应是完成的,所以链接到(在这种情况下notused)的页面将不被处理。浏览器很高兴:它具有对用户点击的链接的响应,并将其呈现如何,在这种情况下可能会将文件保存到磁盘。
讨论
SHtml.link通过生成Lift与您提供的功能相关联的URL来工作。在一个被调用的页面上downloadlink,该URL将如下所示:
downloadlink?F845451240716XSXE3G = _#未使用
当遵循该链接时,Lift会在处理链接资源之前查找该函数并执行该功能。但是,在这种情况下,我们正在通过抛出这个特殊的异常来改变Lift管道。这被Lift所捕获,并且由异常包装的响应被视为请求的最终响应。
S.redirectTovia通过使用此快捷方式ResponseShortcutException.redirect。这个伴侣对象也定义了shortcutResponse你可以这样使用的:
import net.liftweb.http.ResponseShortcutException._
def downloadLink =
SHtml.link("/notused",
() => {
S.notice("The file wasdownloaded")
throw shortcutResponse(poemTextFile)
},
Text("Download") )
我们已经包括一个S.notice突出显示,throwshortcutResponse将在下一页加载页面时处理提升通知,而throw new ResponseShortcutException不会。在这种情况下,当用户下载文件时,通知不会出现,但是下一次将会显示通知,例如当用户浏览到另一页面时。在许多情况下,差异是无关紧要的。
这个食谱使用了Lift的有状态功能。您可以看到能够关闭状态(诗),并提供从内存下载的数据是多么有用。如果您已经从数据库创建了报告,则可以将其作为下载提供,而无需从数据库重新生成项目。
但是,在其他情况下,您可能希望避免将此数据作为链接上的一个功能。在这种情况下,您将需要创建一个返回的REST服务LiftResponse。
也可以看看
第4章讨论了Lift中基于REST的服务。
“流内容”讨论InMemoryResponse和类似的回复内容返回浏览器。
对于报告,ApachePOI项目包含用于生成Excel文件的库; 和OpenCSV是用于生成CSV文件库。
测试要求
问题
你想要能够测试一个需要的功能Req。
解
向Lift提供一个模拟请求MockWeb.testReq,并在Req提供的上下文中运行测试testReq。
第一步是将Lift的测试工具包作为build.sbt中的项目的依赖项:
libraryDependencies+= "net.liftweb" %% "lift-testkit" % "2.5" % "test"
为了演示如何使用testReq,我们将测试一个检测Google爬网程序的功能。Google通过各种User-Agent标头值识别抓取工具,因此我们要测试的功能如下所示:
package code.lib
import net.liftweb.http.Req
object RobotDetector {
val botNames =
"Googlebot" ::
"Mediapartners-Google" ::
"AdsBot-Google" :: Nil
def known_?(ua: String) =
botNames.exists(ua contains _)
def googlebot_?(r: Req) : Boolean =
r.header("User-Agent").exists(known_?)
}
我们有botNamesGoogle作为用户代理发送的魔术列表,我们定义一个支票,该支票known_?需要用户代理字符串,并查看是否有任何机器人满足该用户代理字符串中包含的条件。
该googlebot_?方法给出了一个Lift Req对象,从中我们查找了标题。这个评估是一个Box[String],因为它可能没有标题。我们通过查看是否存在满足条件Box的值来找到known_?答案。
为了测试这个,我们创建一个用户代理字符串,准备一个MockHttpServletRequest标题,并使用电梯MockWeb将低级别请求转换为电梯Req,以便我们测试:
package code.lib
import org.specs2.mutable._
import net.liftweb.mocks.MockHttpServletRequest
import net.liftweb.mockweb.MockWeb
classSingleRobotDetectorSpec extends Specification {
"Google Bot Detector" should {
"spot a web crawler" in {
val userAgent = "Mozilla/5.0 (compatible;Googlebot/2.1)"
// Mock a request with the right header:
val http =new MockHttpServletRequest()
http.headers =Map("User-Agent"-> List(userAgent))
// Test with a Lift Req:
MockWeb.testReq(http) { r =>
RobotDetector.googlebot_?(r) must beTrue
}
}
}
}
使用test命令从SBT运行此操作会产生:
[info] SingleRobotDetectorSpec
[info]
[info] Google Bot Detector should
[info] + spot a web crawler
[info]
[info] Total for specification SingleRobotDetectorSpec
[info] Finished in 18 ms
[info] 1 example, 0 failure, 0 error
讨论
虽然MockWeb.testReq正在处理Req为我们创造的一个环境,为它Req提供MockHttpServletRequest。要配置请求,请在使用之前创建一个模拟实例并根据需要对其状态进行突变testReq。
除了HTTP头,您还可以设置Cookie,内容类型,查询参数,HTTP方法,身份验证类型和正文。分配有body变化,可以根据您分配的值方便地设置内容类型:
· JValue将使用内容类型application/json。
· NodeSeq将使用text/xml(或者您可以提供替代方案)。
· String使用text/plain(除非您提供替代品)。
· Array[Byte] 不设置内容类型。
数据表
在前面所示的示例测试中,必须为不同的用户代理重复设置相同的代码将是乏味的。Specs2的数据表提供了一种通过相同测试来运行不同示例值的紧凑方式:
package code.lib
import org.specs2._
import matcher._
import net.liftweb.mocks.MockHttpServletRequest
import net.liftweb.mockweb.MockWeb
class RobotDetectorSpec extendsSpecification with DataTables {
def is = "Can detect Google robots" ^{
"Bot?" || "User Agent"|
true !! "Mozilla/5.0 (Googlebot/2.1)" |
true !! "Googlebot-Video/1.0" |
true !! "Mediapartners-Google" |
true !! "AdsBot-Google" |
false !! "Mozilla/5.0 (KHTML, like Gecko)" |> {
(expectedResult, userAgent) => {
val http =new MockHttpServletRequest()
http.headers =Map("User-Agent"-> List(userAgent))
MockWeb.testReq(http) { r =>
RobotDetector.googlebot_?(r) must_== expectedResult
}
}
}
}
}
这个测试的核心基本没有变化:我们创建一个模拟,设置用户代理,并检查结果googlebot_?。不同的是,Specs2提供了一个整洁的方式来列出各种场景并通过功能进行管理。
在SBT下运行此输出将是:
[info] Can detect Google robots
[info] + Bot? | User Agent
[info] true | Mozilla/5.0 (Googlebot/2.1)
[info] true | Googlebot-Video/1.0
[info] true | Mediapartners-Google
[info] true | AdsBot-Google
[info] false | Mozilla/5.0 (KHTML, like Gecko)
[info]
[info] Total for specification RobotDetectorSpec
[info] Finished in 1 ms
[info] 1 example, 0 failure, 0 error
虽然我们的表中首先列出了预期值,但是没有必要把它放在首位。
也可以看看
The Lift wiki讨论了这个主题以及其他方法,如使用Selenium进行测试。
渲染纺织品标记
问题
您要在Web应用程序中呈现纺织品标记。
解
将Lift Textile模块安装在build.sbt文件中,将以下内容添加到依赖关系列表中:
"net.liftmodules"%% "textile_2.5" % "1.3"
然后,您可以使用该模块使用该toHtml方法渲染纺织品。
例如,在添加模块并运行SBT console命令后启动SBT,可以尝试使用模块:
scala>
import
net.liftmodules.textile._
import
net.liftmodules.textile._
scala>
TextileParser
.toHtml("""
| h1. Hi!
|
| The module in "Lift":http://www.liftweb.net for turning Textile markup
| into HTML is pretty easy to use.
|
| * As you can see.
| * In this example.
| """)
res0
:
scala.xml.NodeSeq
=
NodeSeq
(,
<h1>
Hi
!</h1>,
,<p>
The module in<a
href=
"http://www.liftweb.net"
>
Lift</a>
for turning Textilemarkup<br></br>
into HTML is pretty easy to use.</p>
,,<ul><li>
As you can see.</li>
<li>
In this example.</li>
</ul>
,, )
如果我们漂亮地打印出来,看看输出会更容易一些:
scala>
val
pp
=
new
PrettyPrinter
(width
=
35
,
step
=
2
)
pp
:
scala.xml.PrettyPrinter
=
scala.xml.
PrettyPrinter
@
54
c19de8
scala>
pp.formatNodes(res0)
res1
:
String
=
<h1>
Hi!</h1><p>
The module in
<a
href=
"http://www.liftweb.net"
>
Lift
</a>
for turning Textile markup
<br></br>
into HTML is pretty easy to use.
</p><ul>
<li>
As you can see.</li>
<li>
In this example.</li>
</ul>
讨论
没有什么特别的代码可以成为一个提升模块,虽然有常见的约定:它们通常被打包为net.lift模块,但不一定要; 他们通常依赖于Lift的一个版本; 他们有时使用提供的钩LiftRules来提供特定的行为。任何人都可以创建和发布电梯模块,任何人都可以为现有模块做出贡献。最后,它们被声明为SBT中的依赖关系,并且像任何其他依赖项一样被拉入你的代码。
依赖名称由两个元素组成:模块兼容的Lift的名称和“版本”,如图2-1所示。“版”我们只是提升版本号的第一部分。“2.5”版本意味着模块与任何启动“2.5”的升降机兼容。
图2-1。模块版本的结构
这种结构已被采用,因为模块具有独立的释放循环,不依赖于Lift。然而,模块也可能依赖于Lift的某些功能,Lift可能会在主要版本之间更改API,因此需要使用Lift版本号的一部分来标识模块。
也可以看看
没有什么是纺织品的真正规格,但是有可用的参考资料,涵盖了要输入的典型的标记种类以及您期望看到的HTML。
纺织模块的单元测试为您提供了一系列支持的示例。
“在模块中共享代码”介绍如何创建模块。