@mmckegg/mutant
Advanced tools
Comparing version 2.2.1 to 3.0.0
123
array.js
@@ -0,12 +1,18 @@ | ||
var Value = require('./value') | ||
var LazyWatcher = require('./lib/lazy-watcher') | ||
var isReferenceType = require('./lib/is-reference-type') | ||
var isSame = require('./lib/is-same') | ||
var isObservable = require('./is-observable') | ||
var resolve = require('./resolve') | ||
var addCollectionMethods = require('./lib/add-collection-methods') | ||
module.exports = Array | ||
function Array (defaultValues) { | ||
function Array (defaultValues, opts) { | ||
var object = [] | ||
var sources = [] | ||
var releases = [] | ||
var fixedIndexing = opts && opts.fixedIndexing || false | ||
var comparer = opts && opts.comparer || null | ||
var binder = LazyWatcher(update, listen, unlisten) | ||
@@ -26,10 +32,33 @@ binder.value = object | ||
observable.push = function (args) { | ||
for (var i = 0; i < arguments.length; i++) { | ||
add(arguments[i]) | ||
// getLength, get, indexOf, etc | ||
addCollectionMethods(observable, sources) | ||
observable.push = function (item) { | ||
var result = null | ||
if (arguments.length === 1) { | ||
result = add(item) | ||
} else { | ||
result = [] | ||
for (var i = 0; i < arguments.length; i++) { | ||
result.push(add(arguments[i])) | ||
} | ||
} | ||
binder.broadcast() | ||
return result | ||
} | ||
observable.put = function (index, valueOrObs) { | ||
valueOrObs = getObsValue(valueOrObs) | ||
sources[index] = valueOrObs | ||
object[index] = resolve(valueOrObs) | ||
if (binder.live) { | ||
tryInvoke(releases[index]) | ||
releases[index] = bind(valueOrObs) | ||
} | ||
binder.broadcast() | ||
return valueOrObs | ||
} | ||
observable.insert = function (valueOrObs, at) { | ||
valueOrObs = getObsValue(valueOrObs) | ||
sources.splice(at, 0, valueOrObs) | ||
@@ -39,28 +68,7 @@ if (binder.live) releases.splice(at, 0, bind(valueOrObs)) | ||
binder.broadcast() | ||
return valueOrObs | ||
} | ||
observable.get = function (index) { | ||
return sources[index] | ||
} | ||
observable.getLength = function (index) { | ||
return sources.length | ||
} | ||
observable.includes = function (valueOrObs) { | ||
return !!~sources.indexOf(valueOrObs) | ||
} | ||
observable.indexOf = function (valueOrObs) { | ||
return sources.indexOf(valueOrObs) | ||
} | ||
observable.forEach = function (fn, context) { | ||
sources.slice().forEach(fn, context) | ||
} | ||
observable.find = function (fn) { | ||
return sources.find(fn) | ||
} | ||
observable.indexOf | ||
@@ -93,4 +101,7 @@ | ||
observable.delete = function (valueOrObs) { | ||
var index = sources.indexOf(valueOrObs) | ||
if (~index) { | ||
observable.deleteAt(sources.indexOf(valueOrObs)) | ||
} | ||
observable.deleteAt = function (index) { | ||
if (index >= 0 && index < sources.length) { | ||
sources.splice(index, 1) | ||
@@ -104,10 +115,33 @@ if (binder.live) releases.splice(index, 1).forEach(tryInvoke) | ||
observable.set = function (values) { | ||
unlisten() | ||
sources.length = 0 | ||
releases.length = 0 | ||
object.length = 0 | ||
values.forEach(add) | ||
if (binder.live) { | ||
listen() | ||
if (fixedIndexing) { | ||
var length = values && values.length || 0 | ||
for (var i = 0; i < length; i++) { | ||
if (!sources[i]) { | ||
var valueOrObs = getObsValue(values[i]) | ||
sources[i] = valueOrObs | ||
object[i] = resolve(valueOrObs) | ||
if (binder.live) { | ||
releases[i] = bind(valueOrObs) | ||
} | ||
} else { | ||
sources[i].set(values[i]) | ||
} | ||
} | ||
for (var index = length; index < sources.length; index++) { | ||
tryInvoke(releases[index]) | ||
} | ||
releases.length = length | ||
sources.length = length | ||
object.length = length | ||
binder.broadcast() | ||
} else { | ||
unlisten() | ||
sources.length = 0 | ||
releases.length = 0 | ||
object.length = 0 | ||
values.forEach(add) | ||
if (binder.live) { | ||
listen() | ||
binder.broadcast() | ||
} | ||
} | ||
@@ -120,8 +154,17 @@ } | ||
function getObsValue (valueOrObs) { | ||
if (fixedIndexing && !isObservable(valueOrObs)) { | ||
valueOrObs = Value(valueOrObs) | ||
} | ||
return valueOrObs | ||
} | ||
function add (valueOrObs) { | ||
valueOrObs = getObsValue(valueOrObs) | ||
sources.push(valueOrObs) | ||
object.push(resolve(valueOrObs)) | ||
if (binder.live) { | ||
releases.push(bind(valueOrObs)) | ||
} | ||
object.push(resolve(valueOrObs)) | ||
return valueOrObs | ||
} | ||
@@ -146,5 +189,5 @@ | ||
var changed = false | ||
sources.forEach(function (key, i) { | ||
var newValue = resolve(observable[key]) | ||
if (newValue !== object[i] || isReferenceType(newValue)) { | ||
sources.forEach(function (source, i) { | ||
var newValue = resolve(source) | ||
if (!isSame(newValue, object[i], comparer)) { | ||
object[i] = newValue | ||
@@ -151,0 +194,0 @@ changed = true |
@@ -10,2 +10,3 @@ /* A lazy binding take on computed */ | ||
var isObservable = require('./is-observable') | ||
var isSame = require('./lib/is-same') | ||
@@ -15,4 +16,6 @@ module.exports = computed | ||
computed.NO_CHANGE = {} | ||
computed.extended = extendedComputed | ||
function computed (observables, lambda, opts) { | ||
// opts: nextTick, comparer, context | ||
var instance = new ProtoComputed(observables, lambda, opts) | ||
@@ -39,5 +42,5 @@ return instance.MutantComputed.bind(instance) | ||
this.opts = opts | ||
this.context = this.opts && this.opts.context || {} | ||
this.comparer = opts && opts.comparer || null | ||
this.context = opts && opts.context || {} | ||
this.boundOnUpdate = this.onUpdate.bind(this) | ||
this.boundOnInnerUpdate = this.onInnerUpdate.bind(this) | ||
this.boundUpdateNow = this.updateNow.bind(this) | ||
@@ -80,3 +83,3 @@ } | ||
if (this.inner) { | ||
this.releaseInner = this.inner(this.boundOnInnerUpdate) | ||
this.releaseInner = this.inner(this.onInnerUpdate.bind(this, this.inner)) | ||
} | ||
@@ -113,3 +116,3 @@ this.live = true | ||
var newValue = resolve(this.observables[i]) | ||
if (newValue !== this.values[i] || this.isMutable(newValue)) { | ||
if (!isSame(newValue, this.values[i], this.comparer)) { | ||
changed = true | ||
@@ -128,3 +131,3 @@ this.values[i] = newValue | ||
if (newComputedValue !== this.computedValue || (this.isMutable(newComputedValue) && !isObservable(newComputedValue))) { | ||
if (!isSame(newComputedValue, this.computedValue, this.comparer)) { | ||
if (this.releaseInner) { | ||
@@ -140,3 +143,3 @@ this.releaseInner() | ||
if (this.live) { | ||
this.releaseInner = this.inner(this.boundOnInnerUpdate) | ||
this.releaseInner = this.inner(this.onInnerUpdate.bind(this, this.inner)) | ||
} | ||
@@ -161,6 +164,8 @@ } else { | ||
}, | ||
onInnerUpdate: function (value) { | ||
if (value !== this.computedValue || this.isMutable(this.computedValue)) { | ||
this.computedValue = value | ||
this.broadcast() | ||
onInnerUpdate: function (obs, value) { | ||
if (obs === this.inner) { | ||
if (!isSame(value, this.computedValue, this.comparer)) { | ||
this.computedValue = value | ||
this.broadcast() | ||
} | ||
} | ||
@@ -175,15 +180,11 @@ }, | ||
getValue: function () { | ||
if (!this.live || this.lazy) { | ||
if (!this.live || this.lazy || this.updating) { | ||
this.lazy = false | ||
this.update() | ||
if (this.inner) { | ||
this.computedValue = resolve(this.inner) | ||
} | ||
} | ||
return this.computedValue | ||
}, | ||
isMutable: function (value) { | ||
if (this.opts && this.opts.immutableTypes && isInstanceOfAny(value, this.opts.immutableTypes)) { | ||
return false | ||
} else { | ||
return isReferenceType(value) | ||
} | ||
}, | ||
broadcast: function () { | ||
@@ -198,15 +199,21 @@ // cache listeners in case modified during broadcast | ||
function isReferenceType (value) { | ||
return typeof value === 'object' && value !== null | ||
} | ||
function extendedComputed (observables, update) { | ||
var live = false | ||
var lazy = false | ||
function isInstanceOfAny (object, types) { | ||
var result = false | ||
for (var i = 0; i < types.length; i++) { | ||
if (object instanceof types[i]) { | ||
result = true | ||
break | ||
var instance = computed(observables, function () { | ||
return update() | ||
}, { | ||
onListen: function () { live = lazy = true }, | ||
onUnlisten: function () { live = false } | ||
}) | ||
instance.checkUpdated = function () { | ||
if (!live || lazy) { | ||
lazy = false | ||
update() | ||
} | ||
} | ||
return result | ||
return instance | ||
} |
@@ -0,1 +1,3 @@ | ||
var resolve = require('./resolve') | ||
var addCollectionMethods = require('./lib/add-collection-methods') | ||
var computed = require('./computed') | ||
@@ -7,13 +9,14 @@ | ||
var result = computed(observables, function (args) { | ||
var instance = computed.extended(observables, function () { | ||
var index = 0 | ||
for (var i = 0; i < arguments.length; i++) { | ||
for (var x = 0; x < arguments[i].length; x++) { | ||
var value = arguments[i][x] | ||
var raw = get(observables[i], x) | ||
forEach(observables, function (collection) { | ||
forEach(collection, function (item) { | ||
var value = resolve(item) | ||
values[index] = value | ||
rawValues[index] = raw | ||
rawValues[index] = item | ||
index += 1 | ||
} | ||
} | ||
}) | ||
}) | ||
values.length = index | ||
@@ -24,31 +27,20 @@ rawValues.length = index | ||
result.get = function (index) { | ||
return rawValues[index] | ||
var result = function MutantConcat (listener) { | ||
return instance(listener) | ||
} | ||
result.getLength = function (index) { | ||
return rawValues.length | ||
} | ||
// getLength, get, indexOf, etc | ||
addCollectionMethods(result, rawValues, instance.checkUpdated) | ||
result.includes = function (valueOrObs) { | ||
return !!~rawValues.indexOf(valueOrObs) | ||
} | ||
result.indexOf = function (valueOrObs) { | ||
return rawValues.indexOf(valueOrObs) | ||
} | ||
return result | ||
} | ||
function get (target, index) { | ||
if (typeof target === 'function' && !target.get) { | ||
target = target() | ||
function forEach (sources, fn) { | ||
if (sources && !sources.forEach) { | ||
sources = resolve(sources) | ||
} | ||
if (Array.isArray(target)) { | ||
return target[index] | ||
} else if (target && target.get) { | ||
return target.get(index) | ||
if (sources && sources.forEach) { | ||
sources.forEach(fn) | ||
} | ||
} |
60
dict.js
@@ -0,4 +1,6 @@ | ||
var Value = require('./value') | ||
var LazyWatcher = require('./lib/lazy-watcher') | ||
var isReferenceType = require('./lib/is-reference-type') | ||
var isSame = require('./lib/is-same') | ||
var resolve = require('./resolve') | ||
var isObservable = require('./is-observable') | ||
@@ -9,7 +11,10 @@ // TODO: reimplement using LazyWatcher | ||
function Dict (defaultValues) { | ||
function Dict (defaultValues, opts) { | ||
var object = Object.create({}) | ||
var sources = [] | ||
var releases = [] | ||
var fixedIndexing = opts && opts.fixedIndexing || false | ||
var comparer = opts && opts.comparer || null | ||
var binder = LazyWatcher(update, listen, unlisten) | ||
@@ -32,4 +37,6 @@ binder.value = object | ||
observable.put = function (key, valueOrObs) { | ||
valueOrObs = getObsValue(valueOrObs) | ||
put(key, valueOrObs) | ||
binder.broadcast() | ||
return valueOrObs | ||
} | ||
@@ -68,14 +75,34 @@ | ||
observable.set = function (values) { | ||
Object.keys(sources).forEach(function (key) { | ||
tryInvoke(releases[key]) | ||
delete sources[key] | ||
delete releases[key] | ||
delete object[key] | ||
}) | ||
var keys = values && Object.keys(values) || [] | ||
if (fixedIndexing) { | ||
keys.forEach(function (key) { | ||
if (sources[key]) { | ||
sources[key].set(values[key]) | ||
} else { | ||
put(key, getObsValue(values[key])) | ||
} | ||
}) | ||
Object.keys(values).forEach(function (key) { | ||
put(key, values[key]) | ||
}) | ||
Object.keys(sources).forEach(function (key) { | ||
if (!keys.includes(key)) { | ||
tryInvoke(releases[key]) | ||
delete sources[key] | ||
delete releases[key] | ||
delete object[key] | ||
} | ||
}) | ||
} else { | ||
Object.keys(sources).forEach(function (key) { | ||
tryInvoke(releases[key]) | ||
delete sources[key] | ||
delete releases[key] | ||
delete object[key] | ||
}) | ||
binder.broadcast() | ||
keys.forEach(function (key) { | ||
put(key, values[key]) | ||
}) | ||
binder.broadcast() | ||
} | ||
} | ||
@@ -87,2 +114,9 @@ | ||
function getObsValue (valueOrObs) { | ||
if (fixedIndexing && !isObservable(valueOrObs)) { | ||
valueOrObs = Value(valueOrObs) | ||
} | ||
return valueOrObs | ||
} | ||
function put (key, valueOrObs) { | ||
@@ -123,3 +157,3 @@ tryInvoke(releases[key]) | ||
var newValue = resolve(sources[key]) | ||
if (newValue !== object[key] || isReferenceType(newValue)) { | ||
if (!isSame(newValue, object[key], comparer)) { | ||
object[key] = newValue | ||
@@ -126,0 +160,0 @@ changed = true |
@@ -15,6 +15,6 @@ var isObservable = require('../is-observable') | ||
var valueOrObs = properties[key] | ||
var value = resolve(valueOrObs) | ||
if (key === 'style') { | ||
// TODO: handle observable at root for style objects | ||
var value = resolve(valueOrObs) | ||
for (var k in value) { | ||
@@ -29,2 +29,3 @@ var styleObs = isObservable(value[k]) ? value[k] : null | ||
} else if (key === 'hooks') { | ||
var value = resolve(valueOrObs) | ||
if (Array.isArray(value)) { | ||
@@ -36,2 +37,3 @@ value.forEach(function (v) { | ||
} else if (key === 'attributes') { | ||
var value = resolve(valueOrObs) | ||
for (var k in value) { | ||
@@ -46,7 +48,7 @@ var attrObs = isObservable(value[k]) ? value[k] : null | ||
} else if (key === 'events') { | ||
for (var name in value) { | ||
target.addEventListener(name, value[name], false) | ||
for (var name in valueOrObs) { | ||
target.addEventListener(name, valueOrObs[name], false) | ||
} | ||
} else if (key.slice(0, 3) === 'ev-') { | ||
target.addEventListener(key.slice(3), value, false) | ||
target.addEventListener(key.slice(3), valueOrObs, false) | ||
} else if (key === 'className' || key === 'classList') { | ||
@@ -61,3 +63,3 @@ if (Array.isArray(valueOrObs)) { | ||
} else { | ||
target[key] = value | ||
target[key] = resolve(valueOrObs) | ||
var obs = isObservable(valueOrObs) ? valueOrObs : null | ||
@@ -64,0 +66,0 @@ if (obs) { |
@@ -17,3 +17,3 @@ module.exports = function (update, onBind, onUnbind) { | ||
getValue: function () { | ||
checkUpdated: function () { | ||
if (!obj.live || lazy) { | ||
@@ -23,2 +23,6 @@ lazy = false | ||
} | ||
}, | ||
getValue: function () { | ||
obj.checkUpdated() | ||
return obj.value | ||
@@ -25,0 +29,0 @@ }, |
98
map.js
var resolve = require('./resolve') | ||
var LazyWatcher = require('./lib/lazy-watcher') | ||
var isReferenceType = require('./lib/is-reference-type') | ||
var isSame = require('./lib/is-same') | ||
var addCollectionMethods = require('./lib/add-collection-methods') | ||
@@ -8,6 +9,12 @@ module.exports = Map | ||
function Map (obs, lambda, opts) { | ||
// opts: comparer, maxTime, onRemove | ||
var comparer = opts && opts.comparer || null | ||
var releases = [] | ||
var invalidateReleases = new global.WeakMap() | ||
var binder = LazyWatcher(update, listen, unlisten) | ||
var lastValues = new global.Map() | ||
var rawSet = new global.Set() | ||
var items = [] | ||
@@ -35,18 +42,4 @@ | ||
result.get = function (index) { | ||
return raw[index] | ||
} | ||
addCollectionMethods(result, raw, binder.checkUpdated) | ||
result.getLength = function (index) { | ||
return raw.length | ||
} | ||
result.includes = function (valueOrObs) { | ||
return !!~raw.indexOf(valueOrObs) | ||
} | ||
result.indexOf = function (valueOrObs) { | ||
return raw.indexOf(valueOrObs) | ||
} | ||
return result | ||
@@ -61,2 +54,6 @@ | ||
rebindAll() | ||
if (opts && opts.onListen) { | ||
opts.onListen() | ||
} | ||
} | ||
@@ -69,3 +66,6 @@ | ||
rebindAll() | ||
lastValues.clear() | ||
if (opts && opts.onUnlisten) { | ||
opts.onUnlisten() | ||
} | ||
} | ||
@@ -87,3 +87,3 @@ | ||
if (typeof item === 'object' || item !== currentItem) { | ||
if (!isSame(item, currentItem, comparer)) { | ||
if (maxTime && Date.now() - startedAt > maxTime) { | ||
@@ -100,10 +100,12 @@ queueUpdateItem(i) | ||
// clean up cache | ||
var oldLength = items.length | ||
var oldLength = raw.length | ||
var newLength = getLength(obs) | ||
Array.from(lastValues.keys()).filter(notIncluded, obs).forEach(deleteEntry, lastValues) | ||
items.length = getLength(obs) | ||
values.length = items.length | ||
raw.length = items.length | ||
for (var index = items.length; index < oldLength; index++) { | ||
items.length = newLength | ||
values.length = newLength | ||
raw.length = newLength | ||
for (var index = newLength; index < oldLength; index++) { | ||
rebind(index) | ||
} | ||
Array.from(rawSet.values()).filter(notIncluded, raw).forEach(notifyRemoved) | ||
} | ||
@@ -134,14 +136,50 @@ | ||
function invalidateOn (item, obs) { | ||
if (!invalidateReleases.has(item)) { | ||
invalidateReleases.set(item, []) | ||
} | ||
invalidateReleases.get(item).push(obs(invalidate.bind(null, item))) | ||
} | ||
function addInvalidateCallback (item) { | ||
return invalidateOn.bind(null, item) | ||
} | ||
function notifyRemoved (item) { | ||
rawSet.delete(item) | ||
invalidateReleases.delete(item) | ||
if (opts && opts.onRemove) { | ||
opts.onRemove(item) | ||
} | ||
} | ||
function invalidate (item) { | ||
var changed = false | ||
var length = getLength(obs) | ||
lastValues.delete(item) | ||
for (var i = 0; i < length; i++) { | ||
if (get(obs, i) === item) { | ||
updateItem(i) | ||
changed = true | ||
} | ||
} | ||
if (changed) { | ||
binder.broadcast() | ||
} | ||
} | ||
function updateItem (i) { | ||
var item = get(obs, i) | ||
if (isReferenceType(item)) { | ||
raw[i] = lambda(item) | ||
if (!lastValues.has(item) || !isSame(item, item, comparer)) { | ||
var newValue = lambda(item, addInvalidateCallback(item)) | ||
if (newValue !== raw[i]) { | ||
raw[i] = newValue | ||
} | ||
rawSet.add(newValue) | ||
lastValues.set(item, raw[i]) | ||
rebind(i) | ||
} else { | ||
if (!lastValues.has(item)) { | ||
lastValues.set(item, lambda(item)) | ||
} | ||
raw[i] = lastValues.get(item) | ||
} | ||
values[i] = resolve(raw[i]) | ||
rebind(i) | ||
} | ||
@@ -170,3 +208,3 @@ | ||
return obs(function (value) { | ||
if (values[index] !== value || typeof value === 'object') { | ||
if (!isSame(values[index], value, comparer)) { | ||
values[index] = value | ||
@@ -173,0 +211,0 @@ binder.broadcast() |
{ | ||
"name": "@mmckegg/mutant", | ||
"version": "2.2.1", | ||
"version": "3.0.0", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "array.js", |
@@ -25,3 +25,4 @@ mutant | ||
- `MutationObserver` (optional, only for root `html-element` binding support) | ||
- ES5 arrays | ||
- ES5 arrays (`Array.prototype.forEach`, etc) | ||
- `Array.prototype.includes` | ||
@@ -28,0 +29,0 @@ ## Use |
var Value = require('./value') | ||
var LazyWatcher = require('./lib/lazy-watcher') | ||
var isReferenceType = require('./lib/is-reference-type') | ||
var isSame = require('./lib/is-same') | ||
@@ -13,3 +13,3 @@ module.exports = Struct | ||
function Struct (properties) { | ||
function Struct (properties, opts) { | ||
var object = Object.create({}) | ||
@@ -20,2 +20,4 @@ var releases = [] | ||
var comparer = opts && opts.comparer || null | ||
var observable = function MutantStruct (listener) { | ||
@@ -77,3 +79,3 @@ if (!listener) { | ||
releases.push(obs(function (val) { | ||
if (val !== object[key] || isReferenceType(val)) { | ||
if (!isSame(val, object[key], comparer)) { | ||
object[key] = val | ||
@@ -98,3 +100,3 @@ if (!suspendBroadcast) { | ||
var newValue = observable[key]() | ||
if (newValue !== object[key] || isReferenceType(newValue)) { | ||
if (!isSame(newValue, object[key], comparer)) { | ||
object[key] = observable[key]() | ||
@@ -101,0 +103,0 @@ changed = true |
19
value.js
module.exports = Observable | ||
function Observable (value) { | ||
function Observable (value, opts) { | ||
var listeners = [] | ||
value = value === undefined ? null : value | ||
value = getValue(value, opts) | ||
observable.set = function (v) { | ||
value = v | ||
value = getValue(v, opts) | ||
var cachedListeners = listeners.slice(0) | ||
for (var i = 0, len = cachedListeners.length; i < len; i++) { | ||
cachedListeners[i](v) | ||
cachedListeners[i](value) | ||
} | ||
@@ -39,1 +39,12 @@ } | ||
} | ||
function getValue (value, opts) { | ||
if (value == null) { | ||
if (opts && opts.defaultValue != null) { | ||
value = opts.defaultValue | ||
} else { | ||
value = null | ||
} | ||
} | ||
return value | ||
} |
58517
41
2102
86