105 lines
3.0 KiB
CoffeeScript
105 lines
3.0 KiB
CoffeeScript
# The default `.` adapter thats comes with Rivets.js. Allows subscribing to
|
|
# properties on plain objects, implemented in ES5 natives using
|
|
# `Object.defineProperty`.
|
|
Rivets.public.adapters['.'] =
|
|
id: '_rv'
|
|
counter: 0
|
|
weakmap: {}
|
|
|
|
weakReference: (obj) ->
|
|
unless obj.hasOwnProperty @id
|
|
id = @counter++
|
|
Object.defineProperty obj, @id, value: id
|
|
|
|
@weakmap[obj[@id]] or= callbacks: {}
|
|
|
|
cleanupWeakReference: (ref, id) ->
|
|
unless Object.keys(ref.callbacks).length
|
|
unless ref.pointers and Object.keys(ref.pointers).length
|
|
delete @weakmap[id]
|
|
|
|
stubFunction: (obj, fn) ->
|
|
original = obj[fn]
|
|
map = @weakReference obj
|
|
weakmap = @weakmap
|
|
|
|
obj[fn] = ->
|
|
response = original.apply obj, arguments
|
|
|
|
for r, k of map.pointers
|
|
callback() for callback in weakmap[r]?.callbacks[k] ? []
|
|
|
|
response
|
|
|
|
observeMutations: (obj, ref, keypath) ->
|
|
if Array.isArray obj
|
|
map = @weakReference obj
|
|
|
|
unless map.pointers?
|
|
map.pointers = {}
|
|
functions = ['push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'splice']
|
|
@stubFunction obj, fn for fn in functions
|
|
|
|
map.pointers[ref] ?= []
|
|
|
|
unless keypath in map.pointers[ref]
|
|
map.pointers[ref].push keypath
|
|
|
|
unobserveMutations: (obj, ref, keypath) ->
|
|
if Array.isArray(obj) and obj[@id]?
|
|
if map = @weakmap[obj[@id]]
|
|
if pointers = map.pointers[ref]
|
|
if (idx = pointers.indexOf(keypath)) >= 0
|
|
pointers.splice idx, 1
|
|
|
|
delete map.pointers[ref] unless pointers.length
|
|
@cleanupWeakReference map, obj[@id]
|
|
|
|
observe: (obj, keypath, callback) ->
|
|
callbacks = @weakReference(obj).callbacks
|
|
|
|
unless callbacks[keypath]?
|
|
callbacks[keypath] = []
|
|
desc = Object.getOwnPropertyDescriptor obj, keypath
|
|
|
|
unless desc?.get or desc?.set
|
|
value = obj[keypath]
|
|
|
|
Object.defineProperty obj, keypath,
|
|
enumerable: true
|
|
get: -> value
|
|
set: (newValue) =>
|
|
if newValue isnt value
|
|
@unobserveMutations value, obj[@id], keypath
|
|
value = newValue
|
|
|
|
if map = @weakmap[obj[@id]]
|
|
callbacks = map.callbacks
|
|
|
|
if callbacks[keypath]
|
|
cb() for cb in callbacks[keypath].slice() when cb in callbacks[keypath]
|
|
@observeMutations newValue, obj[@id], keypath
|
|
|
|
unless callback in callbacks[keypath]
|
|
callbacks[keypath].push callback
|
|
|
|
@observeMutations obj[keypath], obj[@id], keypath
|
|
|
|
unobserve: (obj, keypath, callback) ->
|
|
if map = @weakmap[obj[@id]]
|
|
if callbacks = map.callbacks[keypath]
|
|
if (idx = callbacks.indexOf(callback)) >= 0
|
|
callbacks.splice idx, 1
|
|
|
|
unless callbacks.length
|
|
delete map.callbacks[keypath]
|
|
@unobserveMutations obj[keypath], obj[@id], keypath
|
|
|
|
@cleanupWeakReference map, obj[@id]
|
|
|
|
get: (obj, keypath) ->
|
|
obj[keypath]
|
|
|
|
set: (obj, keypath, value) ->
|
|
obj[keypath] = value
|