Tutorial
Getting Started
Reactive depends on jQuery and Underscore:
<script src='//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js'></script>
<script src='//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js'></script>
Reactive is hosted on cdnjs as well, so you can source directly from there:
<script src='//cdnjs.cloudflare.com/ajax/libs/reactive-coffee/1.2.2/reactive-coffee.min.js'></script>
If you're using Bower, you can also add Reactive as a dependency for your project:
bower install --save reactive-coffee
If you're using node (or browserify, etc.), you can install via NPM:
npm install --save reactive-coffee
This will transitively pull in the above dependencies for Reactive (temp. note: bower has issues solving dependency version constraints).
If you are not using server-side CoffeeScript compilation (recommended for production), you can get started quickly with client-side compilation:
<script src='//cdnjs.cloudflare.com/ajax/libs/coffee-script/1.6.3/coffee-script.min.js'></script>
<script type="text/coffeescript">
# Your code goes here!
</script>
To follow along with these examples and use reactive HTML element tags
without having to use their fully qualified name in rx.rxt.tags
, include
this at the top of your CoffeeScript:
rx.rxt.importTags()
This lets you write:
span 'hello world'
Alternatively, to use without polluting the global namespace (recommended for larger-scale apps), just shorten the namespace prefix:
T = rx.rxt.tags
T.span 'hello world'
The following examples also lift rx.bind
out of its namespace:
bind = rx.bind
You can play with the following examples in this minimal jsFiddle.
Cells
The core building block is an observable cell, ObsCell
.
x = rx.cell()
A cell is just a container for a value (initialized to undefined
above, but
you could also have passed in an initial value). You can get
/set
this
value:
x = rx.cell(0)
x.set(3)
x.get() # 3
You can put arbitrary values into cells, such as arrays and objects.
Events
The special thing about observables is that you can subscribe to events on them, where events are fired when the value of the cell changes in some way. For simple cells like the above, there's just a single on-set event type:
subscriptionId = x.onSet.sub ([oldVal, newVal]) ->
console.log "x was set from #{oldVal} to #{newVal}"
The listener is just a simple callback. All event types take callbacks of a
single argument—the type of that argument is event-specific, and in the case of
onSet
it's a pair of [old value, new value]
. The sub
method returns a
unique identifier for this subscription, which can later be used to unsubscribe
a listener:
x.onSet.unsub(subscriptionId)
Once subscribed, you can start reacting to its events. For instance:
firstName = rx.cell('John')
# This ensures .name will always reflect the firstName
firstName.onSet.sub ([oldVal, newVal]) ->
$('.name').text(newVal)
firstName.set('Jane')
Events immediately invoke a newly added listener to bring them up to speed
on the current value, but you can suppress this first invocation with the
simple rx.skipFirst
utility function:
x.onSet.sub rx.skipFirst ([oldVal, newVal]) -> ...
Dependent Cells
The above is a simple way of observing and responding to events on cells, but it's a bit verbose. In most UIs, you really just want to be able to declaratively express your views in terms of a model (data binding), rather than explicitly manage listeners.
To extend the above example, let's say you now had a displayed name that depended on two cells (comprising your "model"). You could just create explicit subscriptions and listeners:
firstName = rx.cell('John')
lastName = rx.cell('Smith')
updateName = ->
$('.name').text("#{firstName.get()} #{lastName.get()}")
firstName.onSet.sub -> updateName
lastName.onSet.sub -> updateName
firstName.set('Jane')
lastName.set('Doe')
However, the primary way in which these cells are to be composed is via bind
,
which lets you simply write an expression or function in terms of the dependent
nodes:
fullName = bind -> "#{firstName.get()} #{lastName.get()}"
Now, fullName
is always bound to the firstName + lastName
. Key here is
that no explicit subscription management is necessary. This scales well to
more complex dependencies, and is more readable/declarative:
displayName = bind ->
if showRealName.get()
"Full name: #{fullName.get()}"
else
"Fake name: #{fakeName.get()}"
The bindings are managed such that only the dependent cells that could possibly
affect the result are effective dependencies. In this example, at any moment,
fullName
depends either only on showRealName
and fullName
or only on
showRealName
and fakeName
. If showRealName
is false, changes to
firstName
and lastName
will not trigger a re-render of the .name
.
firstName
and lastName
are source cells that support setting of values.
fullName
is itself also a cell, but a "read-only" dependent cell that we
can bind to some expression of source cells. These do not have a set
method.
Dependent cells can in turn be bound to as well:
greeting = bind -> "Welcome back #{fullName.get()}!"
These bindings can form an arbitrary DAG.
Reactive programming is the same concept behind how spreadsheet calculations work, and is similar to the data-binding feature present in many of the other frameworks—which is an effective paradigm for frontend UI development—but instead of being exclusively applied to view components, typically confined to a template language, this is generalized to be a generic way of expressing arbitrary time-varying data structures.
When you set a cell to the same value (value doesn't change), the event is not propagated. This is a useful efficiency boost that assumes/encourages you to think of binds/cells not as reacting to discrete events, but more abstractly as continuous time-varying signals. In general, your cells are encouraged to be deterministic in their effects, if not pure functions altogether.
Transactions
Sometimes you want to make a more complex update on some cells, while preserving certain semantics/invariants over them. For instance, consider two cells representing the balances in two different bank accounts. When transferring funds from one to the other, there will transiently be a moment when the total funds across both will be either greater or less than what it really should be:
a = rx.cell(5)
b = rx.cell(3)
sum = bind -> a.get() + b.get()
# sum should always be 8, but when transferring 1 from a to b...
a.set(a.get() - 1)
# sum is now 7!
b.set(b.get() + 1)
# sum back to 8
To prevent these "glitches" from being visible, we can bundle up the operations into a transaction. Throughout the duration of this transaction, events will not be propagated. For instance:
rx.transaction =>
a.set(a.get() - 1)
# sum will not yet have received any events
b.set(b.get() + 1)
# now sum will have received notifications, but it will see that the `a`
# and `b` still sum to 8.
Arrays and Maps
Cells are the most general type of observable container. However, there are
also observable containers with special support for arrays and maps
(that is, Javascript objects, which are basically maps). This specialized
support is for more efficient and fine-grained event types reflecting
changes to particular sub-parts rather than an all-encompassing onSet
event whenever any part of the array or map changes.
For instance, arrays commonly have elements inserted into or removed from them, in which case we'd like to avoid re-rendering entire dependent sections of the DOM or otherwise needing to figure out what parts have changed (the relationship between these reactive data structures and DOM UI rendering will be explained in later sections).
Arrays support an onChange
event. It fires
with a triple [index, removed, added]
, where index
is the offset into the
array where the change is happening, removed
is the sub-array of elements
removed, and added
is the sub-array of elements inserted. Example:
xs = rx.array([1,2,3])
xs.onChange.sub ([index, removed, added]) ->
console.log "replaced #{removed.length} element(s) at offset #{index} with #{added.length} new element(s)"
xs.push(4)
# replaced 0 element(s) at offset 3 with 1 new element(s)
From a declarative point of view, you can define a dependent array to be a mapped transformation of a source array:
ys = xs.map (x) -> 2 * x
# ys.all() is now [2, 4, 6, 8]
xs.push(5)
# ys.all() is now [2, 4, 6, 8, 10]
Besides push
as shown in the above example, there are a few other
mutation methods built in. Some are modeled after Javascript's Array
methods, e.g. splice
, while others are new, e.g. insert
.
However, for arbitrary transformations of arrays, you can use the update
method, which actually does the smart thing and performs a diff between the
old and new contents of the array, so as to minimize the downstream
dependencies. So in fact, you could implement all other update methods in
terms of update
. The above push
example could also have been written
with xs.update(xs.all().concat([4]))
, and that would have similarly
resulted in just a single element insertion in the downstream mapped array.
In the same spirit, you can convert an ObsCell
containing an array to a
DepArray
with cellToArray
, which simply maintains the DepArray
via
the update
mechanism (diffing and issuing minimal downstream change
events):
cell = rx.cell([1,2,3])
xs = rx.cellToArray(cell)
xs.onChange.sub ([index, removed, added]) ->
console.log "replaced #{removed.length} element(s) at offset #{index} with #{removed.length} new element(s)"
cell.set(cell.get().concat([4]))
# replaced 0 element(s) at offset 3 with 1 new element(s)
Objects also exist, but are called maps. They are pretty straightforward as regular containers:
months = rx.map()
months.put('Mar', 3)
months.put('Aug', 8)
months.put('Oct', 10)
months.get('Mar') # 3
months.remove('Oct') # 10
months.all() # {"Mar": 3, "Aug": 8}
With maps, there are three different event types:
months = rx.map({"Mar": 3, "Aug": 8})
months.onAdd.sub ([key, val]) ->
console.log "set #{key} to #{val}"
# set Mar to 3
# set Aug to 8
months.onChange.sub ([key, oldVal, newVal]) ->
console.log "updated #{key} (which had mapped to #{oldVal}) to #{newVal}"
months.onRemove.sub ([key, val]) ->
console.log "removed #{key} which had mapped to #{val}"
months.set('May', 5)
# set May to 5
months.set('Mar', 'three')
# updated Mar (which had mapped to 3) to three
months.remove('Oct')
# removed Aug which had mapped to 8
The key to maps is that you can bind to changes to just certain keys. For instance, if you had:
mar = bind -> months.get('Mar')
dec = bind -> months.get('Dec')
Then mar
will be re-evaluated only when the "Mar"
key is
changed/removed, and dec
will only when the "Dec"
key is inserted.
Object-Oriented Programming: A Pattern for Public and Private cells.
Often, when using an object-oriented approach, one wants to expose values to the user without permitting them
to change those values directly. When using reactive, we would also want the user to be able to subscribe
listeners. to these private cells. In addition, we still need to be able to set the value of the cell internally.
To achieve this, we can make use of rx.bind
, which returns a DepCell
object. These objects do not have a
set method, and thus cannot be directly mutated by the user:
class Incrementer
constructor: ->
_value = rx.cell(0) # private field which can be freely changed within this constructor's scope.
@value = bind => _value.get() # public field; its value mirrors that of _value, but cannot be directly changed by the user.
@increment = => _value.set(value.get() + 1)
inc = new Incrementer()
inc.value.onSet.sub ([oldVal, newVal]) -> console.log(newVal)
inc.increment() # prints 1
inc.value.set(42) # error, not allowed.
The usefulness of this pattern is not limited to object oriented programming. We can use this any time we wish to return a read-only cell:
autoIncrementedCell = (start) ->
value = rx.cell(start)
setInterval(value.set(value.get() + 1), 1000)
return bind -> value.get()
c = autoIncrementedCell(0)
c.onSet.sub ([oldVal, newVal]) -> console.log(newVal)
Static Templates
Now for a brief jump to something entirely different from reactive programming....
The "template" system is implemented as an embedded domain-specific language (DSL) in CoffeeScript, which happens to have a syntax that lends itself well to expressing the template structure.
Here's a simple template:
div {class: 'sidebar'}, [
h2 {}, ['Send a message']
form {action: '/msg', method: 'POST'}, [
input {type: 'text', name: 'comment', placeholder: 'Your message'}, []
select {name: 'recipient'}, [
option {value: '0'}, ['John']
option {value: '1'}, ['Jane']
]
button {class: 'submit-btn'}, ['Send']
]
]
It translates to the following HTML:
<div class="sidebar">
<h2>Send a message</h2>
<form action='/msg' method='POST'>
<input type='text' name='comment' placeholder='Your message'/>
<select name='recipient'>
<option value='0'>John</option>
<option value='1'>Jane</option>
</select>
<button class='submit-btn'>Send</button>
</form>
</div>
Note how the children of a DOM node are always lists, even when it's just a single text node.
Since this is CoffeeScript, you can embed arbitrary logic:
# Loops:
ul {class: 'friends'},
for name in names
li {class: 'friend'}, [name]
# Or equivalently:
ul {class: 'friends'}, names.map (name) ->
li {class: 'friend'}, [name]
# Conditionals:
div {class: 'profile'}, [
img {src: "#{user.picUrl}"}, []
if signedIn
button {}, ["Add #{user.name}" as a friend."]
else
p {}, ["Sign up to connect with #{user.name}!"]
]
Side note: for loops, it's generally better to prefer using .map()
, since
it's the same interface for both Array
and rx.array
, and it also
creates a unique binding for the element, avoiding a common pitfall with
JavaScript/CoffeeScript loops and closures (you can also use CoffeeScript's
handy do
keyword).
So far we've been feeding full explicit arguments to each tag. They are
actually more flexible in what they take. We can clean up a bunch of the
syntactic noise in the last example by replacing ['string']
with just
'string'
, and omitting empty arguments like {}
and []
(the remaining
argument is treated as the attributes or the contents based on its type):
div {class: 'profile'}, [
img {src: "#{user.picUrl}"}
if signedIn
button "Add #{user.name}" as a friend."
else
p "Sign up to connect with #{user.name}!"
]
Tags are really just functions that return DOM elements (wrapped in jQuery objects), so you are free to attach behaviors to them:
$button = button {class: 'submit-btn'}, 'Click Me!'
$button.click -> $(this).text('I been clicked.')
However, since often times you may be working with deeply nested templates
structures where it's clumsy to tack on behaviors afterward, you can for
convenience supply a function in an attribute named init
, which is
immediately invoked with the current element bound to this
:
table {}, properties.map (prop) ->
tr [
td prop.name
td [
input {
type: 'text'
value: prop.value
placeholder: 'Enter property value'
init: -> @blur => setProperty(prop, @val())
}
]
]
Since CoffeeScript assignments are expressions and it automatically manages variable scoping, we also have a convenient way of naming elements anywhere in a template structure:
table {}, properties.map (prop) ->
$row = tr [
td prop.name
td [
input {
type: 'text'
value: prop.value
placeholder: 'Enter property value'
init: -> @blur =>
$row.css('opacity', .5)
setProperty(prop, @val())
}
]
]
init
was an example of a special attribute. These are just attributes
that are intercepted and handled differently from others, which are simply
passed on as regular HTML attributes on the DOM element. Besides init
,
built-in special attributes include:
style
, which in addition to regular style strings accepts jQuery-style mappings like{fontSize: 10}
, so you can write:div { style: bind -> width: score.get() color: if team.get() == 'a' then 'red' else 'blue' display: 'none' if gameMode.get() != 'running' }
class
, which in addition to regular class strings accepts an array of class names (filtering outundefined
), e.g.:button { class: bind -> [ 'btn' 'btn-primary' if formComplete.get() ] }
(You can also access this transform directly via
rxt.smushClasses
.)and all jQuery events:
click
,focus
,mouseleave
,keyup
,load
, etc. So we could have bound the blur event in the earlier example directly (note it also takes a jQuery object asthis
):... input { type: 'text' value: prop.value placeholder: 'Enter property value' blur: -> $row.css('opacity', .5) setProperty(prop, @val()) } ...
You can also add your own special attribute by inserting a function into
the specialAttrs
global structure.
Reactive Templates
Reactive templates tie together the UI-building style from the previous section with the reactive programming primitives from earlier.
Now, you could just write explicit imperative code to transform the DOM in a way that consistently reflects the bindings you're interested in. For instance:
$('body').append(input {
class: 'name passive'
type: 'text'
placeholder: 'Name'
value: ''
})
displayName.onSet.sub ([oldVal, newVal]) ->
$('.name').val(newVal)
isActive.onSet.sub ([oldVal, newVal]) ->
if newVal
$('.name').removeClass('passive').addClass('active')
else
$('.name').removeClass('active').addClass('passive')
However, more complex transformations can become more involved.
names = rx.array(['1','2','3','4','5'])
$('body').append($nameList = div {class: 'name-list'})
spans = names.map (name) -> span name
spans.onChange.sub ([index, added, removed]) ->
# Homework: fill in logic here for efficiently inserting/removing DOM
# nodes!
You also shouldn't have to repeatedly code this logic any time you want to make bindings, and it would be much more clear to specify the template declaratively:
$('body').append(
input {
class: bind -> "name #{if isActive.get() then 'active' else 'passive'}"
type: 'text'
placeholder: 'Name'
value: bind -> displayName.get()
}
)
$('body').append(
div {class: 'name-list'}, names.map (name) ->
span name
)
You're declaring what the UI should always look like over time, and the system frees you from the responsibility of maintaining this.
Here's another quick example, this one of a todo list. Notice how here we are
using a raw array in a cell, rather than an rx.array
. This is fine but not
as efficient in the face of large arrays.
tasks = rx.cell(['Get milk', 'Take out trash', 'Clean up room'])
$('body').append(
ul {class: 'tasks'}, bind ->
for task in tasks.get()
li {class: 'task'}, "User: #{task}"
)
Any attribute can be a cell:
input {value: bind -> displayName.get()}
Contents can be a cell that returns an array (of strings or elements):
span {}, bind -> [displayName.get()]
div {}, bind -> names.all()
# shorthand:
span bind -> displayName.get()
div bind -> names.all()
or an observable array:
div {}, names
# results in: <div>01234</div>
# shorthand:
div names
div {}, names.map (name) ->
span {}, [name]
# results in: <div><span>0</span><span>1</span>...</div>
# shorthand:
div names.map (name) ->
span name
You have very fine-grained control over the re-rendering process. For instance, say we had a model like the following:
class User
constructor: (id, name) ->
@id = rx.cell(id)
@name = rx.cell(name)
class App
constructor: (users) ->
@users = rx.array(users)
app = new App([
new User('John', 0)
new User('Jane', 1)
])
If we wanted to just re-render individual elements, we could do that with:
select app.users.map (user) ->
option {value: bind -> "#{user.id.get()}"}, bind -> "#{user.name.get()}"
On the other hand, if we wanted to re-render the entire section whenever anything changed, we could do so with:
select bind -> app.users.all().map (user) ->
option {value: "#{user.id.get()}"}, "#{user.name.get()}"
bind
/map
calls are insulated from outer calls,
re-rendering only what's necessary.
As an escape hatch, to insulate some code fn
from the
current bind context and not subscribe to any dependencies therein, you
can use rx.snap(fn)
. This is evaluated only once:
div {class: 'editor'}, bind -> [
input {type: 'text', value: snap -> x.get()}
]
Without the snap
, anytime x
changes, the entire div
would be
re-rendered, but with a bind
, the text box's contents would be
re-rendered. snap
is the "anti-bind
".
Sometimes you will want to append an observable array after some element.
Or you may want to make one element among a set conditionally rendered.
For these situations, there's a handy rx.flatten
utility function:
div {}, rx.flatten [
h1 {}, 'Header'
headerItems.map (item) -> ...
bind -> separator if showSeparator.get()
h1 {}, 'Footer'
footerItems.map (item) -> ...
]
(These behaviors may be folded into core rxt
behavior if it proves to be
a sensible default.)
More on Arrays and Reactive Templates
Arrays also support an indexed
method which returns a copy of the array,
but which actually tracks the indices of its elements, such that when you
map
the array, the mapper function is given the index (as a reactive
cell) in addition to the current element. This way you can react to shifts
in the position of each element.
For instance, if you had a list where you wanted the rows to be styled based on whether they're prime:
xs = rx.array(...)
ul xs.indexed().map (x,i) ->
li {class: bind -> if isPrime(i.get()) then 'prime' else ''}, x
# shifts the entire array, but still only prime rows have class "prime"
xs.removeAt(0)
This is not built into map
since it would make map
s more expensive, and
in general this is a less-frequently used feature.
Components
Making reusable components is as simple as defining a function, where the convention is to pass in a single named argument object, as opposed to regular arguments, since components have a tendency to demand a fairly unwieldy set of parameters:
#
# tabs: array of tab contents, where each tab content is itself an array of
# elements or text
# initialTabIndex: index of initial tab; defaults to 0
# activeClass: class to set an active tab; defaults to 'active-tab'
# change: a callback that is called whenever the active tab is changed,
# and is passed the new active tab index
#
tabs = (opts) ->
opts = _(opts).defaults
initialTabIndex: 0
activeClass: 'active-tab'
change: ->
opts = rx.rxt.cast opts,
tabs: 'array'
activeTabIndex = rx.cell(opts.initialTabIndex)
div {class: 'tabs'}, [
ul {class: 'nav-tabs'}, opts.tabs.map ([tabName, tabContents], i) ->
li {
class: bind ->
[
"nav-tab"
"#{if activeTabIndex.get() == i then opts.activeClass else ''}"
].join(' ')
click: ->
activeTabIndex.set(i)
opts.change(i)
}, [tabName]
div {class: 'tab-content'}, bind ->
[tabName, tabContents] = opts.tabs.at(activeTabIndex.get())
tabContents
]
Above, we used a convenience function rxt.cast
which lets us cast inputs
to the desired observable data type (cell or array).
Now we can use this with:
modifyTab = -> ['MODIFY']
createTab = -> ['CREATE']
stylesTab = -> ['STYLES']
tabs {
activeClass: 'my-active-tab'
tabs: rx.array([
['Properties', modifyTab()]
['Create', createTab()]
['Styles', stylesTab()]
])
}
When defining abstractions, it's almost always better to err on the side of
making everything dynamic, since you can always opt-out of dynamism by stopping
propagation (e.g. with snap
or with hoisting things out of a bind
),
whereas you cannot later reintroduce dynamism into places with no dynamic
bindings.
Binding To DOM Element Attributes
We can treat certain interactive DOM elements' attributes as cells, thus binding to them as well.
Consider a type-ahead search component, where the completion list only displays items that prefix-match the given query. Furthermore, we want the completion list to only show up if it's enabled by the checkbox and when we actually have focused on the input text field:
class Doc
constructor: (@title, @url) ->
docs = [
new Doc('An axiomatic basis for computer programming', 'http://sigpl.or.kr/school/2005w/slides/2_17_1.pdf')
new Doc('The next 700 programming languages', 'http://www.thecorememory.com/Next_700.pdf')
new Doc('A theory of type polymorphism in programming', 'http://courses.engr.illinois.edu/cs421/sp2012/project/milner-polymorphism.pdf')
new Doc('A semantics of multiple inheritance', 'http://lucacardelli.name/Papers/Inheritance.pdf')
]
$('body').append(
div {class: 'search'}, [
$query = input {type: 'text', placeholder: 'Enter query'}
$showResults = input {type: 'checkbox', id: 'show-results'}
label {for: 'show-results'}, 'Show type-ahead results'
div {class: 'results'}, bind ->
focused = $query.rx('focused')
console.log 'firing', focused
if focused.get() and $showResults.rx('checked').get()
for doc in docs when doc.title.indexOf($query.rx('val').get()) >= 0
div [
a {href: doc.url}, doc.title
]
else
[]
]
)
We are using three different types of reactive element attributes.
rx(property)
returns the value of the given property (focused
,
checked
, val
) as a cell.
Lifting
Often times your models will consist of objects or instances with various observable fields. For instance, consider the User
class defined earlier:
class User
constructor: (id, name) ->
@id = rx.cell(id)
@name = rx.cell(name)
You can alternatively take advantage of the dynamic runtime and use the
meta-facility rx.lift
to convert a plain old Javascript object containing
non-observable fields to one with observable fields:
x = {a:0, b:[], c:{}, d:null}
rx.lift(x) # also returns x
x.a.get() # 0
x.b.all() # []
x.c.get() # {}
x.d.get() # null
It also makes writing User
above simpler:
class User
constructor: (@id, @name) -> rx.lift(@)
new User(0, 'Joe').name.get() # Joe
By default, fields that are arrays are turned into rx.array
, while others
are turned into rx.cell
. You can control/refine how fields are lifted by
passing in a spec (by default, uses rx.liftSpec
to generate one). See
the API documentation for more details.
Anti-Pattern: Non-Nesting
Do not construct UIs "out-of-band" from the bind
hierarchy:
namedisp = span {class: 'name'}, username.get()
popup = div {class: 'modal'}, bind -> namedisp
The above will either
- fail to ever update
namedisp
, assuming there is no outer bind running this code, or - cause any outer bind running this code (not shown) to re-evaluate (likely unintended).
This doesn't mean you always need to syntactically nest your code in the following way:
popup =
div {class: 'modal'}, bind ->
span {class: 'name'}, username.get()
Instead, you can maintain the original structure of the code, but just defer running the code until you're inside the bind, using functions:
namedisp = -> span {class: 'name'}, username.get()
popup = div {class: 'modal'}, bind -> namedisp()
Next Steps
Congrats on making it all the way through the basic tutorial! You can continue to learn more about "advanced" concepts (really just more details), or read more on the motivation/design rationale behind the library.
Otherwise, go forth and build some web apps using Reactive! Drop us a line if you do and would like to share your work on the website.