This commit is contained in:
Evan You 2014-03-08 21:33:06 -05:00
parent 656c0157f5
commit 10a0cc1838
7 changed files with 203 additions and 40 deletions

View File

@ -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"
]
}

View File

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

View File

@ -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
src/directives/view.js Normal file
View File

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

View File

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

View File

@ -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!"
}
})
var app = new Vue({
el: 'body'
})
app.route = route
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>'
})
// 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>

View File

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