-
Notifications
You must be signed in to change notification settings - Fork 0
client obs
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+1Often, 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.
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)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+1Another 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.
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+1Create 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"
vFunc 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.
```
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.
Exactly the same as get, only without subscribing to changes.
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.
Reactive iteration (see examples above).
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+1Returns 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.
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.
Obs has client-size timer functions.
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'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- [How it works](How it works)
- [Your first plugin](Your first plugin)
- Submitting and distributing your plugin
- Using the Developer Tools
- Example plugins on Github
-
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)
- Client
- Example UI elements