3

I want to create a dynamic HTML template on click of a canvas.

This is what I tried:

var btn = document.createElement("div");
btn.classList.add("custom-signature-btn-row");
btn.classList.add("d-flex");
btn.innerHTML = `
    <div class="btn-grid d-flex mr-2">
        <span class="element-icon drag-icon md-icon sign-btn">drag_indicator</span>
        <button class="md-button md-primary md-theme-default button-custom-regular" v-on:click="toggleSignature()">
            Signature
        </button>
        <div class="signature-dropdow" v-bind:class="{ active: isActive }">
            <ul>
                <li>
                    <div class="md-layout-item">
                        <div class="md-field">
                            <label for="movie">Assigned to Anyone</label>
                            <select name="movie" class="md-select" id="movie">
                                <option class="md-option" value="fight-club">Fight Club</option>
                                <option class="md-option" value="godfather">Godfather</option>
                            </select>
                        </div>
                    </div>
                </li>
            </ul>
        </div>
    </div>
`;

document.addEventListener('click', function(event) {
    if (event.target && event.target.classList.contains("pdf-canvas")) {
        console.log(event.target.parentNode);
        event.target.parentNode.appendChild(btn);
    }
});

The problem with above code is v-bind and v-on won't work with this approach.

3 Answers 3

5

You can programmatically render the component.

First, move the element as a component, e.g:

Button.vue

<template>
    <div class="custom-signature-btn-row d-flex btn-grid d-flex mr-2">
        <span class="element-icon drag-icon md-icon sign-btn">drag_indicator</span>
        <button class="md-button md-primary md-theme-default button-custom-regular" v-on:click="toggleSignature()">
            Signature
        </button>
        <div class="signature-dropdow" v-bind:class="{ active: isActive }">
            <ul>
                <li>
                    <div class="md-layout-item">
                        <div class="md-field">
                            <label for="movie">Assigned to Anyone</label>
                            <select name="movie" class="md-select" id="movie">
                                <option class="md-option" value="fight-club">Fight Club</option>
                                <option class="md-option" value="godfather">Godfather</option>
                            </select>
                        </div>
                    </div>
                </li>
            </ul>
        </div>
    </div>
</template>

<script>
export default {
  props: {
    active: false,
    toggleSignature: {
      type: Function,
      default: () => {}
    }
  }
}
</script>

On the canvas click handler, programmatically create a vue Button instance, render, and mount it into the target element.

import Vue from 'vue';
import Button from './Button.vue';

...

document.addEventListener('click', function(event) {
    if (event.target && event.target.classList.contains("pdf-canvas")) {
        const ButtonClass = Vue.extend(Button);
        const buttonInstance = new ButtonClass({
            propsData: { 
                isActive: this.isActive, // pass any data you need here
                toggleSignature: this.toggleSignature, // callback
            }
        });
        buttonInstance.$mount();
        event.target.parentNode.appendChild(buttonInstance.$el);
    }
});
Sign up to request clarification or add additional context in comments.

9 Comments

I am new to vuejs and using SFC, I don't understand what is $el in my SFC, I am using this eventListener in mounted() section.
$el refers to the root HTML element created by Vue on the component.
I am sorry I don't have an $el, new Vue({ router, store, render: h => h(App) }).$mount("#app"); This is my app.js
If you're referring to buttonInstance.$el, that is irrelevant with the el you're referring to.
|
1

You can refactor your code into an ordinary Vue component:

Vue.component('dynamic-btn', {
  data: () => ({
    isActive: true
  }),
  methods: {
    toggleSignature() {
      console.log('Clicked!')
    }
  },
  template: `
    <div class="custom-signature-btn-row d-flex">
    <div class="btn-grid d-flex mr-2">
        <span class="element-icon drag-icon md-icon sign-btn">drag_indicator</span>
        <button class="md-button md-primary md-theme-default button-custom-regular" v-on:click="toggleSignature()">
            Signature
        </button>
        <div class="signature-dropdow" v-bind:class="{ active: isActive }">
            <ul>
                <li>
                    <div class="md-layout-item">
                        <div class="md-field">
                            <label for="movie">Assigned to Anyone</label>
                            <select name="movie" class="md-select" id="movie">
                                <option class="md-option" value="fight-club">Fight Club</option>
                                <option class="md-option" value="godfather">Godfather</option>
                            </select>
                        </div>
                    </div>
                </li>
            </ul>
        </div>
    </div>
    </div>
`
});

new Vue({
  el: '#app',
  data: () => ({
    btnCount: 1,
  }),
});

Vue.config.productionTip = false;
Vue.config.devtools = false;
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <button @click="btnCount++">Create Component</button>
  <dynamic-btn v-for="(btn, i) in btnCount" :key="i" />
</div>

This way v-bind and v-on will work as normal.


Using SFC syntax:

<template>
  <div class="custom-signature-btn-row d-flex">
    <div class="btn-grid d-flex mr-2">
      <span class="element-icon drag-icon md-icon sign-btn">drag_indicator</span>
      <button class="md-button md-primary md-theme-default button-custom-regular" v-on:click="toggleSignature()">
        Signature
      </button>
      <div class="signature-dropdow" v-bind:class="{ active: isActive }">
        <ul>
          <li>
            <div class="md-layout-item">
              <div class="md-field">
                <label for="movie">Assigned to Anyone</label>
                <select name="movie" class="md-select" id="movie">
                  <option class="md-option" value="fight-club">Fight Club</option>
                  <option class="md-option" value="godfather">Godfather</option>
                </select>
              </div>
            </div>
          </li>
        </ul>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'DynamicBtn',

  data: () => ({ isActive: true }),

  methods: {
    toggleSignature() {
      console.log('Clicked!')
    }
  }
}
</script>
<template>
  <div>
    <button @click="btnCount++">Create Component</button>
    <DynamicBtn v-for="(btn, i) in btnCount" :key="i" />
  </div>
</template>

<script>
import DynamicBtn from './DynamicBtn';

export default {
  name: 'Parent',

  components: { DynamicBtn },

  data: () => ({
    btnCount: 1,
  }),
}
</script>

5 Comments

I am using SFC, and how will the above component append to the clicked canvas?
You'll have to include the canvas portion in you question for me to answer that
Canvas is created dynamically, you can see an example here codesandbox.io/s/hardcore-shape-gt8do?file=/pdfannotate.js
I've updated my answer to include SFC syntax. As to how you can append it to the canvas, I honestly have no idea. Your sandbox is too much code for me to look through. If you can create a minimal reproducible example, though, I'll try my best.
Just inspect element on the preview output window. You don't have to see the code. The canvas itself is dynamically generated by pdf uploaded.
0

Vue internally pre-compiles all the templates into "render functions", so they can build the Virtual DOM tree. During this process all the "v-on" and "v-bind" magic happens - that's why it doesn't work when you simply add new node to the DOM directly

What you can do is write your own render function - Vue provides you with the "createElement" function(AKA "h" function, so-called by convention). There you can specify all the Children, Classes, select the HTML tag, handle events etc.

Or a bit simpler solution will be the Dynamic components (<component v-bind:is="myCompName">), probably in combination with "v-html" to handle the "dynamic" part

1 Comment

I am a bit new to Vue.js can you please create a jsfiddle so that I can get a clearer picture how to append the component to the clicked canvas or div

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.