Lift 2.3 has a new feature: SHtml.idMemoize.  Basically, it lets you use Lift CSS Selector Transforms yet capture the template that was run through the transform such that the you can re-render the HTML in a given Element without having to explicitly load the template that was used to render the page.

Let's take a look at an example, let's say you have some HTML:

      <div class="lift:ShowMemoize">
        <button name="refresh_all">Refresh All</button>

        <div name="one">
          <span>The current time is: </span>
          <button>Click Me</button>
        </div>

        <div name="two">       
          <button>Update this</button>
          <ul>
            <li>Count: </li>
          </ul>
        </div>
      </div>

Basically, a <div> that contains two sub-<div>s.  We want to be able to click on the buttons and have either or both of the <div>s updated (depending on which button we press).  Let's look at the Lift snippet that allows you to redraw part or all of the above HTML:

object ShowMemoize {
  def render =
    "div" #> SHtml.idMemoize(
      outer =>
      // redraw the whole div when this button is pressed
      "@refresh_all [onclick]" #> SHtml.ajaxInvoke(outer.setHtml _) &

      // deal with the "one" div
      "@one" #> SHtml.idMemoize(
        one =>
          "span *+" #> now.toString & // display the time
        "button [onclick]" #> SHtml.ajaxInvoke(one.setHtml _)) & // redraw

      // deal with the "two" div
      "@two" #> SHtml.idMemoize(
        two => // the "two" div
          // display a bunch of items
          "ul *" #> (0 to randomInt(6)).map(i => "li *+" #> i) &
        // update the "two" div on button press
        "button [onclick]" #> SHtml.ajaxInvoke(two.setHtml _)))
}

This code does the following:

  • Selects the outer <div>
  • SHtml.idMemoize is a method that takes IdMemoizeTransform => NodeSeqFuncOrSeqNodeSeqFunc as a parameter.  That type signature may look weird, so let's break it down:
    • It's a function that takes an IdMemoizeTransform as an input and
    • returns a NodeSeqFuncOrSeqNodeSeqFunc.  "What's a NodeSeqFuncOrSeqNodeSeqFunc?" you ask.  It's a NodeSeq => NodeSeq or a Seq[NodeSeq => NodeSeq]... it's basically what you get with a CSS Selector Transform... and there's an implicit conversion from either NodeSeq => NodeSeq or Seq[NodeSeq => NodeSeq] to NodeSeqFuncOrSeqNodeSeqFunc.
    • Basically, it's a function that takes the IdMemoizeTransform and returns a CSS Selector Transform
  • We capture the outer variable which is the IdMemoizeTransform and the do our normal CSS Selector Transform with it
  • We set the onclick attribute of the <button> with id refresh_all to an Ajax invocation of the outer.setHtml() method.  So, when the button is clicked, the body of the outer <div> will be redrawn based on the CSS Selector Transform and the template that was used in the transform.
  • Each of the inner <div>s get the same treatment and each has a <button> that will redraw just that part of the screen
So, the snippet does not have to explicitly know the template passed in... it will do the right thing no matter the template.  You can even have two invocations of the same snippet on the same page with different templates and SHtml.idMemoize will "do the right thing" with the templates.  This makes it easy to build Ajax applications that update the screen real estate the right way.

You can see an example at https://github.com/dpp/starting_point/tree/idMemoize