binding.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. var utils = require('./utils'),
  2. observer = require('./deps-parser').observer,
  3. def = Object.defineProperty
  4. /*
  5. * Binding class.
  6. *
  7. * each property on the scope has one corresponding Binding object
  8. * which has multiple directive instances on the DOM
  9. * and multiple computed property dependents
  10. */
  11. function Binding (seed, key) {
  12. this.seed = seed
  13. this.scope = seed.scope
  14. this.key = key
  15. var path = key.split('.')
  16. this.inspect(utils.getNestedValue(seed.scope, path))
  17. this.def(seed.scope, path)
  18. this.instances = []
  19. this.subs = []
  20. this.deps = []
  21. }
  22. var BindingProto = Binding.prototype
  23. /*
  24. * Pre-process a passed in value based on its type
  25. */
  26. BindingProto.inspect = function (value) {
  27. var type = utils.typeOf(value),
  28. self = this
  29. // preprocess the value depending on its type
  30. if (type === 'Object') {
  31. if (value.get) {
  32. var l = Object.keys(value).length
  33. if (l === 1 || (l === 2 && value.set)) {
  34. self.isComputed = true // computed property
  35. }
  36. }
  37. } else if (type === 'Array') {
  38. utils.watchArray(value)
  39. value.on('mutate', function () {
  40. self.pub()
  41. })
  42. }
  43. self.value = value
  44. }
  45. /*
  46. * Define getter/setter for this binding on scope
  47. * recursive for nested objects
  48. */
  49. BindingProto.def = function (scope, path) {
  50. var self = this,
  51. key = path[0]
  52. if (path.length === 1) {
  53. // here we are! at the end of the path!
  54. // define the real value accessors.
  55. def(scope, key, {
  56. get: function () {
  57. if (observer.isObserving) {
  58. observer.emit('get', self)
  59. }
  60. return self.isComputed
  61. ? self.value.get({
  62. el: self.seed.el,
  63. scope: self.seed.scope
  64. })
  65. : self.value
  66. },
  67. set: function (value) {
  68. if (self.isComputed) {
  69. // computed properties cannot be redefined
  70. // no need to call binding.update() here,
  71. // as dependency extraction has taken care of that
  72. if (self.value.set) {
  73. self.value.set(value)
  74. }
  75. } else if (value !== self.value) {
  76. self.update(value)
  77. }
  78. }
  79. })
  80. } else {
  81. // we are not there yet!!!
  82. // create an intermediate subscope
  83. // which also has its own getter/setters
  84. var subScope = scope[key]
  85. if (!subScope) {
  86. subScope = {}
  87. def(scope, key, {
  88. get: function () {
  89. return subScope
  90. },
  91. set: function (value) {
  92. // when the subScope is given a new value,
  93. // copy everything over to trigger the setters
  94. for (var prop in value) {
  95. subScope[prop] = value[prop]
  96. }
  97. }
  98. })
  99. }
  100. // recurse
  101. this.def(subScope, path.slice(1))
  102. }
  103. }
  104. /*
  105. * Process the value, then trigger updates on all dependents
  106. */
  107. BindingProto.update = function (value) {
  108. this.inspect(value)
  109. var i = this.instances.length
  110. while (i--) {
  111. this.instances[i].update(value)
  112. }
  113. this.pub()
  114. }
  115. /*
  116. * -- computed property only --
  117. * Force all instances to re-evaluate themselves
  118. */
  119. BindingProto.refresh = function () {
  120. var i = this.instances.length
  121. while (i--) {
  122. this.instances[i].refresh()
  123. }
  124. }
  125. /*
  126. * Notify computed properties that depend on this binding
  127. * to update themselves
  128. */
  129. BindingProto.pub = function () {
  130. var i = this.subs.length
  131. while (i--) {
  132. this.subs[i].refresh()
  133. }
  134. }
  135. module.exports = Binding