observe.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. var Emitter = require('emitter'),
  2. utils = require('./utils'),
  3. typeOf = utils.typeOf,
  4. def = Object.defineProperty,
  5. slice = Array.prototype.slice,
  6. methods = ['push','pop','shift','unshift','splice','sort','reverse']
  7. var arrayMutators = {
  8. remove: function (index) {
  9. if (typeof index !== 'number') index = this.indexOf(index)
  10. this.splice(index, 1)
  11. },
  12. replace: function (index, data) {
  13. if (typeof index !== 'number') index = this.indexOf(index)
  14. this.splice(index, 1, data)
  15. }
  16. }
  17. methods.forEach(function (method) {
  18. arrayMutators[method] = function () {
  19. var result = Array.prototype[method].apply(this, arguments)
  20. // watch new objects - do we need this? maybe do it in each.js
  21. // var newElements
  22. // if (method === 'push' || method === 'unshift') {
  23. // newElements = arguments
  24. // } else if (method === 'splice') {
  25. // newElements = slice.call(arguments, 2)
  26. // }
  27. // if (newElements) {
  28. // var i = newElements.length
  29. // while (i--) watch(newElements[i])
  30. // }
  31. this.__observer__.emit('mutate', this.__path__, this, {
  32. method: method,
  33. args: slice.call(arguments),
  34. result: result
  35. })
  36. }
  37. })
  38. // EXTERNAL
  39. function observe (obj, path, observer) {
  40. if (isWatchable(obj)) {
  41. path = path + '.'
  42. var alreadyConverted = !!obj.__observer__
  43. if (!alreadyConverted) {
  44. var ob = new Emitter()
  45. defProtected(obj, '__observer__', ob)
  46. }
  47. obj.__observer__
  48. .on('get', function (key) {
  49. observer.emit('get', path + key)
  50. })
  51. .on('set', function (key, val) {
  52. observer.emit('set', path + key, val)
  53. })
  54. .on('mutate', function (key, val, mutation) {
  55. observer.emit('mutate', path + key, val, mutation)
  56. })
  57. if (!alreadyConverted) {
  58. watch(obj, null, ob)
  59. }
  60. }
  61. }
  62. // INTERNAL
  63. function watch (obj, path, observer) {
  64. var type = typeOf(obj)
  65. if (type === 'Object') {
  66. watchObject(obj, path, observer)
  67. } else if (type === 'Array') {
  68. watchArray(obj, path, observer)
  69. }
  70. }
  71. function watchObject (obj, path, observer) {
  72. defProtected(obj, '__values__', {})
  73. defProtected(obj, '__observer__', observer)
  74. for (var key in obj) {
  75. bind(obj, key, path, obj.__observer__)
  76. }
  77. }
  78. function watchArray (arr, path, observer) {
  79. defProtected(arr, '__path__', path)
  80. defProtected(arr, '__observer__', observer)
  81. for (var method in arrayMutators) {
  82. defProtected(arr, method, arrayMutators[method])
  83. }
  84. // var i = arr.length
  85. // while (i--) watch(arr[i])
  86. }
  87. function bind (obj, key, path, observer) {
  88. var val = obj[key],
  89. values = obj.__values__,
  90. fullKey = (path ? path + '.' : '') + key
  91. values[fullKey] = val
  92. observer.emit('set', fullKey, val)
  93. def(obj, key, {
  94. enumerable: true,
  95. get: function () {
  96. observer.emit('get', fullKey)
  97. return values[fullKey]
  98. },
  99. set: function (newVal) {
  100. values[fullKey] = newVal
  101. watch(newVal, fullKey, observer)
  102. observer.emit('set', fullKey, newVal)
  103. }
  104. })
  105. watch(val, fullKey, observer)
  106. }
  107. function defProtected (obj, key, val) {
  108. def(obj, key, {
  109. enumerable: false,
  110. configurable: false,
  111. value: val
  112. })
  113. }
  114. function isWatchable (obj) {
  115. var type = typeOf(obj)
  116. return type === 'Object' || type === 'Array'
  117. }
  118. module.exports = observe