Skip to content

Commit

Permalink
v-link-active refactor
Browse files Browse the repository at this point in the history
- fix v-link-active when contained v-link is on an element with terminal
  directive or a custom component.
- properly support many-to-many relationships between v-link-active and v-link.
  • Loading branch information
yyx990803 committed Apr 6, 2016
1 parent df223ac commit 3d12e95
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 16 deletions.
50 changes: 36 additions & 14 deletions src/directives/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,41 @@ export default function (Vue) {

const {
bind,
getAttr,
isObject,
addClass,
removeClass
} = Vue.util

const onPriority = Vue.directive('on').priority
const LINK_UPDATE = '__vue-router-link-update__'

let activeId = 0

Vue.directive('link-active', {
priority: onPriority - 1,
priority: 9999,
bind () {
this.el.__v_link_active = true
const id = String(activeId++)
// collect v-links contained within this element.
// we need do this here before the parent-child relationship
// gets messed up by terminal directives (if, for, components)
const childLinks = this.el.querySelectorAll('[v-link]')
for (var i = 0, l = childLinks.length; i < l; i++) {
let link = childLinks[i]
let existingId = link.getAttribute(LINK_UPDATE)
let value = existingId ? (existingId + ',' + id) : id
// leave a mark on the link element which can be persisted
// through fragment clones.
link.setAttribute(LINK_UPDATE, value)
}
this.vm.$on(LINK_UPDATE, this.cb = (link, path) => {
if (link.activeIds.indexOf(id) > -1) {
link.updateClasses(path, this.el)
}
})
},
unbind () {
this.vm.$off(LINK_UPDATE, this.cb)
}
})

Expand All @@ -36,15 +60,10 @@ export default function (Vue) {
this.router = vm.$route.router
// update things when the route changes
this.unwatch = vm.$watch('$route', bind(this.onRouteUpdate, this))
// check if active classes should be applied to a different element
this.activeEl = this.el
var parent = this.el.parentNode
while (parent) {
if (parent.__v_link_active) {
this.activeEl = parent
break
}
parent = parent.parentNode
// check v-link-active ids
const activeIds = getAttr(this.el, LINK_UPDATE)
if (activeIds) {
this.activeIds = activeIds.split(',')
}
// no need to handle click if link expects to be opened
// in a new window/tab.
Expand Down Expand Up @@ -115,7 +134,11 @@ export default function (Vue) {
this.updateActiveMatch()
this.updateHref()
}
this.updateClasses(route.path)
if (this.activeIds) {
this.vm.$emit(LINK_UPDATE, this, route.path)
} else {
this.updateClasses(route.path, this.el)
}
},

updateActiveMatch () {
Expand Down Expand Up @@ -149,8 +172,7 @@ export default function (Vue) {
}
},

updateClasses (path) {
const el = this.activeEl
updateClasses (path, el) {
const activeClass = this.activeClass || this.router._linkActiveClass
// clear old class
if (this.prevActiveClass !== activeClass) {
Expand Down
50 changes: 48 additions & 2 deletions test/unit/specs/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,35 +468,81 @@ describe('Core', function () {
})
var App = Vue.extend({
replace: false,
components: {
test: {
template: '<a><slot></slot></a>'
}
},
template:
'<ul>' +
'<li id="link-a" v-link-active>' +
'<a v-link="{ path: \'/a\' }">Link A</a>' +
'</li>' +
'<li id="link-b" v-link-active>' +
'<a v-link="{ path: \'/b\' }">Link B</a>' +
'<a v-if="true" v-link="{ path: \'/b\' }">Link B</a>' +
'</li>' +
'<li id="link-c" v-link-active>' +
'<test v-link="{ path: \'/c\' }">Link C</test>' +
'</li>' +
'</ul>'
})
router.start(App, el)
el = router.app.$el
var linkA = el.querySelector('#link-a')
var linkB = el.querySelector('#link-b')
var linkC = el.querySelector('#link-c')
expect(linkA.className).toBe('')
expect(linkB.className).toBe('')
expect(linkC.className).toBe('')
router.go('/a')
nextTick(function () {
expect(linkA.className).toBe('active')
expect(linkB.className).toBe('')
expect(linkC.className).toBe('')
router.go('/b')
nextTick(function () {
expect(linkA.className).toBe('')
expect(linkB.className).toBe('active')
done()
expect(linkC.className).toBe('')
router.go('/c')
nextTick(function () {
expect(linkA.className).toBe('')
expect(linkB.className).toBe('')
expect(linkC.className).toBe('active')
done()
})
})
})
})

it('multiple nested v-link-active', function (done) {
router = new Router({
abstract: true,
linkActiveClass: 'active'
})
var App = Vue.extend({
replace: false,
template:
'<div v-link-active class="outer">' +
'<div v-link-active class="inner">' +
'<a v-link="{ path: \'/a\'}">Link A</a>' +
'</div>' +
'</div>'
})
router.start(App, el)
el = router.app.$el
var outer = el.querySelector('.outer')
var inner = el.querySelector('.inner')
expect(outer.className).toBe('outer')
expect(inner.className).toBe('inner')
router.go('/a')
nextTick(function () {
expect(outer.className).toBe('outer active')
expect(inner.className).toBe('inner active')
done()
})
})

it('v-link relative querystring', function (done) {
router = new Router({ abstract: true })
router.map({
Expand Down

0 comments on commit 3d12e95

Please sign in to comment.