reactive.coffee

A lightweight CoffeeScript library/DSL for reactive programming and for declaratively building scalable web UIs

View project onGitHub

Highlights

Already familiar with other frameworks such as Angular? See Design and Comparisons.
  • Library of reactive programming primitives
  • Declarative DOM construction
  • Scalable in both performance and application architecture
  • Simple, no magic, no new template language, all CoffeeScript
  • Tested with Chrome, Firefox, Safari, and IE10
  • Available via Bower and cdnjs
  • Works with jQuery
  • MIT license

Example: To-Do List

Prefer prose to code? Read our introductory blog post for an overview.

Get a quick taste of reactive.coffee. You can play with this example on jsFiddle, see a complete TodoMVC example, or head directly to the tutorial.

# This is our application's core data model, an array of Task objects.
# `cell` and `array` are our primitive reactive data structures.  You can
# listen for (and react to) changes to their values.

class Task
  constructor: (descrip, priority, isDone) ->
    @descrip = rx.cell(descrip)
    @priority = rx.cell(priority)
    @isDone = rx.cell(isDone)

tasks = rx.array([
  new Task('Get milk', 'important', false)
  new Task('Play with Reactive Coffee', 'critical', false)
  new Task('Walk the dog', 'meh', false)
])

# Our main view: a checklist of tasks, a button to add a new task, and a
# task editor component (defined further down).
#
# `bind` (and `array.map`) are the central mechanisms by which you can
# declare cells that are always bound to the current value of some
# expression over other cells.  `z = bind -> x.get() + y.get()` says `z`
# should always reflect the sum even as `x` and `y` change.  Subscription
# management is handled automatically.

main = ->
  currentTask = rx.cell(tasks.at(0)) # "View model" of currently selected task

  $('body').append(
    div {class: 'task-manager'}, [
      h1 bind -> "#{tasks.length()} task(s) for today"
      ul {class: 'tasks'}, tasks.map (task) ->
        li {class: bind -> "task-#{if task == currentTask.get() then 'selected' else 'unselected'}"}, [
          input {type: 'checkbox', click: -> task.isDone.set(@is(':checked')); true}
          span {class: 'descrip'}, bind ->
            "#{task.descrip.get()} (#{task.priority.get()})"
          a {href: 'javascript: void 0', click: -> currentTask.set(task)}, 'Edit'
        ]
      button {click: -> tasks.push(new Task('Task', 'none', false))}, 'Add new task'
      taskEditor {
        task: bind -> currentTask.get()
        onSubmit: (descrip, priority) ->
          currentTask.get().descrip.set(descrip)
          currentTask.get().priority.set(priority)
      }
    ]
  )

# The task editor demonstrates how to define a simple component.
# Reusable components, encapsulating both view and behavior, are key to
# application scalability.

taskEditor = (opts) ->
  task = -> opts.task.get()
  theForm = form [
    h2 'Edit Task'
    label 'Description'
    descrip = input {type: 'text', value: bind -> task().descrip.get()}
    br()
    label 'Priority'
    priority = input {type: 'text', value: bind -> task().priority.get()}
    br()
    label 'Status'
    span bind -> if task().isDone.get() then 'Done' else 'Not done'
    br()
    button 'Update'
  ]
  # Here we are munging the strings a bit before updating the model.  The
  # displayed text will instantly reflect the munged data (try
  # appending/prepending spaces).  We could've also made this a `submit`
  # property on the `form` element above; the point is that these are
  # normal jQuery objects that you can flexibly manipulate.
  theForm.submit ->
    opts.onSubmit(descrip.val().trim(), priority.val().trim())
    false

$(main)

Next steps

See more quickstart examples, read through the tutorial, or learn more about the motivation and design rationale.

Contributors

The individuals who through their time, effort, and feedback have made reactive.coffee possible: