Browse Source

repeat object sync and tests

Evan You 12 years ago
parent
commit
0e486a30a2

+ 47 - 7
src/directives/repeat.js

@@ -12,25 +12,35 @@ var Observer   = require('../observer'),
 var mutationHandlers = {
 
     push: function (m) {
-        var i, l = m.args.length,
+        var l = m.args.length,
             base = this.collection.length - l
-        for (i = 0; i < l; i++) {
+        for (var i = 0; i < l; i++) {
             this.buildItem(m.args[i], base + i)
+            this.updateObject(m.args[i], 1)
         }
     },
 
     pop: function () {
         var vm = this.vms.pop()
-        if (vm) vm.$destroy()
+        if (vm) {
+            vm.$destroy()
+            this.updateObject(vm.$data, -1)
+        }
     },
 
     unshift: function (m) {
-        m.args.forEach(this.buildItem, this)
+        for (var i = 0, l = m.args.length; i < l; i++) {
+            this.buildItem(m.args[i], i)
+            this.updateObject(m.args[i], 1)
+        }
     },
 
     shift: function () {
         var vm = this.vms.shift()
-        if (vm) vm.$destroy()
+        if (vm) {
+            vm.$destroy()
+            this.updateObject(vm.$data, -1)
+        }
     },
 
     splice: function (m) {
@@ -41,9 +51,11 @@ var mutationHandlers = {
             removedVMs = this.vms.splice(index, removed)
         for (i = 0, l = removedVMs.length; i < l; i++) {
             removedVMs[i].$destroy()
+            this.updateObject(removedVMs[i].$data, -1)
         }
         for (i = 0; i < added; i++) {
             this.buildItem(m.args[i + 2], index + i)
+            this.updateObject(m.args[i + 2], 1)
         }
     },
 
@@ -136,12 +148,14 @@ module.exports = {
             var method = mutation.method
             mutationHandlers[method].call(self, mutation)
             if (method !== 'push' && method !== 'pop') {
+                // update index
                 var i = arr.length
                 while (i--) {
                     arr[i].$index = i
                 }
             }
             if (method === 'push' || method === 'unshift' || method === 'splice') {
+                // recalculate dependency
                 self.changed()
             }
         }
@@ -150,13 +164,19 @@ module.exports = {
 
     update: function (collection, init) {
 
+        if (
+            collection === this.collection ||
+            collection === this.object
+        ) return
+
         if (utils.typeOf(collection) === 'Object') {
+            if (this.object) {
+                delete this.object.$repeater
+            }
             this.object = collection
             collection = objectToArray(collection)
             def(this.object, '$repeater', collection, false, true)
         }
-        
-        if (collection === this.collection) return
 
         this.reset()
         // attach an object to container to hold handlers
@@ -314,6 +334,26 @@ module.exports = {
         }
     },
 
+    /**
+     *  Sync changes in the $repeater Array
+     *  back to the represented Object
+     */
+    updateObject: function (data, action) {
+        if (this.object && data.$key) {
+            var key = data.$key,
+                val = data.$value || data
+            if (action > 0) { // new property
+                // make key ienumerable
+                delete data.$key
+                def(data, '$key', key, false, true)
+                this.object[key] = val
+            } else {
+                delete this.object[key]
+            }
+            this.object.__observer__.emit('set', key, val, true)
+        }
+    },
+
     reset: function (destroyAll) {
         if (this.childId) {
             delete this.vm.$[this.childId]

+ 58 - 0
test/functional/fixtures/repeat-object.html

@@ -0,0 +1,58 @@
+<div id="test">
+    <ul>
+        <li class="primitive" v-repeat="primitive">{{$key}} {{$value}}</li>
+    </ul>
+    <ul>
+        <li class="obj" v-repeat="obj">{{$key}} {{msg}}</li>
+    </ul>
+    <button id="push" v-on="click:t1">push to primitive</button>
+    <button id="pop" v-on="click:t2">pop from primitive</button>
+    <button id="shift" v-on="click:t3">shift from object</button>
+    <button id="unshift" v-on="click:t4">unshift to object</button>
+    <button id="splice" v-on="click:t5">splice in object</button>
+    <p id="primitive">{{primitive}}</p>
+    <p id="obj">{{obj}}</p>
+</div>
+
+<script src="../../../dist/vue.js"></script>
+<script>
+var test = new Vue({
+    el: '#test',
+    data: {
+        primitive: {
+            a: 1,
+            b: 2
+        },
+        obj: {
+            a: { msg: 'hi!' },
+            b: { msg: 'ha!' }
+        }
+    },
+    methods: {
+        t1: function () {
+            this.primitive.$repeater.push({
+                $key: 'c',
+                $value: 3
+            })  
+        },
+        t2: function () {
+            this.primitive.$repeater.pop()
+        },
+        t3: function () {
+            this.obj.$repeater.shift()
+        },
+        t4: function () {
+            this.obj.$repeater.unshift({
+                $key: 'c',
+                msg: 'ho!'
+            })
+        },
+        t5: function () {
+            this.obj.$repeater.splice(1, 1, {
+                $key: 'd',
+                msg: 'he!'
+            })
+        }
+    }
+})
+</script>

+ 45 - 0
test/functional/specs/repeat-object.js

@@ -0,0 +1,45 @@
+casper.test.begin('Repeat properties of an Object', 24, function (test) {
+    
+    casper
+    .start('./fixtures/repeat-object.html')
+    .then(function () {
+        test.assertElementCount('.primitive', 2)
+        test.assertElementCount('.obj', 2)
+        test.assertSelectorHasText('.primitive:nth-child(1)', 'a 1')
+        test.assertSelectorHasText('.primitive:nth-child(2)', 'b 2')
+        test.assertSelectorHasText('.obj:nth-child(1)', 'a hi!')
+        test.assertSelectorHasText('.obj:nth-child(2)', 'b ha!')
+        test.assertSelectorHasText('#primitive', '{"a":1,"b":2}')
+        test.assertSelectorHasText('#obj', '{"a":{"msg":"hi!"},"b":{"msg":"ha!"}}')
+    })
+    .thenClick('#push', function () {
+        test.assertElementCount('.primitive', 3)
+        test.assertSelectorHasText('.primitive:nth-child(3)', 'c 3')
+        test.assertSelectorHasText('#primitive', '{"a":1,"b":2,"c":3}')
+    })
+    .thenClick('#pop', function () {
+        test.assertElementCount('.primitive', 2)
+        test.assertSelectorHasText('#primitive', '{"a":1,"b":2}')
+    })
+    .thenClick('#shift', function () {
+        test.assertElementCount('.obj', 1)
+        test.assertSelectorHasText('.obj:nth-child(1)', 'b ha!')
+        test.assertSelectorHasText('#obj', '{"b":{"msg":"ha!"}}')
+    })
+    .thenClick('#unshift', function () {
+        test.assertElementCount('.obj', 2)
+        test.assertSelectorHasText('.obj:nth-child(1)', 'c ho!')
+        test.assertSelectorHasText('.obj:nth-child(2)', 'b ha!')
+        test.assertSelectorHasText('#obj', '{"b":{"msg":"ha!"},"c":{"msg":"ho!"}}')
+    })
+    .thenClick('#splice', function () {
+        test.assertElementCount('.obj', 2)
+        test.assertSelectorHasText('.obj:nth-child(1)', 'c ho!')
+        test.assertSelectorHasText('.obj:nth-child(2)', 'd he!')
+        test.assertSelectorHasText('#obj', '{"c":{"msg":"ho!"},"d":{"msg":"he!"}}')
+    })
+    .run(function () {
+        test.done()
+    })
+
+})