Skip to content

client obs

Emiel Mols edited this page Aug 12, 2015 · 1 revision

This module is central to the data model of the Happening Platform. Primarily, obs data primitives wrap around a tree of data, providing getters and setters for easy manipulation, while implementing observer/change-semantics.

Simple example:

Obs = require 'obs'

counter = Obs.create(0)
counterLessThan10 = Obs.create(false)

Obs.observe -> # This get re-executed whenever counter changes
	counterLessThan10.set(counter()<10)

increment = ->
	counter.modify (v) -> v+1

Often, you will use observe semantics in combination with the dom module. In fact, each dom element will run its content function in a new observe context.

Observable hashes

Besides primitive values, observables can hold objects. Each object is represented by an observable, while observable hash values can refer to other observables.

Obs = require 'obs'
Dom = require 'dom'

users = Obs.create
	1:
		name: 'Emiel'
		score: 100

emiel = users.get(1) # another observable hash

Dom.div -> # re-exec when `name` changes
	Dom.text emiel.get('name')
	Dom.text ", your score is "
	Obs.observe -> # re-exec when `score` changes
		Dom.text emiel.get('score')

Dom.div ->
	Dom.text tr("User 1 is called %1", users.get(1,'name'))

Obs.onTime 2000, ->
	users.set(1, score, 150)

Observer scopes

Note that in the example above, when the score is adjusted to 150, only the text node containing the user's old score is removed/re-added, without changing the containing DIV node. This is because observe() creates a new observer scope. In each observer scope, data dependencies are recorded (in this instance, the dependency on the user's score value), and, once a single dependency changes, the scope is re-evaluated.

Creating HTML elements with the dom module implicitly creates new observer scopes.

If you create the observable within the same scope as where you use it, it get's recreated every time. So create your observable outside the scope!

Dom = require 'dom'
Obs = require 'obs'

Dom.section !->
	myO = Obs.create(1)
	Dom.div !->
		# myO = Obs.create(1) < don't declare it here
		Dom.text "click me: " + myO.get()
		Dom.onTap !->
			myO.modify (v) -> v+1

Reactive iteration

Another style of reading observable hashes, is through iteration. The iterate method is given a function, which will be applied for all key/values pairs in the hash, each in their own observer scope. But interestingly, the function is also applied for items that are added to the list later on! Also, when items are removed from the list, the side effects of the function invocation (such as DOM node creation) will be cleaned (more on that later: Obs.clean).

Obs = require 'obs'
Dom = require 'dom'

users = Obs.create
	1: {name: 'Emiel', score: 10}
	2: {name: 'Frank', score: 30}
	3: {name: 'Jelmer', score: 20}

users.iterate (user) !->
	Dom.div "User: \#{name}"
, (user) -> (user 'score')

# A reactive counter. When a new element is added, we only need to do a +1
# and not recount the rest.
userCount = Obs.create 0
users.iterate (user) !-> # executed once for each user
	userCount.modify (v) -> v+1
	Obs.onClean -> # when the user is deleted
		userCount.modify (v) -> v-1

	Dom.div !-> # executed when `userCount` changes
	Dom.text "There are \#{userCount()} users"

Obs.onTime 2000, !->
	users.set 4, {name: 'Peter'}

Here we use onClean(func) to register a function that will be called when a user is removed from the hash. Note the (optional) second argument to iterate is an ordering function: it should return a number that defines the order of the DIV elements in the document. The ordering function creates data dependencies when appropriate; in this case the DIV will rerender at a different position when the user's score changes.

Although the reactive iteration semantics allow for pretty advanced data manipulation already, some standard data mapping primitives are implemented by this module in the form of stream* functions.

Functions

create (initialValue)

Creates a new observable value.

Example:

Dom.section !->
	myObs = Obs.create(4)
	Dom.div !->
		# This entire div is re-rendered when myObs changes.
		Dom.text "Number of cats: " + myObs.get()

	Ui.bigButton "Add a cat", !->
		myObs.modify (v) ->
			v+1

observe (func)

Create a new observer context, in which reads from

Example:

Dom.section !->
	myObs = Obs.create(4)
	Dom.div !->
		Dom.text "Number of cats: "
		Obs.observe !-> # only the value gets re-rendered when changed.
			if myObs.get()
				Dom.text myObs.get()

	Ui.bigButton "Remove a cat", !->
		myObs.modify (v) ->
			v = --v
			if not v
				return "no more cats"
			v

onClean (func)

Func will fire when the current observed object is removed.

Example:
```coffeescript
Dom = require 'dom'
Obs = require 'obs'
Ui = require 'ui'

Dom.section !->
	myObs = Obs.create("Cat in the box")
	myDiv = null
	Dom.div !->
		myDiv = Dom.get()
		if myObs.get()
			Dom.text myObs.get()

		Obs.onClean !->
			myDiv.setText "An empty box"

	Ui.bigButton "Remove cat", !->
		myObs.set null #setting 'null' removes the object.
```

Reading an observable value

myObs.get([path...])

Returns the observed value (recursively, if its a hash with values referencing other observables), subscribing for changes in the current context. When the path does not exist, undefined is returned.

myObs.peek([path...])

Exactly the same as get, only without subscribing to changes.

myObs.set([path...], newValue)

Updates the observed value and returns an observable value that refers to [path...] (or just myObs if no path was specified). This does not subscribe to future changes. When the path does not yet exist, it is created. Setting null values will delete properties.

myObs.iterate([path...], func, orderFunc?)

Reactive iteration (see examples above).

myObs.modify([path...], modifierFunc)

This method peeks the value, passes it as an argument to modifiedFunc and then sets whatever is returned.

Example:

Dom = require 'dom'
Obs = require 'obs'
Ui = require 'ui'

clientCounter = Obs.create(0)
Dom.div !->
	# whenever clientCounter's value is changed, only this DIV is redrawn
	Ui.button "Increment me: " + clientCounter.get(), !->
		clientCounter.modify (v) -> v+1

myObs.ref(path...]

Returns a new observable value, that points at path.... Underneath, both myObs and the newly created observable value refer to the same data. When the given path does not exist, undefined is returned.

myObs.map([path...], func]

Maps an observable hash onto another observable hash. func receives observable values for each item in myObs, and the values it returns will be inserted under the same key in the resulting observable hash.

Timer functions

Obs has client-size timer functions.

onTime (ms, cb)

This creates a simple time. It calls the callback function after a set number of milliseconds.

Example:

Dom.section !->
	wait = null
	myObs = Obs.create "I wonder if it contains a cat."
	Dom.div !->
		Dom.text "A Box. " + myObs.get()
		Ui.spinner 25
		wait = Dom.last()
		wait.style 'display' : 'inline-block'
		wait.style 'visibility' : 'hidden'

	Ui.bigButton "Check if there is a cat", !->
		wait.style 'visibility' : 'visible'
		Obs.onTime 1000, !->
			if Math.random() < 0.5
				myObs.set "No cat."
			else
				myObs.set "There is a cat!"
			wait.style 'visibility' : 'hidden'

interval (ms, cb)

This creates a recurring timer. It calls the callback function after a set number of milliseconds, and then resets its timer.

Example:

Dom.section !->
	myDiv = null
	myText = "A Box. "
	Dom.div !->
		Dom.text myText
		myDiv = Dom.get()

	Ui.bigButton "Check if there is a cat", !->
		Obs.interval 500, !->
			if Math.random() < 0.5
				myText += "No cat. "
			else
				myText += "There is a cat! "
			myDiv.setText myText

Basic topics

API reference

  • API Reference
    • Client
      • [client plugin](client plugin)
      • [client dom](client dom)
      • [client obs](client obs)
      • [client db](client db)
      • [client server](client server)
      • [client page](client page)
      • [client ui](client ui)
      • [client form](client form)
      • [client icon](client icon)
      • [client modal](client modal)
      • [client photo](client photo)
      • [client photoview](client photoview)
      • [client time](client time)
      • [client share](client share)
      • [client map](client map)
      • [client geoloc](client geoloc)
    • Server
      • [server event](server event)
      • [server plugin](server plugin)
      • [server http](server http)
      • [server db](server db)
      • [server photo](server photo)
      • [server time](server time)
  • Example UI elements

Advanced topics

Clone this wiki locally