mutant
Version:
Create observables and map them to DOM elements. Massively inspired by hyperscript and observ-*. No virtual dom, just direct observable bindings. Unnecessary garbage collection is avoided by using mutable objects instead of blasting immutable junk all ove
121 lines (97 loc) • 2.75 kB
JavaScript
var Value = require('./value')
var LazyWatcher = require('./lib/lazy-watcher')
var isSame = require('./lib/is-same')
var extend = require('xtend')
module.exports = Struct
var blackList = {
'length': 'Clashes with `Function.prototype.length`.\n',
'name': 'Clashes with `Function.prototype.name`\n',
'set': '`set` is a reserved key of struct\n'
}
const structs = new WeakSet()
function Struct (properties, opts) {
var object = Object.create({})
var releases = []
var binder = LazyWatcher(update, listen, unlisten)
binder.value = object
if (opts && opts.nextTick) binder.nextTick = true
if (opts && opts.idle) binder.idle = true
var comparer = opts && opts.comparer || null
var observable = function MutantStruct (listener) {
if (!listener) {
return binder.getValue()
}
return binder.addListener(listener)
}
var keys = Object.keys(properties)
var suspendBroadcast = false
keys.forEach(function (key) {
if (blackList.hasOwnProperty(key)) {
throw new Error("Cannot create a struct with a key named '" + key + "'.\n" + blackList[key])
}
var obs = typeof properties[key] === 'function'
? properties[key]
: Value(properties[key])
object[key] = obs()
observable[key] = obs
})
observable.set = function (values, opts) {
var lastValue = suspendBroadcast
suspendBroadcast = true
values = values || {}
if (opts && opts.merge) {
values = extend(object, values)
}
// update inner observables
keys.forEach(function (key) {
if (observable[key]() !== values[key]) {
observable[key].set(values[key])
}
})
// store additional keys (but don't create observables)
Object.keys(values).forEach(function (key) {
if (!(key in properties)) {
object[key] = values[key]
}
})
suspendBroadcast = lastValue
if (!suspendBroadcast) {
binder.broadcast()
}
}
structs.add(observable)
return observable
// scoped
function listen () {
keys.map(function (key) {
var obs = observable[key]
releases.push(obs(function (val) {
if (!isSame(val, object[key], comparer)) {
object[key] = val
if (!suspendBroadcast) {
binder.broadcast(object)
}
}
}))
})
}
function unlisten () {
while (releases.length) {
releases.pop()()
}
}
function update () {
var changed = false
keys.forEach(function (key) {
var newValue = observable[key]()
if (!isSame(newValue, object[key], comparer)) {
object[key] = observable[key]()
changed = true
}
})
return changed
}
}
Struct.isStruct = function (obj) {
return structs.has(obj)
}