binding.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. var Emitter = require('emitter')
  2. /*
  3. * Binding class.
  4. *
  5. * each property on the scope has one corresponding Binding object
  6. * which has multiple directive instances on the DOM
  7. * and multiple computed property dependents
  8. */
  9. function Binding (seed, key) {
  10. this.seed = seed
  11. this.key = key
  12. this.set(seed.scope[key])
  13. this.defineAccessors(seed, key)
  14. this.instances = []
  15. this.dependents = []
  16. this.dependencies = []
  17. }
  18. /*
  19. * Pre-process a passed in value based on its type
  20. */
  21. Binding.prototype.set = function (value) {
  22. var type = typeOf(value),
  23. self = this
  24. // preprocess the value depending on its type
  25. if (type === 'Object') {
  26. if (value.get || value.set) { // computed property
  27. self.isComputed = true
  28. } else { // normal object
  29. // TODO watchObject
  30. }
  31. } else if (type === 'Array') {
  32. watchArray(value)
  33. value.on('mutate', function () {
  34. self.emitChange()
  35. })
  36. }
  37. this.value = value
  38. }
  39. /*
  40. * Define getter/setter for this binding on scope
  41. */
  42. Binding.prototype.defineAccessors = function (seed, key) {
  43. var self = this
  44. Object.defineProperty(seed.scope, key, {
  45. get: function () {
  46. seed.emit('get', key)
  47. return self.isComputed
  48. ? self.value.get()
  49. : self.value
  50. },
  51. set: function (value) {
  52. if (self.isComputed && self.value.set) {
  53. self.value.set(value)
  54. } else if (value !== self.value) {
  55. self.value = value
  56. self.update(value)
  57. }
  58. }
  59. })
  60. }
  61. /*
  62. * Process the value, then trigger updates on all dependents
  63. */
  64. Binding.prototype.update = function (value) {
  65. this.set(value)
  66. this.instances.forEach(function (instance) {
  67. instance.update(value)
  68. })
  69. this.emitChange()
  70. }
  71. /*
  72. * Notify computed properties that depend on this binding
  73. * to update themselves
  74. */
  75. Binding.prototype.emitChange = function () {
  76. this.dependents.forEach(function (dept) {
  77. dept.refresh()
  78. })
  79. }
  80. /*
  81. * get accurate type of an object
  82. */
  83. var toString = Object.prototype.toString
  84. function typeOf (obj) {
  85. return toString.call(obj).slice(8, -1)
  86. }
  87. /*
  88. * augment an Array so that it emit events when mutated
  89. */
  90. var aproto = Array.prototype,
  91. arrayMutators = ['push','pop','shift','unshift','splice','sort','reverse'],
  92. arrayAugmentations = {
  93. remove: function (index) {
  94. if (typeof index !== 'number') index = index.$index
  95. this.splice(index, 1)
  96. },
  97. replace: function (index, data) {
  98. if (typeof index !== 'number') index = index.$index
  99. this.splice(index, 1, data)
  100. }
  101. }
  102. function watchArray (collection) {
  103. Emitter(collection)
  104. arrayMutators.forEach(function (method) {
  105. collection[method] = function () {
  106. var result = aproto[method].apply(this, arguments)
  107. collection.emit('mutate', {
  108. method: method,
  109. args: aproto.slice.call(arguments),
  110. result: result
  111. })
  112. }
  113. })
  114. for (var method in arrayAugmentations) {
  115. collection[method] = arrayAugmentations[method]
  116. }
  117. }
  118. module.exports = Binding