| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- var Emitter = require('emitter'),
- observer = require('./deps-parser').observer
- /*
- * Binding class.
- *
- * each property on the scope has one corresponding Binding object
- * which has multiple directive instances on the DOM
- * and multiple computed property dependents
- */
- function Binding (seed, key) {
- this.seed = seed
- this.key = key
- var path = key.split('.')
- this.set(getValue(seed.scope, path))
- this.defineAccessors(seed.scope, path)
- this.instances = []
- this.dependents = []
- this.dependencies = []
- }
- /*
- * Pre-process a passed in value based on its type
- */
- Binding.prototype.set = function (value) {
- var type = typeOf(value),
- self = this
- // preprocess the value depending on its type
- if (type === 'Object') {
- if (value.get || value.set) { // computed property
- self.isComputed = true
- }
- } else if (type === 'Array') {
- watchArray(value)
- value.on('mutate', function () {
- self.emitChange()
- })
- }
- this.value = value
- }
- /*
- * Define getter/setter for this binding on scope
- */
- Binding.prototype.defineAccessors = function (scope, path) {
- var self = this,
- key = path[0]
- if (path.length === 1) {
- // here we are! at the end of the path!
- // define the real value accessors.
- Object.defineProperty(scope, key, {
- get: function () {
- if (observer.isObserving) {
- observer.emit('get', self)
- }
- return self.isComputed
- ? self.value.get()
- : self.value
- },
- set: function (value) {
- if (self.isComputed) {
- // computed properties cannot be redefined
- // no need to call binding.update() here,
- // as dependency extraction has taken care of that
- if (self.value.set) {
- self.value.set(value)
- }
- } else if (value !== self.value) {
- self.value = value
- self.update(value)
- }
- }
- })
- } else {
- // we are not there yet!!!
- // create an intermediate subscope
- // which also has its own getter/setters
- var subScope = scope[key]
- if (!subScope) {
- subScope = {}
- Object.defineProperty(scope, key, {
- get: function () {
- return subScope
- },
- set: function (value) {
- // when the subScope is given a new value,
- // copy everything over to trigger the setters
- for (var prop in value) {
- subScope[prop] = value[prop]
- }
- }
- })
- }
- this.defineAccessors(subScope, path.slice(1))
- }
- }
- /*
- * Process the value, then trigger updates on all dependents
- */
- Binding.prototype.update = function (value) {
- this.set(value)
- this.instances.forEach(function (instance) {
- instance.update(value)
- })
- this.emitChange()
- }
- /*
- * Notify computed properties that depend on this binding
- * to update themselves
- */
- Binding.prototype.emitChange = function () {
- this.dependents.forEach(function (dept) {
- dept.refresh()
- })
- }
- // Helpers --------------------------------------------------------------------
- /*
- * Get a value from an object based on a path array
- */
- function getValue (scope, path) {
- if (path.length === 1) return scope[path[0]]
- var i = 0
- /* jshint boss: true */
- while (scope[path[i]]) {
- scope = scope[path[i]]
- i++
- }
- return i === path.length ? scope : undefined
- }
- /*
- * get accurate type of an object
- */
- var toString = Object.prototype.toString
- function typeOf (obj) {
- return toString.call(obj).slice(8, -1)
- }
- /*
- * augment an Array so that it emit events when mutated
- */
- var aproto = Array.prototype,
- arrayMutators = ['push','pop','shift','unshift','splice','sort','reverse'],
- arrayAugmentations = {
- remove: function (index) {
- if (typeof index !== 'number') index = index.$index
- this.splice(index, 1)
- },
- replace: function (index, data) {
- if (typeof index !== 'number') index = index.$index
- this.splice(index, 1, data)
- }
- }
- function watchArray (collection) {
- Emitter(collection)
- arrayMutators.forEach(function (method) {
- collection[method] = function () {
- var result = aproto[method].apply(this, arguments)
- collection.emit('mutate', {
- method: method,
- args: aproto.slice.call(arguments),
- result: result
- })
- }
- })
- for (var method in arrayAugmentations) {
- collection[method] = arrayAugmentations[method]
- }
- }
- module.exports = Binding
|