Good style for CSS binding with Ajax

92 views
Skip to first unread message

David Brooks

unread,
Jan 26, 2011, 7:37:55 AM1/26/11
to lif...@googlegroups.com
Hi all,

(This is a reasonably long one I'm afraid.)

Since moving over to use the new designer-friendly templates and CSS binding, I've found myself searching for an elegant replacement for my old pattern of Ajax programming with bind/chooseTemplate. (Caveat: my original pattern was not necessarily elegant, but it got less elegant with CSS binding.) I'm looking for some experience/advice on style when working with these things.

Essentially, when performing a full-page render life is good: a template is passed to the snippet and can be transformed into the final output (X)HTML. In contrast, when re-rendering with Ajax, you don't don't have an input template to transform, so you have to get to it somehow. My pre-CSS-binding approach had been to select and cache parts of the template, either in a 'var' (as here) or a closure:

private var cache = Empty
def render(in: NodeSeq) = {
cache = Full(chooseTemplate("rerenderable", "content", in))
...
}

then I could get it back out for Ajax re-rendering:

def rerender = {
JqId("abc") ~> JqHtml(cache.openOr(Nil))
}

I don't know if this is a good pattern, but it becomes harder when using the CSS binding, because there is no CssBind that performs the caching. I've included my first-pass at the problem attached below, but due to the hoop-jumping it entailed, I thought I'd get some advice from the experts first.

So my question is: what's the "right" way to get Ajax re-rendering working using CssBinds?

Cheers,
D

== Attempt at caching for Ajax re-rendering ==

To allow me to chain my caches into CssBind statements, I subclassed CssBindImpl to provide a cache-or-pass-thru:

/**
* A temporary cache for templates, so that the template can be
* cached when invoked and retrieved for later Ajax re-rendering.
*/
case class CacheThru(sel: String) extends CssBindImpl(Full(sel), CssSelectorParser.parse(sel)) {
private var cache: Box[NodeSeq] = Empty
def template = cache
// when the content is first visited, cache it;
// otherwise, pass it straight through
def calculate(in: NodeSeq): Seq[NodeSeq] = if(cache.isEmpty) {
cache = Full(in)
in
} else {
in
}
}

I can then use the above in a chain as follows:

private val cache = new CacheThru(".my-template-css")

def render(in: NodeSeq) = {
// caches or passes thru
cache & ...
}

then I could get it back out for Ajax re-rendering:

def rerender = {
JqId("abc") ~> JqHtml(cache.template.openOr(Nil))
}

It's neater than try to chain functions into CssBinds, but I haven't really played with it enough to know the downsides.

David Pollak

unread,
Jan 26, 2011, 9:14:51 AM1/26/11
to lif...@googlegroups.com
I think this is a huge issues and I've struggled with it myself.

You email prompted an idea:

object MySnip {

  def render = SHtml.memoize {"#Foo" #> bar}
}

def memoize(transform: => NodeSeq => NodeSeq): NodeSeq => NodeSeq with MemoizedTransform

trait MemoizedTransform {
  /**
  * Run the transform again against the most recently passed NodeSeq
  */
  def applyAgain(): NodeSeq
}

So what will happen is that MemoizedTransform will capture the most recently applied (for the scope of the Request) NodeSeq so in your ajax call, you simply say "applyAgain()" and voila, you get the transform applied to the memoized view.

Sound good?



--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.




--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Blog: http://goodstuff.im
Surf the harmonics

David Brooks

unread,
Jan 26, 2011, 10:55:56 AM1/26/11
to lif...@googlegroups.com

On 26 Jan 2011, at 14:14, David Pollak wrote:

> I think this is a huge issues and I've struggled with it myself.
>
> You email prompted an idea:
>
> object MySnip {
>
> def render = SHtml.memoize {"#Foo" #> bar}
> }
>
> def memoize(transform: => NodeSeq => NodeSeq): NodeSeq => NodeSeq with
> MemoizedTransform
>
> trait MemoizedTransform {
> /**
> * Run the transform again against the most recently passed NodeSeq
> */
> def applyAgain(): NodeSeq
> }
>
> So what will happen is that MemoizedTransform will capture the most recently
> applied (for the scope of the Request) NodeSeq so in your ajax call, you
> simply say "applyAgain()" and voila, you get the transform applied to the
> memoized view.
>
> Sound good?

It does indeed! Just to double-check: do I call render.applyAgain() to get out the original template, or to re-render that portion using the original template as a starting point? I'm assuming the latter, so could I also do:

def render = "#left" #> renderLeft & "#right" #> renderRight
def renderLeft = SHtml.memoize { ... } // cache #left template
def renderRight = SHtml.memoize { ... } // cache #right template
def rerender = JqId("left") ~> JqHtml(renderLeft.applyAgain())

thereby caching bits of the page that I might need to render individually? Looks like an excellent addition either way, but being able to do it for any sub-part of the page would be super-cool.

Cheers,
D

David Pollak

unread,
Jan 26, 2011, 11:27:59 AM1/26/11
to lif...@googlegroups.com
On Wed, Jan 26, 2011 at 7:55 AM, David Brooks <davidjam...@gmail.com> wrote:

On 26 Jan 2011, at 14:14, David Pollak wrote:

> I think this is a huge issues and I've struggled with it myself.
>
> You email prompted an idea:
>
> object MySnip {
>
>  def render = SHtml.memoize {"#Foo" #> bar}
> }
>
> def memoize(transform: => NodeSeq => NodeSeq): NodeSeq => NodeSeq with
> MemoizedTransform
>
> trait MemoizedTransform {
>  /**
>  * Run the transform again against the most recently passed NodeSeq
>  */
>  def applyAgain(): NodeSeq
> }
>
> So what will happen is that MemoizedTransform will capture the most recently
> applied (for the scope of the Request) NodeSeq so in your ajax call, you
> simply say "applyAgain()" and voila, you get the transform applied to the
> memoized view.
>
> Sound good?

It does indeed! Just to double-check: do I call render.applyAgain() to get out the original template, or to re-render that portion using the original template as a starting point? I'm assuming the latter, so could I also do:

 def render = "#left" #> renderLeft & "#right" #> renderRight
 def renderLeft = SHtml.memoize { ... } // cache #left template
 def renderRight = SHtml.memoize { ... } // cache #right template
 def rerender = JqId("left") ~> JqHtml(renderLeft.applyAgain())

Yeah... that's pretty much the way it'll work.
 

thereby caching bits of the page that I might need to render individually? Looks like an excellent addition either way, but being able to do it for any sub-part of the page would be super-cool.

Cheers,
D

--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.

David Pollak

unread,
Jan 26, 2011, 11:28:24 AM1/26/11
to lif...@googlegroups.com
Oh... and please open a ticket on this.


--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.

David Brooks

unread,
Jan 26, 2011, 11:43:34 AM1/26/11
to lif...@googlegroups.com
On 26 Jan 2011, at 16:28, David Pollak wrote:

> Oh... and please open a ticket on this.

Done - #866

I couldn't find any more useful assignment to features than "All" (is this common or util?), and wasn't sure if you wanted it assigned to you, but I'll happily go back and correct it.

https://www.assembla.com/spaces/liftweb/tickets/866-shtml-memoize--caching-templates-for-ajax-re-rendering

Cheers,
D

Reply all
Reply to author
Forward
0 new messages