Pārlūkot izejas kodu

fix each.js for new architecture

Evan You 12 gadi atpakaļ
vecāks
revīzija
c6c5fdb3d8
5 mainītis faili ar 143 papildinājumiem un 109 dzēšanām
  1. 1 4
      TODO.md
  2. 48 10
      examples/repeated-items.html
  3. 17 30
      src/compiler.js
  4. 65 60
      src/directives/each.js
  5. 12 5
      src/observer.js

+ 1 - 4
TODO.md

@@ -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?

+ 48 - 10
examples/repeated-items.html

@@ -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>

+ 17 - 30
src/compiler.js

@@ -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 --------------------------------------------------------------------

+ 65 - 60
src/directives/each.js

@@ -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()
-        }
-        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)
-            }
+            added = m.args.length - 2,
+            removedVMs = this.vms.splice(index, removed)
+        for (i = 0; i < removed; i++) {
+            removedVMs[i].$destroy()
         }
-        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++) {
+            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++) {
-            viewmodel = this.collection[i]
-            viewmodel.$index = i
-            this.container.insertBefore(viewmodel.$el, this.ref)
+            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)
         }
     },
 

+ 12 - 5
src/observer.js

@@ -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)
             }