10 Lift and JavaScript
In this chapter we’ll be discussing some of the techniques that Lift provides for simplifying and abstracting access to JavaScript on the client side. Using these facilities follows Lift’s model of separating code from presentation by allowing you to essentially write JavaScript code in Scala. Lift also provides a layer that allows you to use advanced JavaScript functionality via either the JQuery or YUI user interface libraries.
10.1 JavaScript high level abstractions
You may have noticed that Lift already comes with rich client side functionality in the form of AJAX and COMET support (chapter
11↓). Whenever you use this support, Lift automatically generates the proper
<script> elements in the returned page so that the libraries are included. Lift goes one step further, however, by providing a class hierarchy representing JavaScript expressions. For example, with an AJAX form element in Lift the callback method must return JavaScript code to update the client side. Instead of just returning a raw JavaScript string to be interpreted by the client, you return an instance of the
JsCmd trait (either directly or via implicit conversion) that is transformed into the proper JavaScript for the client.
JsCmd represents a JavaScript command that can be executed on the client. There is an additional “base” trait called JsExp that represents a JavaScript expression.The differences between them are not usually important to the developer, since a JsExp instance is implicitly converted to a JsCmd. Also note that while Lift’s JavaScript classes attempt to keep things type-safe there are some limitations; in particular, Lift can’t check semantic things like whether the variable you’re trying to access from a given JsCmd actually exists. Besides the obvious use in techniques like AJAX and COMET, Lift also makes it simple to attach JavaScript to regular Scala XML objects, such as form fields.
As a simple example, let’s look at how we might add a simple alert to a form if it doesn’t validate. In this example, we’ll assume we have a
name form field that shouldn’t be blank. Listing
10.1↓ shows a possible binding from our form snippet. Let’s break this down a bit: the first thing is that in order to reference form elements (or any elements for that matter) from JavaScript, they need to have an
id attribute. We add the id attribute
↓ to our text field by passing a
Pair[String,String]. Next, we need to define our actual validation. We do this by adding some javascript to the
onclick attribute of our submit button. The onclick attribute evaluates whatever javascript is assigned when the button is clicked; if the javascript evaluates to true then submission continues. If it evaluates to false then submission is aborted. In our case, we use the JsIf case class to check to see if the value of our myName field is equal to an empty string. In this case the JE object holds an implicit conversion from a Scala string to a Str (JavaScript string) instance. The second argument to JsIf is the body to be executed if the condition is true. In our case we want to pop up an alert to the user and stop form submission. The JsCmd trait (which Alert mixes in) provides a “&” operator which allows you to chain multiple commands together. Here we follow the Alert with a JsReturn, which returns the specified value; again, there’s an implicit conversion from Boolean to JsExp, so we can simply provide the “false” value.
import JsCmds._
import JE._
var myName = ""
bind(...
"name" -> text(myName, myName = _, "id" -> "myName"),
"submit" -> submit("Save", ..., "onclick" ->
JsIf(JsEq(ValById("myName"), ""),
Alert("You must provide a name") & JsReturn(false))
)
)
10.1.1 JsCmd and JsExp overview
If you peruse the Lift API docs you’ll find a large number of traits and classes under the JsCmds and JE objects; these provide the vast majority of the functionality you would need to write simple JavaScript code directly in Lift. Having said that, however, it’s important to realize that the Lift classes are intended to be used for small code fragments. If you need to write large portions of JavaScript code for your pages, we recommend writing that code in
pure JavaScript in an external file and then including that file in your pages. In particular, if you write your code as JavaScript functions, you can use the
JE.Call class to execute those functions from your Lift code. Table
10.1↓ gives a brief overview of the available JsCmds, while table
10.2↓ shows the JE expression abstractions.
After
|
Executes the given JsCmd fragment after a given amount of time
|
Alert
|
Corresponds directly to the JavaScript alert function
|
CmdPair
|
Executes two JsCmd fragments in order
|
FocusOnLoad
|
Forces focus on the given XML element when the document loads
|
Function
|
Defines a JavaScript function with name, parameter list, and JsCmd body
|
JsBreak, JsContinue, JsReturn
|
Corresponds directly to the JavaScript “break”, “continue”, and “return” keywords
|
JsFor, JsForIn, JsDoWhile, JsWhile
|
These define loop constructs in JavaScript with conditions and execution bodies
|
JsHideId, JsShowId
|
Hides or shows the HTML element with the given Id. This is actually handled via the LiftArtifacts’ hide and show methods
|
JsIf
|
Corresponds to the JavaScript “if” statement, with a condition, body to execute if the condition is true, and optional “else” body statement
|
JsTry
|
Defines a try/catch block tha can optionally alert if an exception is caught
|
JsWith
|
Defines a with statement to reduce object references
|
OnLoad
|
Defines a JavaScript statement that is executed on page load
|
Noop
|
Defines an empty JavaScript statement
|
RedirectTo
|
Uses window.location to redirect to a new page
|
ReplaceOptions
|
Replaces options on a form Select with a new list of options.
|
Run
|
Executes the given string as raw javascript
|
Script
|
Defines a <script> element with proper CDATA escaping, etc to conform to XHTML JavaScript support
|
SetElemById
|
Assigns a statement to a given element by id. Optional parameters allow you to specify properties on the element
|
SetExp
|
Defines an assignment to an arbitrary JavaScript expression from another JavaScript expression
|
SetHtml
|
Sets the contents of a given HTML node by Id to a given NodeSeq. This is especially useful in Ajax calls that update parts of the page
|
SetValById
|
Defines an assignment to a given element’s “value” property
|
Table 10.1 Basic JsCmds
AnonFunc
|
Defines an anonymous JavaScript function
|
Call
|
Calls a JavaScript function by name, with parameters
|
ElemById
|
Obtains a DOM element by its Id, with optional property access
|
FormToJson
|
Converts a given form (by Id) into a JSON representation
|
Id, Style, Value
|
Represents the “id”, “style” and “value” element attributes
|
JsArray
|
Constructs a JavaScript array from a given set of JavaScript expressions
|
JsEq, JsNotEq, JsGt, JsGtEq, JsLt, JsLtEq
|
Comparison tests between two JavaScript expressions. JsExp instances also have a “===” operator which is equivalent to JsEq
|
JsTrue, JsFalse, JsNull
|
Represents the “true”, “false”, and “null” values
|
JsFunc
|
Similar to Call; executes a JavaScript function
|
JsObj
|
Represents a JavaScript object with a Map for properties
|
JsRaw
|
Represents a raw JavaScript fragment. You can use this if Lift doesn’t provide functionality via abstractions
|
JsVal
|
Represents an abritrary JavaScript value
|
JsVar
|
Represents a JavaScript variable, with optional property access
|
Num
|
Represents a JavaScript number. JE contains implicit conversions from Scala numeric types to Num
|
Str
|
Represents a Javascript String. JE contains implicit conversions from a Scala String to Str
|
Stringify
|
Calls JSON.stringify to convert a JavaScript object into a JSON string representation
|
ValById
|
Represents the “value” property of a given element by Id
|
Table 10.2 Basic JE abstractions
10.1.2 JavaScript Abstraction Examples
As you can see, Lift provides a large coverage of JavaScript functionality through its abstraction layer. Even if you’ve done a lot of JavaScript, however, the abstractions don’t always map one-to-one and it can take some effort to wrap your head around it. We’re going to provide a few examples to help you understand how it works. We’ll start off with a simple example of an Ajax callback (Ajax is covered in chapter
11↓). Listing
10.1.2↓ shows how we can update an HTML element with new content via the Ajax call. In this case, we’re changing a chart image based on some passed parameters. Our HTML needs to contain an element with an id of “tx_graph”; this element will have its children
replaced with whatever NodeSeq we pass as the second argument.
def updateGraph() = {
val dateClause : String = ...
val url = "/graph/" + acctName + "/" + graphType + dateClause
JsCmds.SetHtml("tx_graph", <img src={url} />)
}
As a more complex example, we could add some JavaScript behavior combining Ajax with some client-side state, as shown in listing
10.1.2↓.
import js.JE._ // for implicit conversions
def moreComplexCallback (value : String) = {
JsIf(ValById("username") === value.toLowerCase, {
JsFunc("logAccess", "Self-share attempted").cmd & Alert("You can’t share with yourself!")
})
}
10.2 JQuery and other JavaScript frameworks
We’ve mentioned earlier that Lift uses the JQuery JavaScript framework by default. Lift wouldn’t be Lift, however, if it didn’t provide a mechanism for using other frameworks. The way that lift determines which JavaScript framework to use is via the JSArtifacts trait along with the LiftRules.jsArtifacts var. Lift comes with two default implementations of JSArtifacts: JQueryArtifacts and YUIArtifacts. If you want to use a different framework, you must provide a concrete implementation of the JSArtifacts trait specific to that framework. The JQuery support in Lift extends beyond just the JSArtifacts, support; there are also a number of JSExp and JsCmd traits and classes in the net.liftweb.http.js.jquery package that provide JQuery specific implementations for standard expressions and commands.
Changing one implementation or another can be done from LiftRules.jsArtifacts variable, which by default points to JQueryArtifacts. Typically this is done in Boot, as shown in listing
10.2↓.
import net.liftweb.http.js.yui.YUIArtifacts
class Boot {
def boot = {
...
LiftRules.jsArtifacts = YUIArtifacts
...
}
In addition to changing LiftRules, you also need to take into account that other frameworks have their own scripts and dependencies that you’ll need to include in your pages. For YUI you would need to include the following scripts (at minimum):
<script src="/classpath/yui/yahoo.js" type="text/javascript"/>
<script src="/classpath/yui/event.js" type="text/javascript"/>
<script src="/classpath/yui/dom.js" type="text/javascript"/>
<script src="/classpath/yui/connection.js" type="text/javascript"/>
<script src="/classpath/yui/json.js" type="text/javascript"/>
<script src="/classpath/liftYUI.js" type="text/javascript"/>
Of course, to keep things simple you could either place all of these items in a template that you could embed, or you could combine the files into a single JavaScript source file.
We have some simple recommendations on using different JavaScript frameworks from within Lift:
-
If you don’t necessarily need YUI widgets or if you can find similar functionality in JQuery plugins, we recommend using the JQuery framework. Lift provides much better support out-of-the-box for JQuery
-
Do not mix JQuery and YUI unless you really know what you are doing. Getting both of them together leads to a number of collisions.
10.3 XML and JavaScript
What we’ve covered so far is pretty much standard JavaScript behind some Lift facades. There are situations, however, when you want to do things that are complicated or outside the scope of typical JavaScript functionality. One example of this is when you need to build dynamic DOM elements from JavaScript code, say to build an HTML list. Lift has a very nice way of dealing with such situation; with a few lines of code you can achieve quite a lot. The main functionality for this is provided via the Jx* classes, which you can use to transform a scala.xml.NodeSeq into javascript code that generates the corresponding nodes on the client side. Listing
10.3↓ shows a simple example of emitting a div on a page via JavaScript.
import net.liftweb.http.js._
import JE._
val div = Jx(<div>Hi there</div>)
This code generates the following JavaScript code:
function(it) {
var df = document.createDocumentFragment();
var vINIJ1YTZG5 = document.createElement(’div’);
df.appendChild(vINIJ1YTZG5);
vINIJ1YTZG5.appendChild(document.createTextNode(’Hi there’));
return df;
}
As you can see, Lift took our XML code and transformed it into a JavaScript function that dynamically creates a document fragment containing the given NodeSeq. The it parameter can be any JavaScript object; we’ll cover how you use it in a moment. The name of the var is automatically and randomly generated to ensure uniqueness.
Of course, if that was all Lift was doing that’s not much help. At this point we’ve only generated a function that generates XML. Let’s take a look on a more complex example that shows the real power of the Jx classes. Assume we have a JSON structure that contains an array of objects containing firstName and lastName properties. This JSON structure could look something like:
var list = {
persons: [
{name: "Thor", race: "Asgard"},
{name: "Todd", race: "Wraith"},
{name: "Rodney", race: "Human"}
]
}
// Guess what I’ve been watching lately ?
Now we can use a combination of Jx classes to render this content as an HTML dynamic list:
Rendering a JSON List Via Jx
def renderPerson =
Jx(<li class="item_header"> {JsVar("it", "name")}
is {JsVar("it", "race")}</li>)
Jx(<ul>{JxMap(JsVar("it.persons"), renderPerson)}</ul>)
Well what this code does is this:
-
Construct an <ul> list that contains a bunch of elements
-
JxMap takes a JavaScript object, in this case it.persons (remember it is the parameter of the generated function), and iterate for each element of the array and apply the renderPerson function. Of course each element of the array will be a JSON object containing name and race properties.
-
The renderPerson function generates a JavaScript function as we’ve already shown, and renders the JavaScript code that generates the <li> elements containing the name value followed by “is” followed by the race value.
-
If we send this generated JavaScript function to client and calling it by pass the list variable above It will create the following document fragment:
<ul>
<li class="item_header">Thor is Asgard</li>
<li class="item_header">Todd is Wraith</li>
<li class="item_header">Rodney is Human</li>
</ul>
With a couple of lines of code we’ve managed to generate the JavaScript code that creates document fragments dynamically. Here is the list of JX classes that you may find interesting:
Class
|
Description
|
JxBase
|
The parent trait for all other Jx classes
|
JxMap
|
Iterates over a JavaScript array and applies a function on each element
|
JxMatch
|
Match a JsExp against a sequence of JsCase
|
JxCase
|
Contains a JsExp for matching purposes and the NodeSeq to be applied in case the matching succeeds
|
JxIf
|
Contains a JsExp and a NodeSeq to be applied only if JsExp is evaluated to true
|
JxIfElse
|
Similar with JxIf but it contains the else branch
|
Jx
|
The basic application of the transformation from a NodeSeq to the JavaScript code
|
JSON is a way of structuring information in JavaScript code. One of its most common uses is to represent structured information on the wire. One example would be a JavaScript AJAX API where the server response is in fact a JSON construct. Let’s look at an example first in listing
10.4↓:
class SimpleSnippet {
def ajaxFunc() : JsCmd = {
JsCrVar("myObject", JsObj(("persons", JsArray(
JsObj(("name", "Thor"), ("race", "Asgard")),
JsObj(("name", "Todd"), ("race", "Wraith")),
JsObj(("name", "Rodney"), ("race", "Human"))
)))) & JsRaw("alert(myObject.persons[0].name)")
}
def renderAjaxButton(xhtml: Group): NodeSeq = {
bind("ex", xhtml,
"button" -> SHtml.ajaxButton(Text("Press me"), ajaxFunc _))
}
}
Your template would look like listing
10.4↓:
...
<lift:SimpleSnippet.renderAjaxButton>
<ex:button/>
</lift:SimpleSnippet.renderAjaxButton>
...
First off, we have a simple snippet function called renderAjaxButton. Here we’re binding the ex:button tag and render a XHTML button tag that when pressed will send an Ajax request to server. When this request is received, the ajaxFunc is executed and the JsCmd response is turned into a JavaScript content type response. In ajaxFunc we construct a JSON object (the same one we used previously for the persons object). We assign the JSON structure to the JavaScript variable myObject and them call alert on the first element on the persons object. The rendered JavaScript code that will be send down the wire will be:
var myObject = {’persons’: [{’name’: ’Thor’, ’race’: ’Asgard’},
{’name’: ’Todd’, ’race’: ’Wraith’} ,
{’name’: ’Rodney’, ’race’: ’Human’}]};
alert(myObject.persons[0].name);
So in your page when you press the button you’ll get an alert dialog saying “Thor”. Here we used the JsRaw class which basically renders the exact thing you passed to it: raw JavaScript code.
Now that we’ve covered sending JSON from the server to the client, let’s look at going in the opposite direction. Lift provides a mechanism for sending form data to the server encapsulated in a JSON object. In and of itself sending the data in JSON format is relatively simple; where Lift really adds value is via the JsonHandler class. This class provides a framework for simplifying processing of submitted JSON data. To start, let’s look at some example template code for a JSON form:
<lift:surround with="default" at="content">
<lift:JSONForm.head />
<lift:JSONForm.show>
<input type="text" name="name" />
<br />
<input type="text" name="value" />
<br />
<input type="radio" name="vehicle" value="Bike" />
<input type="radio" name="vehicle" value="Car" />
<input type="radio" name="vehicle" value="Airplane" />
<br />
<select name="cars">
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
<option value="opel">Opel</option>
<option value="audi">Audi</option>
</select>
<button type="submit">Submit</button>
</lift:JSONForm.show>
<div id="json_result"></div>
</lift:surround>
A you can see, the XHTML template is relatively straightforward. The Snippet code is where things really get interesting:
JSON Form Snippet Code
class JSONForm {
def head =
<head>
<script type="text/javascript"
src={"/" + LiftRules.resourceServerPath + "/jlift.js"} />
{Script(json.jsCmd)}
</head>
def show(html: Group): NodeSeq = {
SHtml.jsonForm(json, html)
}
import JsCmds._
object json extends JsonHandler {
def apply(in: Any): JsCmd = SetHtml("json_result", in match {
case JsonCmd("processForm", _, p: Map[String, _], _) => {
// process the form or whatever
println("Cars = " + urlDecode(p("cars")))
println("Name = " + urlDecode(p("name")))
<b>{p}</b>
}
case x => <b>Problem... didn’t handle JSON message {x}</b>
})
}
}
The first thing we define is the head function. Its purpose is simply to generate the JavaScript functions that set up the form handling on the client side. That means that when the submit button is clicked, the contents of the form are turned into JSON and submitted via an Ajax call to the server. The show function defines the connection between the concrete JsonHandler instance that will process the form and the template HTML that contains the form. We perform this binding with the SHtml.jsonForm method. This wraps the HTML with a <form> tag and sets the onsubmit event to do JSON bundling.
The key part of the equation is our JsonHandler object. The apply method is what will be called when the JSON object is submitted to the server. If the JSON is properly parsed then you’ll get a JsonCmd instance which you can use Scala’s matching to pick apart. The apply function needs to return a JsCmd (JavaScript code), which in this case sets the HTML content of the json_result div element. When the form is stringified into its JSON representation Lift uses a command property indicating the action that needs to be done on server and the actual JSON data. In the case of JSON forms the command is always “processForm” as this is important for pattern matching as seen above. The actual form content is a Map object that can be easily use to obtain the values for each form field.
10.5 JqSHtml object
SHtml generated code is independent on the JavaScript framework used. However
net.liftweb.http.jquery.JsSHtml object contains artifacts that are bound with JQuery framework. For instance it contains the autocomplete function that renders an input type text element but when start typing it will suggest words starting with what you typed already. Please see
http://www.pengoworks.com/workshop/jquery/autocomplete.htm for examples.
We’ve seen so far how we can abstract JavaScript code at Scala level using Lift’s JS abstraction. You can model endless cases by using these abstractions. But let’s take a look on another example a bit more complex. It is about a fast search where you have a text box and when you hit enter it will return the list of items that contain that sequence. The list of items will be rendered in a DIV real estate.
Example template
<lift:surround with="default" at="content">
<lift:Hello.ajaxian>
<text:show/>
</lift:Hello.ajaxian>
<div id="items_list" style="width: 300px; height: 100px; overflow: auto; border: 1px solid black;">
</div>
</lift:surround>
So we just have a really simple snippet and the div placeholder.
Example snippet
import JE._
import net.liftweb.http.js.jquery.JqJE._
import net.liftweb.http.SHtml._
import net.liftweb.util.Helpers._
import JsCmds._
val names = "marius" :: "tyler" :: "derek" :: "dave" :: "jorge" :: "viktor" :: Nil
def ajaxian(html: Group) : NodeSeq = {
bind("text", html,
"show" -> ajaxText("Type something", {value => {
val matches = names.filter(e => e.indexOf(value) > -1)
SetHtml("items_list", NodeSeq.Empty) &
JsCrVar("items", JsArray(matches.map(Str(_)):_*)) &
JsCrVar("func", Jx(<ul>{
JxMap(JsVar("it"), Jx(<li><a href="">{JsVar("it")}</a></li>)) } </ul>).toJs) &
(ElemById("items_list") ~> JsFunc("appendChild", Call("func", JsVar("items"))))
}})
)
}
The part with the snippet is probably already familiar to you. We are calling the ajaxText function which renders an input text element. When you hit enter an Ajax request will be sent and the anonymous function that we bound here will be executed. Here is what happens:
-
First filter out the names that contain the provided value in the input text. So all element that contain that sequence.
-
Then return a JsExp that we are building:
-
SetHtml is clearing out the div element that we’re using as a real estate for our search results list
-
Then we re declaring a JavaScript variable which is an array containing the resulting items that matched the search criteria.
-
Then we are declaring thr func variable which obviously is a function. We’ve seen above how to use the Jx artifacts. Now we are building a html list (<ul>) that for each element from the it variable will build the <li> sequences. The it variable is actually the paramter that this function takes which is the items array that we declared above.
-
After that we are obtaining the HTML node denominated by “items_list” id and call appendChild function of the Node object. The ~> function is use to call functions of objects. Of course to the appendChild function we need to provide a parameter. This parameter is the document fragment returned by func function. When we are caling the func function we are passing items variable decalred above.
As you noticed already we composed a small JavaScript code by chainin multiple JS expressions/commands using the & function.
(C) 2012 Lift 2.0 EditionWritten by Derek Chen-Becker, Marius Danciu and Tyler Weir