第2章、Lift和HTML

生成HTML通常是Web应用程序的主要组件。本章涉及LiftView First方法和CSS选择器的使用。后面的章节更加专注于表单处理,REST Web服务,JavaScriptAjaxComet

本章的代码是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>importnet.liftweb.util.Helpers._
importnet.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 工作表,其中EclipseIntelliJ IDEA的做到,那么你也可以运行在一个工作表中的转换。

也可以看看

选择器的语法最好在Simply Lift中描述

有关如何使用EclipseIntelliJ 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-primarydiv

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网站上,有更多关于SiteMapMenu片段。

HTML条件注释

问题

您想使用Internet Explorer HTML条件注释在你的模板

将标记放在代码段中,并将该片段包含在您的页面或模板中。

例如,假设我们要包含HTML5 Shiv(又名HTML5 ShimJavaScript,以便我们可以将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时找不到代码段

问题

您正在使用LiftHTML5解析器,其中一个代码段将呈现找不到类错误。甚至发生了<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)。

也可以看看

邮件列表中列出XHTMLHTML5解析器之间的主要区别。

避免CSSJavaScript缓存

问题

您已经在应用程序中修改了CSSJavaScript,但是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=> StringLiftRules.attachResourceId。以前显示的默认实现在示例中使用资源名称/myscript.js,并返回附加了ID的资源名称。

您还可以在<lift:with-resource-id>...<lift:with-resource-id>块内包装多个标签 。但是,请避免在<head>您的页面中执行此操作,因为HTML5解析器将标签移动到头部之外。

使用代理

请注意,一些代理可能选择不使用查询参数缓存资源。如果这对您有影响,可以编写自定义资源ID方法来将随机资源ID从查询参数中移出并进入路径。

这是一个这样做的方法。而不是生成看起来像/assets/style.css?F61973 JavaScriptCSS链接,我们将生成/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应用程序前使用nginxApache,则可以将这些Web服务器配置为在到达Lift之前执行重写。

也可以看看

http://bit.ly/14BfNYJ显示了默认的实现attachResourceId

Google优化缓存说明是浏览器行为的一个很好的信息来源。

有一个支持URL重写,描述在Lift维基。很少使用重写,只适用于特殊情况。它不适合这种复兴,如发布到Stackoverflow中所概述的。许多问题看起来像重写问题更好的解决了一个菜单参数

Grunt和类似的工具可以修改路径。Diego Medina“ 使用GruntBowerLift”的帖子是一个很好的起点。

添加到页面的头

问题

您使用模板进行布局,但在一个特定页面上,您需要向该<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在回复 ReqBox[Failure]。这允许您根据请求和故障类型自定义响应。

有三种类型NotFound

NotFoundAsTemplate

有用的从a调用Lift模板处理设施 ParsePath

NotFoundAsResponse

允许你返回一个特定的 LiftResponse

NotFoundAsNode

NodeSeq将电梯 包裹起来,转化为404回应

在示例中,我们匹配任何未找到的情况,不管请求和失败,并将其评估为资源ParsePath。我们使用的路径是/404.html

如果你想知道,过去两年truefalse参数,以ParsePath 表明我们已经给了是绝对的,在一个斜线没有结束的路径。 ParsePathURI路径的表示形式,如果路径是绝对路径,或者以斜线结尾,那么它们是用于匹配的有用标志,但在这种情况下,它们是不相关的。

请注意,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=> LiftResponseHTTP处理周期结束时提供 功能来更改响应。这是一个非常通用的机制:在这个例子中,我们是匹配一个状态代码,但是我们可以匹配任何暴露的东西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.errorS.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将如下所示:

downloadlinkF845451240716XSXE3G = _#未使用

当遵循该链接时,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 <ahref="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>,
, )

如果我们漂亮地打印出来,看看输出会更容易一些:

scala>val pp =new PrettyPrinter(width=35, step=2)
pp: scala.xml.PrettyPrinter = scala.xml.PrettyPrinter@54c19de8
 
scala> pp.formatNodes(res0)
res1: String =
<h1>Hi!</h1><p>
  The module in
  <ahref="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

纺织模块的单元测试为您提供了一系列支持的示例。

在模块中共享代码介绍如何创建模块。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值