fix each.js for new architecture

This commit is contained in:
Evan You 2013-08-23 01:04:26 -04:00
parent 77ce1fc355
commit c6c5fdb3d8
5 changed files with 143 additions and 109 deletions

View File

@ -1,8 +1,5 @@
- fix architecture: objects as single source of truth...
- auto add .length binding for Arrays
- $watch / $unwatch & $destroy(now much easier)
- $watch / $unwatch (now much easier)
- add a few util methods, e.g. extend, inherits, traverse
- prototypal scope/binding inheritance (Object.create)
- tests
- docs
- sd-with?

View File

@ -10,6 +10,17 @@
<li sd-each="item:items" sd-text="item.title"></li>
</ul>
<p>Total items: {{items.length}}</p>
<p>
<button sd-on="click:push">push</button>
<button sd-on="click:pop">pop</button>
<button sd-on="click:shift">shift</button>
<button sd-on="click:unshift">unshift</button>
<button sd-on="click:splice">splice</button>
<button sd-on="click:remove">remove</button>
<button sd-on="click:replace">replace</button>
<button sd-on="click:sort">sort</button>
<button sd-on="click:reverse">reverse</button>
</p>
</div>
<script src="../dist/seed.js"></script>
<script>
@ -17,24 +28,51 @@
seed.config({debug: true})
var items = [
{ title: 'hi' },
{ title: 'ha' },
{ title: 'hu' },
{ title: 'ho' },
{ title: 'he' }
]
var test = {
test: [1, 2, 3]
}
var demo = new seed.ViewModel({
el: '#app',
data: {
items: items,
test: test
push: function () {
this.items.push({ title: randomChar() })
},
pop: function () {
this.items.pop()
},
shift: function () {
this.items.shift()
},
unshift: function () {
this.items.unshift({ title: randomChar() })
},
splice: function () {
this.items.splice(0, 1, { title: randomChar() }, { title: randomChar() })
},
replace: function () {
this.items.replace(randomPos(), { title: randomChar() })
},
remove: function () {
this.items.remove(randomPos())
},
sort: function () {
this.items.sort(function (a, b) {
return a.title.charCodeAt(0) - b.title.charCodeAt(0)
})
},
reverse: function () {
this.items.reverse()
}
}
})
function randomChar () {
return String.fromCharCode(Math.floor(Math.random() * 30 + 50))
}
function randomPos () {
return Math.floor(Math.random() * items.length)
}
</script>
</body>
</html>

View File

@ -73,7 +73,6 @@ function Compiler (vm, options) {
var observables = this.observables = []
var computed = this.computed = [] // computed props to parse deps from
var ctxBindings = this.contextBindings = [] // computed props with dynamic context
var arrays = this.arrays = []
// prototypal inheritance of bindings
var parent = this.parentCompiler
@ -114,12 +113,6 @@ function Compiler (vm, options) {
binding = observables[i]
Observer.observe(binding.value, binding.key, this.observer)
}
// emit set events for array lengths
i = arrays.length
while (i--) {
binding = arrays[i]
this.observer.emit('set', binding.key + '.length', binding.value.length)
}
// extract dependencies for computed properties
if (computed.length) DepsParser.parse(computed)
// extract dependencies for computed properties with dynamic context
@ -148,17 +141,14 @@ CompilerProto.setupObserver = function () {
// add own listeners which trigger binding updates
observer
.on('get', function (key) {
console.log('get ' + key)
if (bindings[key] && depsOb.isObserving) {
depsOb.emit('get', bindings[key])
}
})
.on('set', function (key, val) {
console.log('set ' + key)
if (bindings[key]) bindings[key].update(val)
})
.on('mutate', function (key) {
console.log('mutate ' + key)
if (bindings[key]) bindings[key].pub()
})
}
@ -363,7 +353,9 @@ CompilerProto.ensurePath = function (key) {
obj = obj[sec]
i++
}
obj[path[i]] = obj[path[i]] || undefined
if (utils.typeOf(obj) === 'Object') {
obj[path[i]] = obj[path[i]] || undefined
}
}
/*
@ -379,24 +371,17 @@ CompilerProto.define = function (key, binding) {
value = binding.value = vm[key], // save the value before redefinening it
type = utils.typeOf(value)
if (type === 'Object') {
if (value.get) {// computed property
binding.isComputed = true
binding.rawGet = value.get
value.get = value.get.bind(vm)
this.computed.push(binding)
} else {
// observe objects later, becase there might be more keys
// to be added to it. we also want to emit all the set events
// when values are available.
this.observables.push(binding)
}
} else if (type === 'Array') {
// observe arrays right now, because they will be needed in
// sd-each directives.
Observer.observe(value, key, compiler.observer)
// we need to later emit set event for the arrays length.
this.arrays.push(binding)
if (type === 'Object' && value.get) {
// computed property
binding.isComputed = true
binding.rawGet = value.get
value.get = value.get.bind(vm)
this.computed.push(binding)
} else if (type === 'Object' || type === 'Array') {
// observe objects later, becase there might be more keys
// to be added to it. we also want to emit all the set events
// after all values are available.
this.observables.push(binding)
}
Object.defineProperty(vm, key, {
@ -488,7 +473,9 @@ CompilerProto.destroy = function () {
}
}
// remove el
el.parentNode.removeChild(el)
if (el.parentNode) {
el.parentNode.removeChild(el)
}
}
// Helpers --------------------------------------------------------------------

View File

@ -1,5 +1,7 @@
var config = require('../config'),
utils = require('../utils'),
var config = require('../config'),
utils = require('../utils'),
Observer = require('../observer'),
Emitter = require('emitter'),
ViewModel // lazy def to avoid circular dependency
/*
@ -10,68 +12,73 @@ var mutationHandlers = {
push: function (m) {
var i, l = m.args.length,
baseIndex = this.collection.length - l
base = this.collection.length - l
for (i = 0; i < l; i++) {
this.buildItem(this.ref, m.args[i], baseIndex + i)
this.buildItem(m.args[i], base + i)
}
},
pop: function (m) {
m.result.$destroy()
pop: function () {
this.vms.pop().$destroy()
},
unshift: function (m) {
var i, l = m.args.length, ref
var i, l = m.args.length
for (i = 0; i < l; i++) {
ref = this.collection.length > l
? this.collection[l].$el
: this.ref
this.buildItem(ref, m.args[i], i)
this.buildItem(m.args[i], i)
}
this.updateIndexes()
},
shift: function (m) {
m.result.$destroy()
this.updateIndexes()
shift: function () {
this.vms.shift().$destroy()
},
splice: function (m) {
var i, pos, ref,
l = m.args.length,
k = m.result.length,
index = m.args[0],
var i,
index = m.args[0],
removed = m.args[1],
added = l - 2
for (i = 0; i < k; i++) {
m.result[i].$destroy()
added = m.args.length - 2,
removedVMs = this.vms.splice(index, removed)
for (i = 0; i < removed; i++) {
removedVMs[i].$destroy()
}
if (added > 0) {
for (i = 2; i < l; i++) {
pos = index - removed + added + 1
ref = this.collection[pos]
? this.collection[pos].$el
: this.ref
this.buildItem(ref, m.args[i], index + i)
}
}
if (removed !== added) {
this.updateIndexes()
for (i = 0; i < added; i++) {
this.buildItem(m.args[i + 2], index + i)
}
},
sort: function () {
var i, l = this.collection.length, viewmodel
var key = this.arg,
vms = this.vms,
col = this.collection,
l = col.length,
sorted = new Array(l),
i, j, vm, data
for (i = 0; i < l; i++) {
viewmodel = this.collection[i]
viewmodel.$index = i
this.container.insertBefore(viewmodel.$el, this.ref)
data = col[i]
for (j = 0; j < l; j++) {
vm = vms[j]
if (vm[key] === data) {
sorted[i] = vm
break
}
}
}
for (i = 0; i < l; i++) {
this.container.insertBefore(sorted[i].$el, this.ref)
}
this.vms = sorted
},
reverse: function () {
var vms = this.vms
vms.reverse()
for (var i = 0, l = vms.length; i < l; i++) {
this.container.insertBefore(vms[i].$el, this.ref)
}
}
}
//mutationHandlers.reverse = mutationHandlers.sort
module.exports = {
bind: function () {
@ -83,9 +90,10 @@ module.exports = {
ctn.removeChild(this.el)
this.collection = null
this.vms = null
this.mutationListener = (function (path, arr, mutation) {
mutationHandlers[mutation.method].call(this, mutation)
}).bind(this)
var self = this
this.mutationListener = function (path, arr, mutation) {
mutationHandlers[mutation.method].call(self, mutation)
}
},
update: function (collection) {
@ -97,28 +105,28 @@ module.exports = {
// force a compile so that we get all the bindings for
// dependency extraction.
if (!this.collection && !collection.length) {
this.buildItem(this.ref, null, true)
this.buildItem()
}
this.collection = collection
this.vms = []
// listen for collection mutation events
// the collection has been augmented during Binding.set()
if (!collection.__observer__) Observer.watchArray(collection, null, new Emitter())
collection.__observer__.on('mutate', this.mutationListener)
// this.compiler.observer.emit('set', this.key + '.length', collection.length)
// create child-seeds and append to DOM
for (var i = 0, l = collection.length; i < l; i++) {
var item = this.buildItem(this.ref, collection[i])
this.container.appendChild(item.$el)
this.vms.push(item)
this.buildItem(collection[i], i)
}
},
buildItem: function (ref, data, dummy) {
var node = this.el.cloneNode(true)
this.container.insertBefore(node, ref)
buildItem: function (data, index) {
ViewModel = ViewModel || require('../viewmodel')
var vmID = node.getAttribute(config.prefix + '-viewmodel'),
var node = this.el.cloneNode(true),
ctn = this.container,
vmID = node.getAttribute(config.prefix + '-viewmodel'),
ChildVM = utils.getVM(vmID) || ViewModel,
wrappedData = {}
wrappedData[this.arg] = data
@ -127,20 +135,17 @@ module.exports = {
each: true,
eachPrefix: this.arg,
parentCompiler: this.compiler,
delegator: this.container,
delegator: ctn,
data: wrappedData
})
if (dummy) {
if (!data) {
item.$destroy()
} else {
return item
}
},
updateIndexes: function () {
var i = this.collection.length
while (i--) {
this.collection[i].$index = i
var ref = this.vms.length > index
? this.vms[index].$el
: this.ref
ctn.insertBefore(node, ref)
this.vms.splice(index, 0, item)
}
},

View File

@ -45,7 +45,7 @@ function watchObject (obj, path, observer) {
}
function watchArray (arr, path, observer) {
defProtected(arr, '__path__', path)
if (path) defProtected(arr, '__path__', path)
defProtected(arr, '__observer__', observer)
for (var method in arrayMutators) {
defProtected(arr, method, arrayMutators[method])
@ -93,14 +93,21 @@ function isWatchable (obj) {
}
function emitSet (obj, observer) {
var values = obj.__values__
for (var key in values) {
observer.emit('set', key, values[key])
if (typeOf(obj) === 'Array') {
observer.emit('set', 'length', obj.length)
} else {
var values = obj.__values__
for (var key in values) {
observer.emit('set', key, values[key])
}
}
}
module.exports = {
// used in sd-each
watchArray: watchArray,
observe: function (obj, rawPath, observer) {
if (isWatchable(obj)) {
var path = rawPath + '.',
@ -133,7 +140,7 @@ module.exports = {
.on('set', proxies.set)
.on('mutate', proxies.mutate)
if (alreadyConverted) {
emitSet(obj, ob)
emitSet(obj, ob, rawPath)
} else {
watch(obj, null, ob)
}