vue.js

Vue Directive: Dynamically Replacing an element with a component - Event Listeners Not Working


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!


Solution

  • 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;