Search code examples
vuejs3outerhtml

Vue3 copy outerHTML the click event not work


After copy the Click button, the copy Click button does not work.

<template>
  <div>
    <div id="container" ref="container">
      <button @click="handleClick">Click Me</button>
    </div>
    <div id="clip" ref="clip"></div>
    <button @click="copyContent">Copy Content</button>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const container = ref<HTMLDivElement | null>(null)
const clip = ref<HTMLDivElement | null>(null)

function handleClick() {
  console.log('Clicked')
}

function copyContent() {
  if (container.value && clip.value) {
    // copy container outerHTML to clip innerHTML
    clip.value.innerHTML = container.value.outerHTML
  }
}
</script>

<style>
#container,
#clip {
  margin-bottom: 10px;
}
button {
  margin-right: 10px;
}
</style>

enter image description here

What I tried

I fix the problem by adding a click event to copy click button.

<template>
  <div>
    <div id="container" ref="container">
      <button @click="handleClick">Click Me</button>
    </div>
    <div id="clip" ref="clip"></div>
    <button @click="copyContent">Copy Content</button>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const container = ref<HTMLDivElement | null>(null)
const clip = ref<HTMLDivElement | null>(null)

function handleClick() {
  console.log('Clicked')
}

function copyContent() {
  if (container.value && clip.value) {
    // copy container outerHTML to clip innerHTML
    clip.value.innerHTML = container.value.outerHTML

    // fix the problem,is work
    const button = clip.value.querySelector('button')
    button!.addEventListener('click', handleClick)
  }
}
</script>

<style>
#container,
#clip {
  margin-bottom: 10px;
}
button {
  margin-right: 10px;
}
</style>

enter image description here

What I am expecting

I wonder why vue3 is not working?
How can I use vue3 to solve the problem?


Solution

  • Vue's way to do anything is to work with VDOM and vnodes, so you just create a component that could return its slot and you use the slot a functional component:

    See on Vue SFC Playground

    <template>
      <div>
        <copy ref="$copy">
          <div id="container">
            <button @click="handleClick">Click me</button>
          </div>
        </copy>
        <div id="clip"><component :is="clip"/></div>
        <button @click="clip = () => $copy.getSlot()()">Copy Content</button>
      </div>
    </template>
    
    <script setup lang="ts">
    import { ref } from 'vue';
    import Copy from './Copy.vue';
    
    const clip = ref(null);
    const $copy = ref();
    
    function handleClick() {
      alert('click')
    }
    
    </script>
    

    Copy.vue

    <script setup>
    import {useSlots} from 'vue';
    const slots = useSlots();
    defineExpose({
      getSlot(name = 'default'){ 
        return slots[name];
      } 
    });
    </script>
    
    <template>
        <slot />
    </template>
    

    A functional approach:

    See on Vue SFC Playground

    <template>
      <div>
        <copy ref="$copy">
          <div id="container">
            <button @click="handleClick">Click me</button>
          </div>
        </copy>
        <div id="clip"><component :is="clip"/></div>
        <button @click="clip = () => $copy.getSlot()()">Copy Content</button>
      </div>
    </template>
    
    <script setup lang="ts">
    import { ref, useSlots } from 'vue';
    
    const Copy = {
      setup(_, {expose}) {
        const slots = useSlots();
        expose({getSlot: (name = 'default') => slots[name]});
        return () => slots.default();
      }
    }
    
    const clip = ref(null);
    const $copy = ref();
    
    function handleClick() {
      alert('click')
    }
    
    </script>