[ANN] reactive-web

146 views
Skip to first unread message

Naftoli Gugenheim

unread,
Jan 24, 2011, 3:10:07 AM1/24/11
to Scala User List, liftweb
With gratitude to the One Who gives man knowledge and understanding, I am happy to announce a project I've been working on: reactive-web.

Reactive-web is new RIA framework, making it easy to write highly interactive web applications, similar to GWT, Flex, etc. It uses the Functional Reactive Programming library reactive, and requires Lift.

It allows your webapp to interact with the browser objects as if they were regular Scala objects inside the JVM, using FRP abstractions such as Event Streams and Signals. You can handle events from DOM objects in Scala code, and you can update DOM objects by mutating the corresponding object in Scala. More importantly though, you can define your view declaratively so that it will be updated automatically.

Updates to the browser in response to events that originated from the browser are returned from the same ajax request that the event was sent through. Events that originate from stimuli on the server can be sent through Lift's superb comet support.

Also, reactive has support for incremental updates to Seqs. So you have a BufferSignal, transform it in all sorts of ways, and render it via a Repeater element, and when the BufferSignal changes only the changes to the DOM will be rendered.

You can use it as a new coding style in regular Lift applications, for one page or your whole application; or you can use it to completely change the kind of webapps you write. You can even use it to build a rich desktop app, using an embedded Jetty instance.

It's at http://github.com/nafg/reactive. Note that it's a work in progress; there is still much to be done, but more in terms of completeness than in terms of plumbing.

Here is a snippet from the demo application. You can run it by cloning the repository (or just downloading the source), typing sbt "project demo" jetty from the project folder, and opening your browser to http://localhost:8080. You will see a text field whose contents are "bound" to the contents of a span, another span that's updated with the current time, and a row of numbers that randomly get added or removed.


package reactive
package web
package snippet

import _root_.scala.xml._

import net.liftweb.util.{Helpers, BindPlus}
  import Helpers._
  import BindPlus._
import net.liftweb.http._




// Among other things, ReactiveSnippet has an implicit
// val, currentPage, that among other things extends Observing.
// Thus any listeners we have can be garbage collected (only) once
// the snippet is garbage collected.
class MainPage extends ReactiveSnippet {
  //////////////////////////////////////////////////////////////////
  // DEMONSTRATE REACTIONS TO CLIENT EVENTS
  //////////////////////////////////////////////////////////////////
  
  // Create a reactive text input. By default its value is updated
  // when the browser fires a change event
  val field = TextInput()
  
  // Link the value property with the keyUp event
  // so that it's updated on every keyUp event
  field.value updateOn field.keyUp
  
  // Set its width
  field.size.value ()= 80

  
  // Create a Signal that binds the field's value
  // Its value will be kept up to date automatically
  val fieldValue = field.value.value map {v =>
    "*" #> Text(v)
  }
  
  // Create a NodeSeq=>NodeSeq that renders fieldValue
  // reactively in whatever element Cell is binded to.
  val cell = Cell(fieldValue)
  
  
  //////////////////////////////////////////////////////////////////
  // DEMONSTRATE REACTIONS TO SERVER EVENTS
  //////////////////////////////////////////////////////////////////
  
  // Create an EventStream that fires timer ticks until
  // the page is no longer alive
  val clockES = new Timer(interval = 2000, cancel = ()=> !isPageAlive)
  
  // Create a signal from the EventStream whose value, until
  // the first tick is received, is 0L
  val clockSig = clockES.hold(0L)
  
  // Create a reactive Span Cell that displays the time in a scala.xml.Text
  val clockSpan = Span(clockSig.map(t => Text((t/1000).toString)))
  

  //////////////////////////////////////////////////////////////////
  // DEMONSTRATE DELTA UPDATES
  //////////////////////////////////////////////////////////////////
  
  // Create an empty BufferSignal[Int]
  val items = BufferSignal[Int]()
  
  // Create a Repeater that binds each element in items
  // to the html element with id="number"
  // As numbers are inserted and removed from the BufferSignal,
  // the corresponding html elements will be inserted and removed.
  // This particular factory returns a NodeSeq=>NodeSeq that can
  // be used in Lift binding.
  val repeater: NodeSeq=>NodeSeq = Repeater {
    items.map{
      _ map { i =>
        ("#number" #> i) : (NodeSeq=>NodeSeq)
      }
    }
  }
  
  // Count from 1 to infinity
  var numbers = Stream.from(1)

  // On each clock tick do an insert or remove
  for(tick <- clockES) {
    println("Clock firing: " + tick)
    // If items is empty then always do an append
    // Otherwise do either an append or a remove,
    // depending on the value of math.random
    if(items.now.isEmpty || math.random > .4) {
      // Get the first number in the Stream and append it to items
      items.value += numbers.head
      // And point to the rest of the Stream
      numbers = numbers.tail
    } else {
      // Remove a random element from items
      items.value.remove(math.random*items.now.length toInt)
    }
  }
  
  
  /**
   * The snippet function
   */
  def render =
    "#field" #> field &
    "#span" #> cell &
    "#clock" #> clockSpan &
    "#div" #> repeater
}


/**
 * A bit more declarative
 */
class MainPage2 extends MainPage {
  override def render =
    "#field" #> field &
    "#span" #> Cell {
      field.value.value map {v => "*" #> Text(v) }
    } &
    "#clock" #> Span {
      clockSig map {t => Text(t/1000 toString)}
    } &
    "#div" #> Repeater {
      items map {
        _ map { i => ("#number" #> i) : (NodeSeq=>NodeSeq)}
      }
    }
}

Noel Welsh

unread,
Jan 24, 2011, 7:03:20 AM1/24/11
to lif...@googlegroups.com, Scala User List
I love FRP. I can't wait to play with this!

N.

On Mon, Jan 24, 2011 at 8:10 AM, Naftoli Gugenheim <nafto...@gmail.com> wrote:
> With gratitude to the One Who gives man knowledge and understanding, I am
> happy to announce a project I've been working on: reactive-web.
> Reactive-web is new RIA framework, making it easy to write highly
> interactive web applications, similar to GWT, Flex, etc. It uses the

> Functional Reactive Programming library reactive, and requires Lift....
--
Noel Welsh
Untyped Ltd                 http://www.untyped.com/
UK company number    06226466

Lukasz Kuczera

unread,
Jan 24, 2011, 12:56:10 PM1/24/11
to Lift
FRP took my attention recently. There's great paper by Odersky et al.
http://infoscience.epfl.ch/record/148043

On Jan 24, 1:03 pm, Noel Welsh <n...@untyped.com> wrote:
> I love FRP. I can't wait to play with this!
>
> N.
>

Naftoli Gugenheim

unread,
Jan 24, 2011, 10:09:25 PM1/24/11
to Ittay Dror, Scala User List, liftweb
You can use it with regular Lift templates. The example I posted uses a Lift template but has the gui a bit more explicit than necessary.
Specifically, there are two generic ways to fill an html element dynamically: Cell, for elements whose contents change as one, and Repeater, for "rubber-stamped" content, where the DOM should only modified incrementally -- if a new contact gets added to the database then a corresponding element is inserted in the correct place; same for deletions and replaces. (Multiple edits are batched, and you can generate the deltas via a diffing algorithm if necessary.)
From the sample I posted:


    "#span" #> Cell {
      field.value.value map {v => "*" #> Text(v) }
    }

means that when whenever the value of the field's value property changes, the element in the template with id="span" is updated by running the contents of that element from the template through the css selector expression "*" #> Text(v) where v is the current value of the field.
Have to run, plan to continue this message...

On Mon, Jan 24, 2011 at 4:07 AM, Ittay Dror <ittay...@gmail.com> wrote:
Looks nice, but is there a way to define wicket/lift style templates? I really don't like defining gui in the code.

Ittay

Naftoli Gugenheim

unread,
Jan 25, 2011, 1:41:14 AM1/25/11
to liftweb
Yes, a lot of things in reactive were influenced by Ingo's paper and work.


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


Naftoli Gugenheim

unread,
Jan 25, 2011, 2:05:05 AM1/25/11
to Ittay Dror, Scala User List, liftweb
On Mon, Jan 24, 2011 at 10:09 PM, Naftoli Gugenheim <nafto...@gmail.com> wrote:
You can use it with regular Lift templates. The example I posted uses a Lift template but has the gui a bit more explicit than necessary.
Specifically, there are two generic ways to fill an html element dynamically: Cell, for elements whose contents change as one, and Repeater, for "rubber-stamped" content, where the DOM should only modified incrementally -- if a new contact gets added to the database then a corresponding element is inserted in the correct place; same for deletions and replaces. (Multiple edits are batched, and you can generate the deltas via a diffing algorithm if necessary.)
From the sample I posted:

    "#span" #> Cell {
      field.value.value map {v => "*" #> Text(v) }
    }

means that when whenever the value of the field's value property changes, the element in the template with id="span" is updated by running the contents of that element from the template through the css selector expression "*" #> Text(v) where v is the current value of the field.
Have to run, plan to continue this message..

Anyway...
So the signature of that factory is Cell(binding: Signal[NodeSeq=>NodeSeq]): NodeSeq=>NodeSeq, which means that the signal's value is a binding function. Of course the signal can be composed from several other signals:
"#span" #> Cell {
  for {
    text <- field.value.value
    time <- clockSignalThatTicksEvery5Seconds
  } yield "*" #> ("You said " + text + " at " + time)
}
Note that every five seconds the span's contents will be replaced via comet, but whenever you edit the text field it will trigger an ajax request and the update javascript will be returned from that ajax request.

Similarly you can use Repeater with a gui defined in the template. Take the Repeater in the example code. Here is the relevant part of the template:
div id="div"><span> [[ <span id="number" /> ]] </span></div>

And in the declarative render function:
    "#div" #> Repeater {
      items map {
        _ map { i => ("#number" #> i) : (NodeSeq=>NodeSeq)}
      }
    }
To break this down: The Repeater factory returns a NodeSeq=>NodeSeq which is used in Lift CSS-selector-based binding (or old-style binding).
The factory takes in a SeqSignal[NodeSeq=>NodeSeq]. Since Items is a SeqSignal[Int], we call map to get a signal that transforms Ints into bind functions. However SeqSignal[Int] extends Signal[Seq[Int]], so we have to nest another call to map; the first maps the signal, the second maps the elements of the signal. Anyway, for each element i, for each value of the SeqSignal, we return a binding function that will operate on all of the div's contents. Thus the output will be something like  [[ 1 ]]  [[ 2 ]] etc.
So far no gui in code, do you agree?
I'm splitting the rest into another message in case my battery dies...

Naftoli Gugenheim

unread,
Jan 25, 2011, 2:17:59 AM1/25/11
to Ittay Dror, Scala User List, liftweb
Now, some things by their very nature need to be in code. For instance, do you have a better idea of how the TextInput should be defined? Without these "DOM proxies" how would you listen to events or property changes? How would you make a property have a dynamic value?
I haven't yet put in the class attribute, but one may want to write something like:
val ti = TextInput()
ti.classes.value ()= ti.value.value map {v => if(valid(v) Nil else List("invalid") }
for a text input that continuously shows its valid state.
I am all ears for ways to make the API better.
Reply all
Reply to author
Forward
0 new messages