第5章、JavaScript,Ajax和Comet

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暴露的任何事件。

SHtml.onEvent
调用具有签名的函数, String => JsCmd 因为它被传递给 value 它附加的节点。在上一个示例中,这将是空字符串,因为按钮没有值。
SHtml.ajaxCall
这是比一般的 onEvent ,因为你给它你想传递给您的服务器端代码的表达式。
SHtml.jsonCall
这更为一般:您给它一个在客户端上执行时返回JSON对象的函数,并且该对象将被传递到服务器端函数。

我们依次看看这些。

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()")

我们的第二个参数ajaxCallJsExp表达式的值作为一个String我们正在使用Lift的JavaScript命令之一替换新值。新值是增加数字的结果(提供它是一个数字)。

最终的结果是你按下按钮和号码更新。应该不用说这些是简单的插图,你可能不想要一个服务器往返来添加一个数字。当在服务器上执行一些有价值的行动时,这些技术就会成为自己的技术。

你可能已经猜到这onEvent是为了ajaxCall实现的JsRaw("this.value")

jsonCall:接收JSON值

两者ajaxCallonEvent最终评估一个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))))

这是JValueJSON的表示。

也可以看看

“选择选项更改时的呼叫服务器”包含一个示例SHtml.onEvents,它将功能绑定到某个事件上的多个事件NodeSeq

探索Lift,第10章列出了JsExp可以使用的各种ajaxCall

“Ajax的JSON表单处理”使用JsonHandler于来自形式向服务器发送JSON数据。

选择选项更改时的呼叫服务器

问题

当选择HTML选择选项时,您想要在服务器上触发一个功能。

注册一个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[PlanetLightYears](
    "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[(StringString)] =
      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)
  }
}

代码的最后一行是为我们做的工作。它正在生成选项并将选择绑定到调用的函数handlerhandler使用所选项目的值调用函数。

我们使用相同的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_ajaxHandlerJavaScript函数中定义liftAjax.js,其会自动添加到您的网页。

收集表单提交的价值

如果您需要在定期提交表单上另外捕获所选值,则可以使用SHtml.onEvents这将事件侦听器附加到一个NodeSeq事件发生时触发服务器端功能。我们可以使用常规表单与常规选择框,但是当选择更改时,在Ajax中连接到服务器。

为了利用这一点,我们的代码段变化很小:

var selectedValue : String = ""

"select" #> onEvents("onchange")(handler) {
  select(options, default, selectedValue = _)
} &
"type=submit" #> onSubmitUnit( () => S.notice("Destination "+selectedValue))

handleronchange事件触发时,我们正在安排调用相同的函数此事件绑定适用于常规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代码中创建客户端操作

问题

在Lift代码中,您需要设置纯粹在客户端JavaScript中运行的操作。

将您的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.netHTML将呈现为:

<button onclick="alert(&quot;Here we go...&quot;);
window.location = &quot;http://liftweb.net&quot;;">点击我</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(&quot;World!&quot;,3)">点击我的问候语</button>

请注意,类型StringInt呼叫的JavaScript语法一直保存。这是因为JavaScript函数名后面JE.Call带有可变数量的JsExp参数。有JavaScript的原始类型包装(JE.StrJE.NumJsTrueJsFalse)和隐式转换,以节省您不必包裹斯卡拉重视自己。

也可以看看

探索提升的第10章提供了一个列表JsCmdsJE表达式。

专注于页面加载的领域

问题

当页面加载时,您希望浏览器从键盘中输入特定字段。

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选择:

Focus
获取元素ID并设置对元素的重点
SetValueAndFocus
类似于 Focus ,但需要一个额外的  String 值来填充元素

如果您需要动态地从Ajax或Comet组件设置焦点,这两个是有用的

也可以看看

源码FocusOnLoad值得一试,了解它和相关命令的构造方式。这可能有助于将您自己的JavaScript功能打包到可用于CSS绑定表达式的命令中

将一个CSS类添加到Ajax窗体

问题

你想设置一个Ajax表单的CSS类。

通过?class=查询参数命名类

<form data-lift="form.ajax?class=boxed">
...
</form>

讨论

如果需要设置多个CSS类,请在类名之间编写空格(例如,class=boxed+primary)。

form.ajax构造是一个常规的片段调用:Form片段是少数内置的片段之一,在这种情况下,我们正在调用该ajax对象方法。但是,通常,代码段调用不会将属性复制到生成的标记中,但是这个代码段被实现来做到这一点。

也可以看看

有关在您自己的片段中访问这些查询参数的示例,请参阅“HTML条件注释”

简单提升,第4章介绍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移到页尾

问题

您希望将代码片段中创建的JavaScript包含在HTML页面的末尾。

使用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.appendJsS.appendGlobalJs以相同的方式工作,但不包括readyJavaScript的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在会话丢失的情况下执行。

配置您的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应用程序的运行情况的有用方式。有很多插件和产品可以帮助您,比如FirefoxHttpFox插件

Ajax文件上传

问题

您希望为用户提供一个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-urlinput领域,这是我们匹配的地址:

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配置。例如,我们已经使用了插件的addprogressall以及done处理程序来显示,更新,然后淡出的进度条。上传完成后,上传的文件的名称将附加到页面。

在REST服务中,上传的文件可以通过uploadedFiles请求方法获得。当Lift收到多形式,它会自动提取文件的uploadedFiles每一个都是一个FileParamHolder,让我们进入fileNamelengthmimeType,和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”。

也可以看看

Diego Medina发布了一个提升REST代码Gist,更完整地整合了小部件的图像上传和图像查看功能,特别是实现了该窗口小部件期望的功能的JSON响应

“文件上传”描述了Lift的基本文件上传行为,以及如何控制文件存储位置。

Antonio Salazar Cardozo已经发布了使用Lift的Ajax机制执行Ajax文件上传的示例代码这避免了外部JavaScript库。

格式化有线单元格

问题

您希望有线UI元素具有与纯文本转换为字符串不同的格式。例如,您希望以浮点值显示货币。

使用该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的接线机制,并给出了详细的购物示例。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值