8000 v-link-active refactor · vuejs/vue-router@3d12e95 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3d12e95

Browse files
committed
v-link-active refactor
- 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.
1 parent df223ac commit 3d12e95

File tree

2 files changed

+84
-16
lines changed

2 files changed

+84
-16
lines changed

src/directives/link.js

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,41 @@ export default function (Vue) {
99

1010
const {
1111
bind,
12+
getAttr,
1213
isObject,
1314
addClass,
1415
removeClass
1516
} = Vue.util
1617

1718
const onPriority = Vue.directive('on').priority
19+
const LINK_UPDATE = '__vue-router-link-update__'
20+
21+
let activeId = 0
1822

1923
Vue.directive('link-active', {
20-
priority: onPriority - 1,
24+
priority: 9999,
2125
bind () {
22-
this.el.__v_link_active = true
26+
const id = String(activeId++)
27+
// collect v-links contained within this element.
28+
// we need do this here before the parent-child relationship
29+
// gets messed up by terminal directives (if, for, components)
30+
const childLinks = this.el.querySelectorAll('[v-link]')
31+
for (var i = 0, l = childLinks.length; i < l; i++) {
32+
let link = childLinks[i]
33+
let existingId = link.getAttribute(LINK_UPDATE)
34+
let value = existingId ? (existingId + ',' + id) : id
35+
// leave a mark on the link element which can be persisted
36+
// through fragment clones.
37+
link.setAttribute(LINK_UPDATE, value)
38+
}
39+
this.vm.$on(LINK_UPDATE, this.cb = (link, path) => {
40+
if (link.activeIds.indexOf(id) > -1) {
41+
link.updateClasses(path, this.el)
42+
}
43+
})
44+
},
45+
unbind () {
46+
this.vm.$off(LINK_UPDATE, this.cb)
2347
}
2448
})
2549

@@ -36,15 +60,10 @@ export default function (Vue) {
3660
this.router = vm.$route.router
3761
// update things when the route changes
3862
this.unwatch = vm.$watch('$route', bind(this.onRouteUpdate, this))
39-
// check if active classes should be applied to a different element
40-
this.activeEl = this.el
41-
var parent = this.el.parentNode
42-
while (parent) {
43-
if (parent.__v_link_active) {
44-
this.activeEl = parent
45-
break
46-
}
47-
parent = parent.parentNode
63+
// check v-link-active ids
64+
const activeIds = getAttr(this.el, LINK_UPDATE)
65+
if (activeIds) {
66+
this.activeIds = activeIds.split(',')
4867
}
4968
// no need to handle click if link expects to be opened
5069
// in a new window/tab.
@@ -115,7 +134,11 @@ export default function (Vue) {
115134
this.updateActiveMatch()
116135
this.updateHref()
117136
}
118-
this.updateClasses(route.path)
137+
if (this.activeIds) {
138+
this.vm.$emit(LINK_UPDATE, this, route.path)
139+
} else {
140+
this.updateClasses(route.path, this.el)
141+
}
119142
},
120143

121144
updateActiveMatch () {
@@ -149,8 +172,7 @@ export default function (Vue) {
149172
}
150173
},
151174

152-
updateClasses (path) {
153-
const el = this.activeEl
175+
updateClasses (path, el) {
154176
const activeClass = this.activeClass || this.router._linkActiveClass
155177
// clear old class
156178
if (this.prevActiveClass !== activeClass) {

test/unit/specs/core.js

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,35 +468,81 @@ describe('Core', function () {
468468
})
469469
var App = Vue.extend({
470470
replace: false,
471+
components: {
472+
test: {
473+
template: '<a><slot></slot></a>'
474+
}
475+
},
471476
template:
472477
'<ul>' +
473478
'<li id="link-a" v-link-active>' +
474479
'<a v-link="{ path: \'/a\' }">Link A</a>' +
475480
'</li>' +
476481
'<li id="link-b" v-link-active>' +
477-
'<a v-link="{ path: \'/b\' }">Link B</a>' +
482+
'<a v-if="true" v-link="{ path: \'/b\' }">Link B</a>' +
483+
'</li>' +
484+
'<li id="link-c" v-link-active>' +
485+
'<test v-link="{ path: \'/c\' }">Link C</test>' +
478486
'</li>' +
479487
'</ul>'
480488
})
481489
router.start(App, el)
482490
el = router.app.$el
483491
var linkA = el.querySelector('#link-a')
484492
var linkB = el.querySelector('#link-b')
493+
var linkC = el.querySelector('#link-c')
485494
expect(linkA.className).toBe('')
486495
expect(linkB.className).toBe('')
496+
expect(linkC.className).toBe('')
487497
router.go('/a')
488498
nextTick(function () {
489499
expect(linkA.className).toBe('active')
490500
expect(linkB.className).toBe('')
501+
expect(linkC.className).toBe('')
491502
router.go('/b')
492503
nextTick(function () {
493504
expect(linkA.className).toBe('')
494505
expect(linkB.className).toBe('active')
495-
done()
506+
expect(linkC.className).toBe('')
507+
router.go('/c')
508+
nextTick(function () {
509+
expect(linkA.className).toBe('')
510+
expect(linkB.className).toBe('')
511+
expect(linkC.className).toBe('active')
512+
done()
513+
})
496514
})
497515
})
498516
})
499517

518+
it('multiple nested v-link-active', function (done) {
519+
router = new Router({
520+
abstract: true,
521+
linkActiveClass: 'active'
522+
})
523+
var App = Vue.extend({
524+
replace: false,
525+
template:
526+
'<div v-link-active class="outer">' +
527+
'<div v-link-active class="inner">' +
528+
'<a v-link="{ path: \'/a\'}">Link A</a>' +
529+
'</div>' +
530+
'</div>'
531+
})
532+
router.start(App, el)
533+
el = router.app.$el
534+
var outer = el.querySelector('.outer')
535+
var inner = el.querySelector('.inner')
536+
expect(outer.className).toBe('outer')
537+
expect(inner.className).toBe('inner')
538+
router.go('/a')
539+
nextTick(function () {
540+
expect(outer.className).toBe('outer active')
541+
expect(inner.className).toBe('inner active')
542+
done()
543+
})
544+
})
545+
500546
it('v-link relative querystring', function (done) {
501547
router = new Router({ abstract: true })
502548
router.map({

0 commit comments

Comments
 (0)
0