I'm working on a Vue 3 project where I need to dynamically replace anchor elements that contain a specific attribute (data-button) with a custom Vue component (CustomButton). To achieve this, I created a directive that queries all the anchor tags within a specified element and dynamically imports the CustomButton component. Below link is a simplified version of my implementation: [you can see a simiplified example here]
Directive (parser-directive.js):
import { nextTick } from 'vue';
import { loadCustomButton } from './dynamic-import-custom-button.js';
const htmlParserDirective = {
async mounted(el) {
await nextTick();
console.log('Directive inserted');
const wrapper = document.createElement('div');
wrapper.innerHTML = el.innerHTML;
const anchorTags = wrapper.querySelectorAll('a[data-button]');
if (anchorTags.length) {
for (const anchor of anchorTags) {
const text = anchor.innerText.trim();
const path = anchor.getAttribute('data-path');
const CustomButton = await loadCustomButton();
console.log('CustomButton loaded:', CustomButton);
const CustomButtonComponent = createApp(CustomButton, { text, path });
const customButtonInstance = CustomButtonComponent.mount(document.createElement('div'));
console.log('CustomButton instance mounted:', customButtonInstance);
customButtonInstance.$el.addEventListener('mouseenter', () => {
console.log('Mouse Enter');
});
customButtonInstance.$el.addEventListener('mouseleave', () => {
console.log('Mouse Leave');
});
anchor.parentNode.replaceChild(customButtonInstance.$el, anchor);
}
el.innerHTML = wrapper.innerHTML;
}
},
};
export default htmlParserDirective;
CustomButton.vue:
<a :href="path">
{{ text }}
</a>
</template>
<script>
export default {
name: 'CustomButton',
props: {
text: {
type: String,
default: '',
},
path: {
type: String,
default: ''
}
},
mounted() {
console.log('CustomButton mounted');
},
};
</script>
<style scoped>
a {
text-decoration: none;
color: inherit;
cursor: pointer;
}
</style>
App.vue:
<div id="app">
<div v-html-parser>
<a href="#" data-button data-path="test.test">Test Button</a>
</div>
</div>
</template>
<script>
export default {
name: 'App',
};
</script>
Problem: The issue I'm facing is that the event listeners for mouseenter and mouseleave are not being triggered. Although I can see that the CustomButton component is loaded and replaced correctly, the added event listeners do not seem to be working.
Question: Why are the mouseenter and mouseleave event listeners not being triggered on the dynamically created CustomButton component? Is there a different approach or a solution to ensure these event listeners are called?
Any help or suggestions would be greatly appreciated!
You just set pure HTML to the directive's div. That copies HTML only obviously, no event handlers, you should move the children elements:
el.innerHTML = '';
for(const child of wrapper.children){
el.appendChild(child);
}
Btw you can make your directive more Vue-standard (acting on VDOM rather on DOM) and mount the buttons with Vue.render
:
import { h, render } from 'vue';
import { loadCustomButton } from './dynamic-import-custom-button.js';
const htmlParserDirective = {
async mounted(el) {
console.log('Directive inserted');
const anchorTags = el.querySelectorAll('a[data-button]');
el.innerHTML = '';
const CustomButton = await loadCustomButton();
console.log('CustomButton loaded:', CustomButton);
for (const anchor of anchorTags) {
render(h(CustomButton, {
text: anchor.innerText.trim(),
path: anchor.getAttribute('data-path'),
onMouseenter: () => console.log('Mouse Enter'),
onMouseleave: () => console.log('Mouse Leave')
}), el);
}
},
};
export default htmlParserDirective;