2

I want to import a JS file to be run along with a template in browser. I tried this, but it didn't work because I need everything loaded before my script can run.

Let me show you the problematic vue file:

<template>
    <div id="canvaspage">

        <canvas id="canvas"></canvas>

        <div id="buttonlist">
            <h5>Select your action:</h5>
            <div class="col">
                <button id="btn1">JS file custom action 1</button>
                <button id="btn2">JS file custom action 2</button>
            </div>
        </div>

    </div>
</template>

<script>
export default {
    name: 'CanvasPage'
}
</script>
...

See that canvas and buttons on template? I want to interact with it using pure JS.

Here is an example of what the JS file is trying to do:

let canvas = document.getElementById('canvas')
let button1 = document.getElementById('btn1')
let button2 = document.getElementById('btn2')

canvas.addEventListener('click', () => {
    console.log('Canvas clicked')
})

button1.addEventListener('click', () => {
    console.log('Button 1 clicked')
})

button2.addEventListener('click', () => {
    console.log('Button 2 clicked')
})

If I try the solution linked above, what happens is that 'canvas', 'button1' and 'button2' are all null, because JS cannot find them. How can I make it work on Vue?

  • You should pack it into a proper ES6 module which exports a function. This function can then be called from within the context of your Vue app. Of course, you need to import it first. You should avoid interacting with DOM elements which are managed by Vue in an external script. Especially, don't use ID's, because an ID must be unique on a single page (you cannot reuse the component on the same page). And if Vue just removed your elements from the DOM (e.g. routing to another page), your script will crash. – ssc-hrep3 Sep 16 '19 at 23:47

2 Answers2

1

I don't see a reason- in this example- why you want to do anything in external js file, why not just interact with dom the vue way - I mean, proper way? Vue can destroy or replace your element with any v-if or rerender action. You can always link to your elements with this.$refs if you want to interact with DOM directly which is lots better than querySelector thingy. But anyway, here's a dummy example:

// external js file - ./extfile.js
export function canvasClick(...args) {
    console.log('canvas clicked with: ', args);
}
export function button1Click(...args) {
    console.log('button1 clicked with: ', args);
}
export function button2Click(...args) {
    console.log('button2 clicked with: ', args);
}

// vue component
<template>
    <div id="canvaspage">
        <canvas id="canvas" @click="canvasAction"></canvas>
        <div id="buttonlist">
            <h5>Select your action:</h5>
            <div class="col">
                <button id="btn1" @click.prevent="button1Action">JS file custom action 1</button>
                <button id="btn2" @click.prevent="button2Action">JS file custom action 2</button>
            </div>
        </div>
    </div>
</template>

<script>
import { canvasClick, button1Click, button2Click } from './extfile';
export default {
    name: 'CanvasPage',
    methods: {
        canvasAction(event) { canvasClick(event, this) },
        button1Action(event) { button1Click(event, this) },
        button2Action(event) { button2Click(event, this) },
    }
}
</script>
Maciej Kwas
  • 6,169
  • 2
  • 27
  • 51
0

Objects managed by Vue are create/destroyed according to Vue' lifecycle. This means that any external code you use to query vue-managed elements should be somewhat coupled to Vue's lifecycle.

This means that, ideally, you should use Vue itself to add the behaviour you want. You should, for instance, add this new function you want into a Vue component. This guarantees a simpler design.

Alternative: If the Vue components are from third-parties, perhaps from another team which you can't count on, you could hook those event listeners to the document and check the target's id attribute instead of hooking the event listeners directly to the canvas element (which may be destroyed by Vue and the hooks lost).

document.body.addEventListener('click', (event) => {
  switch (event.target.id) {
    case 'canvas':
      console.log('Canvas clicked');
      break;
    case 'btn1':
      console.log('Button 1 clicked');
      break;
    case 'btn2':
      console.log('Button 2 clicked');
      break;
  }
}, true);

This code makes it very obvious that if you have more than one element in the DOM with those IDs, all of them will trigger the code.

Demo:

const CanvasComponent = Vue.component('canvas-component', {
  template: `#canvas-component`,
});
const BlankComponent = Vue.component('blank-component', {
  template: `<div><h3>Now click back to canvas and see that the listeners still work.</h3></div>`,
});

var router = new VueRouter({
  routes: [{
      path: '/',
      component: {template: '<div>Click one link above</div>'}
    },{
      path: '/blank',
      component: BlankComponent,
      name: 'blank'
    },
    {
      path: '/canvas',
      component: CanvasComponent,
      name: 'canvas'
    }
  ]
});

var app = new Vue({
  el: '#app',
  router: router,
  template: `
  <div>
     <router-link :to="{name: 'canvas'}">canvas</router-link> |
     <router-link :to="{name: 'blank'}">blank</router-link>
     
     <router-view></router-view>
  </div>
  `
});


document.body.addEventListener('click', (event) => {
  switch (event.target.id) {
    case 'canvas':
      console.log('Canvas clicked');
      break;
    case 'btn1':
      console.log('Button 1 clicked');
      break;
    case 'btn2':
      console.log('Button 2 clicked');
      break;
  }
}, true);
<script src="//unpkg.com/vue@2.6.9/dist/vue.min.js"></script>
<script src="//unpkg.com/vue-router@3.1.3/dist/vue-router.min.js"></script>

<div id="app">
  <canvas-component></canvas-component>
</div>

<template id="canvas-component">
  <div id="canvaspage">
    <canvas id="canvas"></canvas>
    <div id="buttonlist">
      <h5>Select your action:</h5>
      <div class="col">
        <button id="btn1">JS file custom action 1</button>
        <button id="btn2">JS file custom action 2</button>
      </div>
    </div>
  </div>
</template>
acdcjunior
  • 132,397
  • 37
  • 331
  • 304
  • 1
    `document.body.addEventListener('click'...` is a first step to complete mess in project ;) – Maciej Kwas Sep 17 '19 at 00:10
  • @MaciejKwas Yes, but considering the constraint that *you can't interact with the original element and vue destroys/recreates it*, that is the only alternative. If there are other posibilities, I'm all ears (honestly). The OP's scenario is very likely (considering the linked answer) a legacy integration one, so this must be taken into account. If not, adding the code directly to the vue component is the best alternative, as I pointed in the beginning of my answer. – acdcjunior Sep 17 '19 at 00:20