Reputation: 14364
Reproducible simplified example: https://jsfiddle.net/nachocab/evc2374p/235/
More complex example: https://jsfiddle.net/nachocab/evc2374p/125/ (pressing the right arrow twice (doesn't work), then left, then right (it works). More details
I know this is not the Vue way, but I need to modify a string of HTML that I receive from a server (it represents a slide deck with slides). I traverse the DOM tree and change data-hidden
to true
for a few elements and I would like to trigger an update in the slide component.
I've tried several things, including changing the component key, emitting an event and calling forceUpdate, but it doesn't work.
Template:
<div id="app">
<wrapper></wrapper>
</div>
<template id="wrapper">
<slide-deck>
<slide>
<p data-hidden="true">Hello</p>
</slide>
</slide-deck>
</template>
JS:
Vue.prototype.$eventBus = new Vue();
Vue.component('slide-deck', {
created() {
window.addEventListener('keydown', this.handleKeydown);
},
destroyed() {
window.removeEventListener('keydown', this.handleKeydown);
},
methods: {
handleKeydown(e) {
this.$slots.default[0].componentOptions.children[0].data.attrs['data-hidden']="false"
this.$eventBus.$emit('renderSlide')
},
},
render(h) {
console.log('render deck')
return h('div',{}, this.$slots.default)
}
})
Vue.component('slide', {
created() {
this.$eventBus.$on('renderSlide', () => {
this.$forceUpdate()
})
},
render(h) {
console.log('render slide', this.$slots.default[0].data.attrs['data-hidden'])
return h('div',{}, this.$slots.default)
}
})
Vue.component('wrapper', {
template: '#wrapper',
});
new Vue({
el: '#app'
});
Upvotes: 1
Views: 1503
Reputation: 16324
I think it's because you are updating the wrong property on child nodes.
You are using
child.data.attrs['data-hidden']="false"
But you should manipulate the dom element directly using:
child.elm.dataset['hidden'] = false
That being said, you have other problems. You trigger your updateVisibilities
method into the created
hook where the DOM has not been created yet. I suggest you to use the mounted
hook instead.
And finally, because you trigger your updateVisibilities
method after a data update, you should also wait into this method for the DOM to be updated using the Vue nextTick
helper:
updateVisibilities() {
this.$nextTick(() => {
const validElements = this.currentSlide.componentOptions.children.filter(child => child.tag)
validElements.forEach(child => {
console.log(child, child.elm)
child.elm.dataset['hidden'] = child.elm.dataset['order'] > this.currentFragmentIndex
console.log('data-order', child.elm.dataset['order'], 'data-hidden', child.elm.dataset['hidden'])
})
})
}
You also don't need any $forceUpdate
in your case.
Here are your two fiddles fixed:
I hope this helps!
EDIT: And here is the full updated code:
Vue.component('slide-deck', {
data() {
return {
currentSlideIndex: 0,
currentFragmentIndex: 0,
numFragmentsPerSlide: [2,2]
}
},
computed: {
slideComponents() {
return this.$slots.default.filter(slot => slot.tag)
},
currentSlide() {
return this.slideComponents[this.currentSlideIndex]
}
},
mounted () {
window.addEventListener('keydown', this.handleKeydown);
this.updateVisibilities();
},
destroyed() {
window.removeEventListener('keydown', this.handleKeydown);
},
methods: {
handleKeydown(e) {
if (e.code === 'ArrowRight') {
this.increaseFragmentOrSlide()
} else if (e.code === 'ArrowLeft') {
this.decreaseFragmentOrSlide()
}
},
increaseFragmentOrSlide() {
if (this.currentFragmentIndex < this.numFragmentsPerSlide[this.currentSlideIndex] - 1) {
this.currentFragmentIndex +=1
console.log('increased fragment index:', this.currentFragmentIndex)
} else if (this.currentSlideIndex < this.numFragmentsPerSlide.length - 1){
this.currentSlideIndex += 1
this.currentFragmentIndex = 0
console.log('increased slide index:', this.currentSlideIndex)
}
this.updateVisibilities()
},
decreaseFragmentOrSlide() {
if (this.currentFragmentIndex > 0) {
this.currentFragmentIndex -=1
console.log('decreased fragment:', this.currentFragmentIndex)
} else if (this.currentSlideIndex > 0) {
this.currentSlideIndex -= 1
this.currentFragmentIndex = 0
console.log('decreased slide:', this.currentSlideIndex)
}
this.updateVisibilities()
},
updateVisibilities() {
// this.$forceUpdate() // hack
this.$nextTick(() => {
const validElements = this.currentSlide.componentOptions.children.filter(child => child.tag)
validElements.forEach(child => {
console.log(child, child.elm)
child.elm.dataset['hidden'] = child.elm.dataset['order'] > this.currentFragmentIndex
console.log('data-order', child.elm.dataset['order'], 'data-hidden', child.elm.dataset['hidden'])
})
})
}
},
render(h) {
return h('div',{}, [this.currentSlide])
}
})
Vue.component('slide', {
render(h) {
return h('div',{}, this.$slots.default)
}
})
Vue.component('wrapper', {
template: '#wrapper',
});
new Vue({
el: '#app'
});
Upvotes: 2