Chapter 5. JavaScript, Ajax, and Comet

Lift is known for its great Ajax and Comet support, and in this chapter, we’ll explore these.

For an introduction to Lift’s Ajax and Comet features, see Simply Lift, Chapter 9 of Lift in Action (Perrett, 2012, Manning Publications, Co.), or watch Diego Medina’s video presentation.

The source code for this chapter is at https://github.com/LiftCookbook/cookbook_ajax.

Trigger Server-Side Code from a Button

Problem

You want to trigger some server-side code when the user presses a button.

Solution

Use 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)
}

In this snippet, we are binding the click event of a button to an ajaxInvoke: when the button is pressed, Lift arranges for the function you gave ajaxInvoke to be executed.

That function, callback, is just logging a message and returning a JavaScript alert to the browser. The corresponding HTML might include:

<div data-lift="AjaxInvoke.button">
  <button>Click Me</button>
</div>

Discussion

The signature of the function you pass to ajaxInvoke is Unit => JsCmd, meaning you can trigger a range of behaviours: from returning Noop if you want nothing to happen, through changing DOM elements, all the way up to executing arbitrary JavaScript.

The previous example uses a button but will work on any element that has an event you can bind to. We’re binding to onclick but it could be any event the DOM exposes.

Related to ajaxInvoke are the following functions:

SHtml.onEvent
Calls a function with the signature String => JsCmd because it is passed the value of the node it is attached to. In the previous example, this would be the empty string, as the button has no value.
SHtml.ajaxCall
This is more general than onEvent, as you give it the expression you want passed to your server-side code.
SHtml.jsonCall
This is even more general still: you give it a function that will return a JSON object when executed on the client, and this object will be passed to your server-side function.

Let’s look at each of these in turn.

onEvent: receiving the value of a DOM element

You can use onEvent with any element that has a value attribute and responds to the event you choose to bind to. The function you supply to onEvent is called with the element’s value. As an example, we can write a snippet that presents a challenge to the user and validates the response:

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

}

This snippet prompts the user to add the two random numbers presented in the <p> tag, and binds a validation function to the <input> on the page:

<div data-lift="OnEvent">
  <p>Problem appears here</p>
  <input placeholder="Type your answer"></input>
</div>

When onchange is triggered (by pressing Return or the Tab key, for example), the text entered is sent to our onEvent function as a String. On the server-side, we check the answer and send back “Correct!” or “Try again” as a JavaScript alert.

ajaxCall: receiving an arbitrary client-side string

Where onEvent sends this.value to your server-side code, ajaxCall allows you to specify the client-side expression used to produce a value.

To demonstrate this, we can create a template that includes two elements: a button and a text field. We’ll bind our function to the button, but read a value from the input field:

<div data-lift="AjaxCall">
  <input id="num" value="41"></input>
  <button>Increment</button>
</div>

We want to arrange for the button to read the num field, increment it, and return it back to the input field:

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

 }

The first argument to ajaxCall is the expression that will produce a value for our function. It can be any JsExp, and we’ve used ValById, which looks up the value of an element by the ID attribute. We could have used a regular jQuery expression to achieve the same effect with JsRaw("$('#num').val()").

Our second argument to ajaxCall takes the value of the JsExp expression as a String. We’re using one of Lift’s JavaScript commands to replace the value with a new value. The new value is the result of incrementing the number (providing it is a number).

The end result is that you press the button and the number updates. It should go without saying that these are simple illustrations, and you probably don’t want a server round-trip to add one to a number. The techniques come into their own when there is some action of value to perform on the server.

You may have guessed that onEvent is implemented as an ajaxCall for JsRaw("this.value").

jsonCall: receiving a JSON value

Both ajaxCall and onEvent end up evaluating a String => JsCmd function. By contrast, jsonCall has the signature JValue => JsCmd, meaning you can pass more complex data structures from JavaScript to your Lift application.

To demonstrate this, we’ll create a template that asks for input, has a function to convert the input into JSON, and a button to send the JSON to the server:

<div data-lift="JsonCall">
  <p>Enter an addition question:</p>
  <div>
    <input id="x"> + <input id="y"> = <input id="z">.
  </div>
  <button>Check</button>
</div>

<script type="text/javascript">
// <![CDATA[
function currentQuestion() {
  return {
    first:  parseInt($('#x').val()),
    second: parseInt($('#y').val()),
    answer: parseInt($('#z').val())
  };
}
// ]]>

The currentQuestion function is creating an object, which will be turned into a JSON string when sent to the server. On the server, we’ll check that this JSON represents a valid integer addition problem:

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 _ )
  }
}

Working from the bottom of this snippet up, we see a binding of the <button> to the jsonCall. The value we’ll be working on is the value provided by the JavaScript function called currentQuestion. This was defined on the template page. When the button is clicked, the JavaScript function is called and the resulting value will be presented to validate, which is our JValue => JsCmd function.

All validate does is log the JSON data and alert back if the question looks correct or not. To do this, we use the Lift JSON ability to extract JSON to a case class and call the valid_? test to see if the numbers add up. This will evaluate to Some(true) if the addition works, Some(false) if the addition isn’t correct, or None if the input is missing or not a valid integer.

Running the code and entering 1, 2, and 3 into the text fields will produce the following in the server log:

JObject(List(JField(first,JInt(1)), JField(second,JInt(2)),
  JField(answer,JInt(3))))

This is the JValue representation of the JSON.

See Also

Call Server When Select Option Changes includes an example of SHtml.onEvents, which will bind a function to a number of events on a NodeSeq.

Exploring Lift, Chapter 10, lists various JsExp classes you can use for ajaxCall.

Ajax JSON Form Processing using JsonHandler to send JSON data from a form to the server.

Call Server When Select Option Changes

Problem

When an HTML select option is selected, you want to trigger a function on the server.

Solution

Register a String => JsCmd function with SHtml.ajaxSelect.

In this example, we will look up the distance from Earth to the planet a user selects. This lookup will happen on the server and update the browser with the result. The interface is:

<div data-lift="HtmlSelectSnippet">
  <div>
    <label for="dropdown">Planet:</label>
    <select id="dropdown"></select>
  </div>
  <div id="distance">Distance will appear here</div>
</div>

The snippet code binds the <select> element to send the selected value to the server:

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)
  }
}

The last line of the code is doing the work for us. It is generating the options and binding the selection to a function called handler. The handler function is called with the value of the selected item.

We’re using the same String (the planet name) for the option label and value, but they could be different.

Discussion

To understand what’s going on here, take a look at the HTML that Lift produces:

<select id="dropdown"
  onchange="liftAjax.lift_ajaxHandler('F470183993611Y15ZJU=' +
    this.options[this.selectedIndex].value, null, null, null)">
  <option value=""></option>
  <option value="Tau Ceti e">Tau Ceti e</option>
  ...
</select>

The handler function has been stored by Lift under the identifier of F470183993611Y15ZJU (in this particular rendering). An onchange event handler is attached to the <select> element and is responsible for transporting the selected value to the server, and bringing a value back. The lift_ajaxHandler JavaScript function is defined in liftAjax.js, which is automatically added to your page.

Collecting the value on form submission

If you need to additionally capture the selected value on a regular form submission, you can make use of SHtml.onEvents. This attaches event listeners to a NodeSeq, triggering a server-side function when the event occurs. We can use this with a regular form with a regular select box, but wire in Ajax calls to the server when the select changes.

To make use of this, our snippet changes very little:

var selectedValue : String = ""

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

We are arranging for the same handler function to be called when an onchange event is triggered. This event binding is applied to a regular SHtml.select, which is storing the selectedValue when the form is submitted. We also bind a submit button to a function that generates a notice of which planet was selected.

The corresponding HTML also changes little. We need to add a button and make sure the snippet is marked as a form with ?form:

<div data-lift="HtmlSelectFormSnippet?form=post">

  <div>
    <label for="dropdown">Planet:</label>
    <select id="dropdown"></select>
  </div>

  <div id="distance">Distance will appear here</div>

  <input type="submit" value="Book Ticket"/>

</div>

Now when you change a selected value, you see the dynamically updated distance calculation, but pressing the “Book Ticket” button also delivers the value to the server.

See Also

Use a Select Box with Multiple Options describes how to use classes rather than String values for select boxes.

Creating Client-Side Actions in Your Scala Code

Problem

In your Lift code you want to set up an action that is run purely in client-side JavaScript.

Solution

Bind your JavaScript directly to the event handler you want to run.

Here’s an example where we make a button slowly fade away when you press it, but notice that we’re setting up this binding in our server-side Lift code:

package code.snippet

import net.liftweb.util.Helpers._

object ClientSide {
  def render = "button [onclick]" #> "$(this).fadeOut()"
}

In the template, we’d perhaps say:

<div data-lift="ClientSide">
  <button>Click Me</button>
</div>

Lift will render the page as:

<button onclick="$(this).fadeOut()">Click Me</button>

Discussion

Lift includes a JavaScript abstraction that you can use to build up more elaborate expressions for the client-side. For example, you can combine basic commands:

import net.liftweb.http.js.JsCmds.{Alert, RedirectTo}

def render = "button [onclick]" #>
  (Alert("Here we go...") & RedirectTo("http://liftweb.net"))

This pops up an alert dialog and then sends you to http://liftweb.net. The HTML would be rendered as:

<button onclick="alert(&quot;Here we go...&quot;);
window.location = &quot;http://liftweb.net&quot;;">Click Me</button>

Another option is to use JE.Call to execute a JavaScript function with parameters. Suppose we have this function defined:

function greet(who, times) {
  for(i=0; i<times; i++)
    alert("Hello "+who);
}

We could bind a client-side button press to this client-side function like this:

import net.liftweb.http.js.JE

def render =
  "button [onclick]" #> JE.Call("greet", "World!", 3)

On the client-side, we’d see:

<button onclick="greet(&quot;World!&quot;,3)">Click Me For Greeting</button>

Note that the types String and Int have been preserved in the JavaScript syntax of the call. This has happened because JE.Call takes a variable number of JsExp arguments after the JavaScript function name. There are wrappers for JavaScript primitive types (JE.Str, JE.Num, JsTrue, JsFalse) and implicit conversions to save you having to wrap the Scala values yourself.

See Also

Chapter 10 of Exploring Lift gives a list of JsCmds and JE expressions.

Focus on a Field on Page Load

Problem

When a page loads, you want the browser to select a particular field for input from the keyboard.

Solution

Wrap the input with a FocusOnLoad command:

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"/>)
}

The CSS transform in render will match against a name="username" element in the HTML and replace it with a text input field that will be focused on automatically when the page loads.

Although we’re focusing on inline HTML, this could be any NodeSeq, such as the one produced by SHtml.text.

Discussion

FocusOnLoad is an example of a NodeSeq => NodeSeq transformation. It appends to the NodeSeq with the JavaScript required to set focus on that field.

The JavaScript that performs the focus simply looks up the node in the DOM by ID and calls focus on it. Although the previous example code doesn’t specify an ID, the command FocusOn is smart enough to add one automatically for us.

There are two related JsCmd choices:

Focus
Takes an element ID and sets focus on the element
SetValueAndFocus
Similar to Focus, but takes an additional String value to populate the element with

These two are useful if you need to set focus from Ajax or Comet components dynamically.

See Also

The source for FocusOnLoad is worth checking out to understand how it and related commands are constructed. This may help you package your own JavaScript functionality up into commands that can be used in CSS binding expressions.

Add a CSS Class to an Ajax Form

Problem

You want to set the CSS class of an Ajax form.

Solution

Name the class via ?class= query parameter:

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

Discussion

If you need to set multiple CSS classes, encode a space between the class names (e.g., class=boxed+primary).

The form.ajax construction is a regular snippet call: the Form snippet is one of the handful of built-in snippets, and in this case, we’re calling the ajax method on that object. However, normally snippet calls do not copy attributes into the resulting markup, but this snippet is implemented to do exactly that.

See Also

For an example of accessing these query parameters in your own snippets, see HTML Conditional Comments.

Simply Lift, Chapter 4, introduces Ajax forms.

Running a Template via JavaScript

Problem

You want to load an entire page—a template with snippets—inside of the current page (i.e., without a browser refresh).

Solution

Use Template to load the template and SetHtml to place the content on the page.

Let’s populate a <div> with the site home page when a button is pressed:

<div data-lift="TemplateLoad">
  <div id="inject">Content will appear here</div>
  <button>Load Template</button>
</div>

The corresponding snippet would be:

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 _)
}

Clicking the button will cause the content of /index.html to be loaded into the inject element.

Discussion

Templates produces a Box[NodeSeq]. In the previous example, we map this content into a JsCmd that will populate the inject <div>.

If you write unit tests to access templates, be aware that you may need to modify your development or testing environment to include the webapps folder. To do this for SBT, add the following to build.sbt:

unmanagedResourceDirectories in Test <+= (baseDirectory) {
  _ / "src/main/webapp"
}

For this to work in your IDE, you’ll need to add webapp as a source folder to locate templates.

See Also

Trigger Server-Side Code from a Button describes ajaxInvoke and related methods.

Move JavaScript to End of Page

Problem

You want the JavaScript created in your snippet to be included at the end of the HTML page.

Solution

Use S.appendJs, which places your JavaScript just before the closing </body> tag, along with other JavaScript produced by Lift.

In this HTML, we have placed a <script> tag in the middle of the page and marked it with a snippet called JavaScriptTail:

<!DOCTYPE html>
<head>
  <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
  <title>JavaScript in Tail</title>
</head>
<body data-lift-content-id="main">
<div id="main" data-lift="surround?with=default;at=content">
  <h2>JavaScript in the tail of the page</h2>

  <script type="text/javascript" data-lift="JavaScriptTail">
  </script>

  <p>
    The JavaScript about to be run will have been moved
    to the end of this page, just before the closing
    body tag.
  </p>
</div>
</body>
</html>

The <script> content will be generated by a snippet. It doesn’t need to be a <script> tag; the snippet just replaces the content with nothing, but hanging the snippet on the <script> tag is a reminder of the purpose of the snippet:

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
  }
}

Although the snippet is rendering nothing, it calls S.appendJs with a JsCmd. This will produce the following in the page just before the end of the body:

<script type="text/javascript">
// <![CDATA[
jQuery(document).ready(function() {
  alert("Hi");
});
// ]]>
</script>

Observe that the snippet was in the middle of the template, but the JavaScript appears at the end of the rendered page.

Discussion

There are three other ways you could tackle this problem. The first is to move your JavaScript to an external file, and simply include it on the page where you want it. For substantial JavaScript code, this might make sense.

The second is a variation on S.appendJs: S.appendGlobalJs works in the same way but does not include the jQuery ready around your JavaScript. This means you have no guarantee the DOM has loaded when your function is called.

A third option is wrap your JavaScript in a <lift:tail> snippet:

class JavascriptTail {
  def render =
    "*" #> <lift:tail>{Script(OnLoad(Alert("Hi")))}</lift:tail>
}

Note that lift:tail is a general purpose Lift snippet and can be used to move various kinds of content to the end of the page, not just JavaScript.

See Also

Adding to the Head of a Page discusses a related Lift snippet for moving content to the head of the page.

Snippet Not Found When Using HTML5 describes the different ways of invoking a snippet, such as <lift:tail> versus data-lift="tail".

Run JavaScript on Comet Session Loss

Problem

You’re using a Comet actor and you want to arrange for some JavaScript to be executed in the event of the session being lost.

Solution

Configure your JavaScript via LiftRules.noCometSessionCmd.

As an example, we can modify the standard Lift chat demo to save the message being typed in the event of the session loss. In the style of the demo, we would have an Ajax form for entering a message and the Comet chat area for displaying messages received:

<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>A message</li>
  </ul>
</div>

To this we can add a function, stash, which we want to be called in the event of a Comet session being lost:

<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>

Our stash function will save the current value of the input field in a cookie called stashed. We arrange, on page load, to check for that cookie and insert the value into our message field.

The final part is to modify Boot.scala to register our stash function:

import net.liftweb.http.js.JsCmds.Run

LiftRules.noCometSessionCmd.default.set( () => Run("stash()") )

In this way, if a session is lost while composing a chat message, the browser will stash the message, and when the page reloads the message will be recovered.

To test the example, type a message into the message field, then restart your Lift application. Wait 10 seconds, and you’ll see the effect.

Discussion

Without changing noCometSessionCmd, the default behaviour of Lift is to arrange for the browser to load the home page, which is controlled by the LiftRules.noCometSessionPage setting. This is carried out via the JavaScript function lift_sessionLost in the file cometAjax.js.

By providing our own () => JsCmd function to LiftRules.noCometSessionCmd, Lift arranges to call this function and deliver the JsCmd to the browser, rather than lift_sessionLost. If you watch the HTTP traffic between your browser and Lift, you’ll see the stash function call being returned in response to a Comet request.

This recipe has focused on the handling of loss of session for Comet; for Ajax, there’s a corresponding LiftRules.noAjaxSessionCmd setting.

See Also

You’ll find the The ubiquitous Chat app in Simply Lift.

Being able to debug HTTP traffic is a useful way to understand how your Comet or Ajax application is performing. There are many plugins and products to help with this, such as the HttpFox plugin for Firefox.

Ajax File Upload

Problem

You want to offer your users an Ajax file upload tool, with progress bars and drag-and-drop support.

Solution

Add Sebastian Tschan’s jQuery File Upload widget to your project, and implement a REST end point to receive files.

The first step is to download the widget and drag the js folder into your application as src/main/webapp/js. We can then use the JavaScript in a template:

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>jQuery File Upload Example</title>
</head>
<body>

<h1>Drag files onto this page</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>

This template provides an input field for files, an area to use as a progress indicator, and configures the widget when the page loads in a jQuery $( ... ) block. This is just regular usage of the JavaScript widget, and nothing particularly Lift-specific.

The final part is to implement a Lift REST service to receive the file or files. The URL of the service, /upload, is set in data-url on the input field, and that’s the address we match on:

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

  }

}

This implementation simply logs the name of the file received and acknowledges successful delivery with a 200 status code back to the widget.

As with all REST services, it needs to be registered in Boot.scala:

LiftRules.dispatch.append(code.rest.AjaxFileUpload)

By default, the widget makes the whole HTML page a drop-target for files, meaning you can drag a file onto the page and it will immediately be uploaded to your Lift application.

Discussion

In this recipe, we’ve shown just the basic integration of the widget with a Lift application. The demo site for the widget shows other capabilities, and provides documentation on how to integrate them.

Many of the features just require JavaScript configuration. For example, we’ve used the widget’s add, progressall, and done handlers to show, update, and then fade out a progress bar. When the upload is completed, the name of the uploaded file is appended to the page.

In the REST service, the uploaded files are available via the uploadedFiles method on the request. When Lift receives a multipart form, it automatically extracts files as uploadedFiles, each of which is a FileParamHolder that gives us access to the fileName, length, mimeType, and fileStream.

By default, uploaded files are held in memory, but that can be changed (see Discussion in File Upload).

In this recipe, we return a 200 (OkResponse). If we wanted to signal to the widget that a file was rejected, we can return another code. For example, perhaps we want to reject all files except PNG images. On the server-side we can do that by replacing the OkResponse with a test:

import net.liftweb.http.{ResponseWithReason, BadResponse, OkResponse}

if (req.uploadedFiles.exists( _.mimeType != "image/png" ))
  ResponseWithReason(BadResponse(), "Only PNGs")
else
  OkResponse()

We would mirror this with a fail handler in the client JavaScript:

fail: function (e, data) {
  alert(data.errorThrown);
}

If we uploaded, say, a JPEG, the browser would show an alert dialog reporting “Only PNGs.”

See Also

Diego Medina has posted a Gist of Lift REST code to integrate more fully with the image upload and image reviewing features of the widget, specifically implementing the JSON response that the widget expects for that functionality.

File Upload describes the basic file upload behaviour of Lift and how to control where files are stored.

Antonio Salazar Cardozo has posted example code for performing Ajax file upload using Lift’s Ajax mechanisms. This avoids external JavaScript libraries.

Format a Wired Cell

Problem

You want a wired UI element to have a different format than plain conversion to a string. For example, you’d like to display a floating-point value as a currency.

Solution

Use the WiringUI.toNode method to create a wiring node that can render the output formatted as you desire.

As an example, consider an HTML template to display the quantity of an item being purchased and the subtotal:

<div data-lift="Wiring">

<table>
  <tbody>
    <tr><td>Quantity</td><td id="quantity">?</td></tr>
    <tr><td>Subtotal</td><td id="subtotal">?</td></tr>
  </tbody>
</table>

<button id="add">Add Another One</button>

</div>

We’d like the subtotal to display as US dollars. The snippet would be:

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)

}

We have defined a currency method to format the subtotal not as a Double but as a currency amount using the Java number-formatting capabilities. This will result in values like “$19.90” being shown rather than “19.9.”

Discussion

The primary WiringUI class makes it easy to bind a cell as text. The asText method works by converting a value to a String and wrapping it in a Text node. This is done via toNode, however, we can use the toNode method directly to generate a transform function that is both hooked into the wiring UI and uses our code for the translation of the item.

The mechanism is type-safe. In this example, cost is a Double cell, quantity is an Int cell, and subtotal is inferred as a Cell[Double]. This is why our formatting function is passed value as a Double.

Note that the function passed to toNode must return a NodeSeq. This gives a great deal of flexibility as you can return any kind of markup in a NodeSeq. Our example complies with this signature by wrapping a text value in a Text object.

The WiringUI.toNode requires a (T, NodeSeq) => NodeSeq. In the previous example, we ignore the NodeSeq, but the value would be the contents of the element we’ve bound to. Given the input:

<td id="subtotal">?</td>

this would mean the NodeSeq passed to us would just be the text node representing “?”. With a richer template we can use CSS selectors. For example, we can modify the template:

<td>Subtotal</td><td id="subtotal">
  <i>The value is <b class="amount">?</b></i>
</td>

Now we can apply a CSS selector to change just the amount element:

(value, ns) => (".amount *" #> Text(formatter format value)) apply ns)

See Also

Chapter 6 of Simply Lift describes Lift’s Wiring mechanism, and gives a detailed shopping example.

Get Lift Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.