Evan You 12 年之前
父節點
當前提交
10a0cc1838
共有 7 個文件被更改,包括 203 次插入40 次删除
  1. 2 1
      component.json
  2. 10 1
      src/compiler.js
  3. 1 0
      src/directives/index.js
  4. 56 0
      src/directives/view.js
  5. 8 4
      src/directives/with.js
  6. 92 19
      test/functional/fixtures/routing.html
  7. 34 15
      test/functional/specs/routing.js

+ 2 - 1
component.json

@@ -30,6 +30,7 @@
         "src/directives/with.js",
         "src/directives/html.js",
         "src/directives/style.js",
-        "src/directives/partial.js"
+        "src/directives/partial.js",
+        "src/directives/view.js"
     ]
 }

+ 10 - 1
src/compiler.js

@@ -336,6 +336,7 @@ CompilerProto.compile = function (node, root) {
 
         // special attributes to check
         var repeatExp,
+            viewExp,
             withExp,
             directive,
             // resolve a standalone child component with no inherited data
@@ -361,6 +362,13 @@ CompilerProto.compile = function (node, root) {
                 compiler.deferred.push(directive)
             }
 
+        } else if (viewExp = utils.attr(node, 'view')) {
+
+            directive = Directive.parse('view', viewExp, compiler, node)
+            if (directive) {
+                compiler.deferred.push(directive)
+            }
+
         // Child component has 2nd highest priority
         } else if (root !== true && ((withExp = utils.attr(node, 'with')) || hasComponent)) {
 
@@ -377,8 +385,9 @@ CompilerProto.compile = function (node, root) {
 
         } else {
 
-            // compile normal directives
+            // remove the component directive
             utils.attr(node, 'component')
+            // compile normal directives
             compiler.compileNode(node)
 
         }

+ 1 - 0
src/directives/index.js

@@ -12,6 +12,7 @@ module.exports = {
     html      : require('./html'),
     style     : require('./style'),
     partial   : require('./partial'),
+    view      : require('./view'),
 
     attr: function (value) {
         if (value || value === 0) {

+ 56 - 0
src/directives/view.js

@@ -0,0 +1,56 @@
+module.exports = {
+
+    bind: function () {
+
+        // track position in DOM with a ref node
+        var el       = this.raw = this.el,
+            parent   = el.parentNode,
+            ref      = this.ref = document.createComment('v-view')
+        parent.insertBefore(ref, el)
+        parent.removeChild(el)
+
+        // cache original content
+        /* jshint boss: true */
+        var node,
+            frag = this.inner = document.createDocumentFragment()
+        while (node = el.firstChild) {
+            frag.appendChild(node)
+        }
+
+    },
+
+    update: function(value) {
+
+        if (this.childVM) {
+            this.childVM.$destroy()
+        }
+
+        var Ctor  = this.compiler.getOption('components', value)
+        if (!Ctor) return
+
+        var inner = this.inner.cloneNode(true)
+
+        this.childVM = new Ctor({
+            el: this.raw.cloneNode(true),
+            parent: this.vm,
+            created: function () {
+                this.$compiler.rawContent = inner
+            }
+        })
+
+        this.el = this.childVM.$el
+        if (this.compiler.init) {
+            this.ref.parentNode.insertBefore(this.el, this.ref)
+        } else {
+            this.childVM.$before(this.ref)
+        }
+
+    },
+
+    unbind: function() {
+        if (this.childVM) {
+            this.childVM.$destroy()
+        }
+    }
+
+}

+ 8 - 4
src/directives/with.js

@@ -1,4 +1,4 @@
-var nextTick = require('../utils').nextTick
+var utils = require('../utils')
 
 module.exports = {
 
@@ -6,7 +6,7 @@ module.exports = {
         if (this.el.vue_vm) {
             this.subVM = this.el.vue_vm
             var compiler = this.subVM.$compiler
-            if (!compiler.bindings[this.arg]) {
+            if (this.arg && !compiler.bindings[this.arg]) {
                 compiler.createBinding(this.arg)
             }
         } else if (this.isEmpty) {
@@ -55,6 +55,8 @@ module.exports = {
                 delayReady: !this.last
             }
         })
+        // mark that this VM is created by v-with
+        utils.defProtected(this.subVM, '$with', true)
     },
 
     /**
@@ -69,7 +71,7 @@ module.exports = {
         this.subVM.$compiler.observer.on('change:' + this.arg, function (val) {
             if (!self.lock) {
                 self.lock = true
-                nextTick(function () {
+                utils.nextTick(function () {
                     self.lock = false
                 })
             }
@@ -80,7 +82,9 @@ module.exports = {
     unbind: function () {
         // all watchers are turned off during destroy
         // so no need to worry about it
-        this.subVM.$destroy()
+        if (this.subVM.$with) {
+            this.subVM.$destroy()
+        }
     }
 
 }

+ 92 - 19
test/functional/fixtures/routing.html

@@ -1,28 +1,101 @@
-<div v-if="route.hi">Hi! <a href="#ho">Next</a></div>
-<div v-if="route.ho">Ho! <a href="#ha">Next</a></div>
-<div v-if="route.ha">Ha! <a href="#hi">Next</a></div>
-
 <script src="../../../dist/vue.js"></script>
+<style type="text/css">
+    body {
+        padding: 20px;
+        font-family: 'Helvetica Neue', Arial, sans-serif;
+    }
+    ul {
+        list-style-type: none;
+        padding: 0;
+    }
+    li {
+        display: inline-block;
+        margin-right: 10px;
+    }
+    a {
+        color: #999;
+        text-decoration: none;
+    }
+    a.current {
+        color: blue;
+    }
+    .view {
+        position: absolute;
+        opacity: 1;
+        -webkit-transition: all .2s ease;
+        transition: all .2s ease;
+    }
+    .v-enter {
+        opacity: 0;
+        -webkit-transform: translate3d(30px, 0, 0);
+        transform: translate3d(30px, 0, 0);
+    }
+    .v-leave {
+        opacity: 0;
+        -webkit-transform: translate3d(-30px, 0, 0);
+        transform: translate3d(-30px, 0, 0);
+    }
+</style>
+
+<div>
+    <ul>
+        <li v-repeat="routes">
+            <a href="#!/{{$value}}" v-class="current:currentView == $value">{{$value}}</a>
+        </li>
+    </ul>
+    <div v-view="currentView" class="view" v-transition>
+        <p>Hello! {{msg}}</p>
+    </div>
+</div>
+
 <script>
 
-    var route = {
-        hi: false,
-        ho: false,
-        ha: false
+Vue.component('home', {
+    template: '<h1>Home</h1><div class="content">{{>yield}}</div>',
+    created: function () {
+        this.msg = "Home sweet home!"
     }
+})
 
-    window.addEventListener('hashchange', updateRoute)
-    function updateRoute () {
-        var path = location.hash.slice(1) || 'hi'
-        for (var key in route) {
-            route[key] = key === path
-        }
+Vue.component('page1', {
+    template: '<h1>Page1</h1><div class="content">{{>yield}}</div>',
+    created: function () {
+        this.msg = "Welcome to page 1!"
     }
+})
+
+Vue.component('page2', {
+    template: '<h1>Page2</h1><div class="content">{{>yield}}</div>',
+    created: function () {
+        this.msg = "Welcome to page 2!"
+    }
+})
+
+Vue.component('notfound', {
+    template: '<h1>404 yo</h1>'
+})
 
-    var app = new Vue({
-        el: 'body'
-    })
-    app.route = route
+// simple routing
+var routes = ['home', 'page1', 'page2']
+
+function getRoute () {
+    var path = location.hash.replace(/^#!\/?/, '') || 'home'
+    return routes.indexOf(path) > -1
+        ? path
+        : 'notfound'
+}
+
+window.addEventListener('hashchange', function () {
+    app.currentView = getRoute()
+})
+
+// load the app
+var app = new Vue({
+    el: 'div',
+    data: {
+        currentView: getRoute(),
+        routes: routes
+    }
+})
 
-    updateRoute()
 </script>

+ 34 - 15
test/functional/specs/routing.js

@@ -1,26 +1,45 @@
-casper.test.begin('Routing', 10, function (test) {
+casper.test.begin('Routing', 24, function (test) {
     
     casper
     .start('./fixtures/routing.html')
     .then(function () {
-        test.assertElementCount('div', 1)
-        test.assertSelectorHasText('div', 'Hi!')
+        test.assertElementCount('.view', 1)
+        test.assertElementCount('.view.v-leave', 0)
+        test.assertSelectorHasText('a.current', 'home')
+        test.assertSelectorHasText('h1', 'Home')
+        test.assertSelectorHasText('.content', 'Home sweet home!')
     })
-    .thenClick('a', function () {
-        test.assertElementCount('div', 1)
-        test.assertSelectorHasText('div', 'Ho!')
+    .thenClick('a[href$="page1"]', function () {
+        test.assertSelectorHasText('a.current', 'page1')
+        // in transition
+        test.assertElementCount('.view', 2)
+        test.assertElementCount('.view.v-leave', 1)
     })
-    .thenClick('a', function () {
-        test.assertElementCount('div', 1)
-        test.assertSelectorHasText('div', 'Ha!')
+    .wait(250, function () {
+        test.assertElementCount('.view', 1)
+        test.assertElementCount('.view.v-leave', 0)
+        test.assertSelectorHasText('h1', 'Page1')
+        test.assertSelectorHasText('.content', 'Welcome to page 1!')
     })
-    .thenClick('a', function () {
-        test.assertElementCount('div', 1)
-        test.assertSelectorHasText('div', 'Hi!')
+    .thenClick('a[href$="page2"]', function () {
+        test.assertSelectorHasText('a.current', 'page2')
+        // in transition
+        test.assertElementCount('.view', 2)
+        test.assertElementCount('.view.v-leave', 1)
     })
-    .thenOpen('./fixtures/routing.html#ho', function () {
-        test.assertElementCount('div', 1)
-        test.assertSelectorHasText('div', 'Ho!')
+    .wait(250, function () {
+        test.assertElementCount('.view', 1)
+        test.assertElementCount('.view.v-leave', 0)
+        test.assertSelectorHasText('h1', 'Page2')
+        test.assertSelectorHasText('.content', 'Welcome to page 2!')
+    })
+    // reload to test initial page load with a route
+    .reload(function () {
+        test.assertSelectorHasText('a.current', 'page2')
+        test.assertElementCount('.view', 1)
+        test.assertElementCount('.view.v-leave', 0)
+        test.assertSelectorHasText('h1', 'Page2')
+        test.assertSelectorHasText('.content', 'Welcome to page 2!')
     })
     .run(function () {
         test.done()