WoJ
WoJ

Reputation: 30005

Where should a vue.js script be loaded?

I am learning Vue.js by writing a small script and ended up with a catch 22 kind of situation.

The JS script (explorer.js) which handles the Vue.js part:

var vm = new Vue({
    el: "#root",
    data: {
        posts: {},
        tags: {}
    }
})

qwest.get('/posts.json')
    .then(function (xhr, response) {
        vm.posts = response;
    });

qwest.get('/tags.json')
    .then(function (xhr, response) {
        vm.tags = response;
    });

Case 1: loading explorer.js early:

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qwest/4.4.6/qwest.min.js"></script>
<link rel="stylesheet" href="/static/css/explorer.css">
<script src="/static/js/explorer.js"></script>

<div id="root">
    <button v-for="(content, title) in posts" class="l1 btn">
        {{ title }}
        <button v-for="tag in content.tags" class="l2 btn">
            {{ tag }}
            <button v-for="t in tags[tag]" class="l3 btn">
                {{ t }}
            </button>
        </button>
    </button>
</div>

I get a Cannot find element: #root error from Vue. This is understandable: when explorer.js runs, the <div id="root"> is not known yet.

Case 2: loading explorer.js late:

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qwest/4.4.6/qwest.min.js"></script>
<link rel="stylesheet" href="/static/css/explorer.css">

<div id="root">
    <button v-for="(content, title) in posts" class="l1 btn">
        {{ title }}
        <button v-for="tag in content.tags" class="l2 btn">
            {{ tag }}
            <button v-for="t in tags[tag]" class="l3 btn">
                {{ t }}
            </button>
        </button>
    </button>
</div>

<script src="/static/js/explorer.js"></script>

I get a Property or method "data" is not defined on the instance but referenced during render. error (and other of the same style).
This is also understandable: the v-for functions try to access data which have not been defined yet.

How (or rather - where) should I load explorer.js?

Upvotes: 2

Views: 2549

Answers (3)

jony89
jony89

Reputation: 5367

Vue.js should be loaded at the start.

You don't need to specify the data property in order to access it's properties, see the Hello World example:

https://jsfiddle.net/chrisvfritz/50wL7mdz/

I'm not sure what is your data structure but, you could try something like that (without the data property):

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qwest/4.4.6/qwest.min.js"></script>

<div id="root">
    <button v-for="post in posts" class="l1 btn">
        {{ post.title }}
        <button v-for="tag in tags" class="l2 btn">
            {{ tag }}
        </button>
    </button>
</div>

Just work according to your data structure but without the data

Also, with Vue.js you can't have dynamically added root properties, see https://v2.vuejs.org/v2/guide/reactivity.html, so you must specify the root properties in your data as your initial structure.

The explorer.js should be loaded after your html template (at the end), the warnings you get/errors is due to not defining your model at the start, even nested roots !

other than that everything seems to be fine.

Upvotes: 0

Bert
Bert

Reputation: 82479

First, you should add explorer.js at the bottom of your file. You could add it at the top of your file if you wrapped the instantiation of the Vue (new Vue(...) in a function that only ran when the page was completely loaded, but it's standard practice to add the script at the bottom.

Your primary issue is that nested buttons are invalid HTML. This is the issue that is throwing

Property or method "content" is not defined on the instance but referenced during render.

This appears to mainly be Vue complaining about your invalid HTML. I've modified your template below to something that will actually render, but you'll need to tailor it to your needs.

<div v-for="(content, title) in posts" class="l1 btn">
  <button>{{ title }}</button>
  <span v-for="tag in content.tags" class="l2 btn" style="margin-left:1em">
     <button>{{ tag }}</button>
     <span v-for="t in tags[tag]" class="l3 btn">
       <button>{{ t }}</button>
     </span>
  </span>
</div>

Here is a working example.

Upvotes: 2

Chad Campbell
Chad Campbell

Reputation: 937

I actually wrote a post that explains the Vue Cannot Find Element error. You'll get this error if you try to instantiate Vue to0 early. In my Vue.js training course, all examples load the Vue.js framework towards the end of the file. In general, I have the Vue.js framework loaded via a script tag and then another script block that initializes the Vue instance itself. These two items happen just before the closing body tag.

In regards to Case #1, just move your script elements after your <div id="root" ... element and you should be in good shape.

You can use the v-cloak directive to create smooth loading experience. The reason that I recommend loading your Vue last is based on recommendations from PageSpeed Insights. These may, or may not, be relevant to your scenario though.

Upvotes: 1

Related Questions