Up: Chapter 6

6.2 Hooking it up to the UI

Now that we can declare relationships among cells, how do we associate the value of Cells with the user interface?
Turns out that it’s pretty simple:
 "#total" #> WiringUI.asText(total)
We associate the element with id="total" with a function that displays the value in total. Here’s the method definition:
  /**
   * Given a Cell register the
   * postPageJavaScript that will update the element with
   * a new value.
   *
   * @param cell the cell to associate with
   * 
   * @return a function that will mutate the NodeSeq (an id attribute may be added if
   * there’s none already defined)
   */
  def asText[T](cell: Cell[T]): NodeSeq => NodeSeq = 
Huh? that’s a lot of mumbo-jumbo... what’s a postPageJavaScript?
So, here’s the magic of WiringUI: Most web frameworks treat a page rendering as an event in time. Maybe (in the case of Seaside), there are some side effects of rendering that close over page rendering state such that when forms are submitted back, you get page state back. Lift treats a full HTML page render and subsequent Ajax requests on the page as a single event that has a single scope. This means that RequestVars populated during a page render are available during subsequent Ajax requests on that page. Part of the state that results in a page render is the postPageJavaScript which is a bucket of () => JsCmd or a collection of functions that return JavaScript. Before responding to any HTTP request associated with the page, Lift runs all these functions and appends the resulting JavaScript to the response sent back to the browser. HTTP requests associated with the page include the initial page render, subsequent Ajax request associated with the page and associated Comet (long poll) requests generated by the page.
For each Cell that you wire up to the user interface, Lift captures the id of the DOM node (and if there’s no id, Lift will assign one) and the current value of the Cell. Lift generates a function that looks at the current Cell value and if it’s changed, Lift generates JavaScript that updates the DOM node with the Cell’s current value.
The result is that if an Ajax operation changes the value of a ValueCell, then all the dependent cells will update and the associated DOM updates will be carried back with the HTTP response.
You have a lot of control over the display of the value. The asText method creates a Text(cell.toString). However, WiringUI.apply allows you to associate a function that converts the Cell’s type T to a NodeSeq. Further, you can control the transition in the browser with a jsEffect (type signiture (String, Boolean, JsCmd) => JsCmd). There are pre-build jsEffects based on jQuery including my favorite, fade:
  /**
   * Fade out the old value and fade in the new value
   * using jQuery fast fade.
   */
  def fade: (String, Boolean, JsCmd) => JsCmd = {
    (id: String, first: Boolean, cmd: JsCmd) => {
      if (first) cmd
      else {
        val sel = "jQuery(’#’+"+id.encJs+")"
        Run(sel+".fadeOut(’fast’, function() {"+
            cmd.toJsCmd+" "+sel+".fadeIn(’fast’);})")
      }
    }
  }
Which you can use as:
 "#total" #> WiringUI.asText(total, JqWiringSupport.fade)
Now, when the total field updates, the old value will fade out and the new value will fade in... cool.
Up: Chapter 6

(C) 2012 David Pollak