Vue property not defined in instance for template - javascript

I have a Vue template that displays a set of tracks, but my site isn't loading. Console says
Property or method "track" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
(found in root instance)
I'm sure the issue has to do with parent-child vue instance relationship or something along those lines, but I'm not too sure, since I'm only starting to learn about vue.js
I read over this https://v2.vuejs.org/v2/guide/components.html#Using-v-on-with-Custom-Events, but I'm having a hard time seeing what the problem is. What's going wrong?
Here's the template in html:
<div id="vue-div">
<template v-for="track in spotifyResults.items">
<spotify :track="track"></spotify>
</template>
<!-- spotify vue template -->
<script type="text/x-template" id="spotifyResult">
<tr>
<td>
<img :src="track.album.images[2].url"/>
</td>
<td>${track.artists[0].name}
</td>
<td>${track.album.name}
</td>
<td>${track.name}
</td>
<td>
<a :href="track.uri">Play</a>
</td>
<td><span v-on:click="add_track_to_library(track.album.images[2].url, track.artists[0].name, track.album.name, track.name, spotify, track.uri, none)">+</span></td>
</tr>
</script>
</div>
And here is the js:
var spotify = {
template: '#spotifyResult',
delimiters: ['${', '}'],
props: ['track']
}
self.vue = new Vue({
el: "#vue-div",
delimiters: ['${', '}'],
unsafeDelimiters: ['!{', '}'],
components: {
spotify: spotify,
},
data: {
spotifyResults: {
items: []
},
}
});

You are defining your component template incorrectly. Vue is attempting to render your #spotifyResult template as a <template> block.
To use X-Templates, you use
<script type="text/x-template" id="spotifyResult">
<tr>
<!-- etc -->
</tr>
</script>
This <script> tag needs to go in your main .html file, outside the root element.
JSFiddle Demo ~ https://jsfiddle.net/7u0aboe6/3/

Related

Mount a vue component to an element in Nuxt 3

I'm trying to mount a Vue component to an element that is rendered by a v-html directive. The parent Vue component is a table. Every table cell has richtext content (including images).
If there is an image in the richtext, I need to add an existing copyright component, that opens an overlay. So it can't be plain HTML.
The component looks as follows (simplified):
How do I do this?
<script lang="ts" setup>
import { onMounted, ref } from '#imports'
const tableEl = ref<Array<HTMLTableElement>>([])
const imageEls = ref<Array<HTMLImageElement>>([])
onMounted(() => {
const els = tableEl.value.querySelectorAll('p > img')
imageEls.value = Array.from(els) as Array<HTMLImageElement>
imageEls.value.forEach((imageEl) => {
const parent: HTMLParagraphElement = imageEl.parentElement as HTMLParagraphElement
parent.style.position = 'relative' // Up to this point, everything works...
// How do I add my "<CopyrightNotice/>" component here?
})
})
</script>
<template>
<div>
<table>
<tr v-for="row in rows" :key="row.id">
<td ref="tdEls" v-for="col in row.cols" v-html="col.content" :key="col.id" />
</tr>
</table>
</div>
</template>
Rendered, it looks like this:
<div>
<table>
<tr>
<td>
<p>Hello World!</p>
</td>
<td>
<p>Content</p>
<p>
<img src="/link/to/src.jpg" alt="a cat">
</p>
</td>
</tr>
</table>
</div>
Instead of working with DOM using JS it is better idea to render something called vnode.
Your solution can break Vuejs reactivity / virtual DOM.
Follow documentation here: Render Functions & JSX
There is an example with combining HTML elements and Vuejs components.

Vue.js not rendering table data [duplicate]

I'm struggling to develop a simple component and use it inside a loop:
<template id="measurement">
<tr class="d-flex">
</tr>
</template>
Vue.component('measurement', {
template: '#measurement',
props: {
name: String,
data: Object,
val: String,
},
});
This is obviously not functional yet but already fails:
<table v-for="(m, idx) in sortedMeters">
<measurement v-bind:data="m"></measurement>
</table>
gives ReferenceError: Can't find variable: m inside view. For a strange reason the same thing works, i.e. without error, in a paragraph:
<p v-for="(m, idx) in sortedMeters">
<measurement v-bind:data="m"></measurement>
</p>
What causes the variable to be not found?
PS.: here's a fiddle: https://jsfiddle.net/andig2/u47gh3w1/. It shows a different error as soon as the table is included.
Update It is intended that the loop produces multiple tables. Rows per table will be created by multiple measurements
TLDR: Before Vue is passed the DOM template, the browser is hoisting <measurement v-bind:name="i" v-bind:data="m"> outside the <table> (outside v-for context), leading to the errors in Vue. This is a known caveat of DOM template parsing.
The HTML spec requires the <table> contain only specific child elements:
<caption>
<colgroup>
<thead>
<tbody>
<tr>
<tfoot>
<script> or <template> intermixed with above
Similarly, the content model of <tr> is:
<td>
<th>
<script> or <template> intermixed with above
The DOM parser of compliant browsers automatically hoists disallowed elements – such as <measurement> – outside the table. This happens before the scripting stage (before Vue even gets to see it).
For instance, this markup:
<table>
<tr v-for="(m,i) in obj">
<measurement v-bind:name="i" v-bind:data="m"></measurement>
</tr>
</table>
...becomes this after DOM parsing (before any scripting):
<measurement v-bind:name="i" v-bind:data="m"></measurement> <!-- hoisted outside v-for -->
<table>
<tr v-for="(m,i) in obj">
</tr>
</table>
Notice how i and m are then outside the context of the v-for loop, which results in Vue runtime errors about i and m not defined (unless by chance your component coincidentally declared them already). m was intended to be bound to <measurement>'s data prop, but since that failed, data is simply its initial value (also undefined), causing the rendering of {{data.value}} to fail with Error in render: "TypeError: Cannot read property 'value' of undefined".
To demonstrate hoisting without these runtime errors and without Vue, run the code snippet below:
<table style="border: solid green">
<tr>
<div>1. hoisted outside</div>
<td>3. inside table</td>
2. also hoisted outside
</tr>
</table>
...then inspect the result in your browser's DevTools, which should look like this:
<div>1. hoisted outside</div>
2. also hoisted outside
<table style="border: solid green">
<tr>
<td>3. inside table</td>
</tr>
</table>
Solution 1: Use <tr is="measurement">
If you prefer DOM templates, you could use the is attribute on a <tr> to specify measurement as the type (as suggested by the Vue docs and by another answer). This first requires the <measurement> template use <td> or <th> as a container element inside <tr> to be valid HTML:
<template id="measurement">
<tr>
<td>{{name}} -> {{data.value}}</td>
</tr>
</template>
<div id="app">
<table v-for="(m,i) in sortedMeters">
<tr is="measurement" v-bind:name="i" v-bind:data="m" v-bind:key="i"></tr>
</table>
</div>
Vue.component('measurement', {
template: '#measurement',
props: {
name: String,
data: Object
}
})
new Vue({
el: '#app',
data: {
sortedMeters: {
apple: {value: 100},
banana: {value: 200}
},
}
})
<script src="https://unpkg.com/vue#2.6.11"></script>
<template id="measurement">
<tr>
<td>{{name}} -> {{data.value}}</td>
</tr>
</template>
<div id="app">
<table v-for="(m,i) in sortedMeters">
<tr is="measurement" v-bind:name="i" v-bind:data="m" v-bind:key="i"></tr>
</table>
</div>
Solution 2: Wrap <table> in component
If you prefer DOM templates, you could use a wrapper component for <table>, which would be able to contain <measurement> without the hoisting caveat.
Vue.component('my-table', {
template: `<table><slot/></table>`
})
<div id="app">
<my-table v-for="(m, i) in sortedMeters">
<measurement v-bind:name="i" v-bind:data="m"></measurement>
</my-table>
</div>
Vue.component('measurement', {
template: '#measurement',
props: {
name: String,
data: Object
}
})
Vue.component('my-table', {
template: `<table><slot/></table>`
})
new Vue({
el: '#app',
data: {
sortedMeters: {
apple: {value: 100},
banana: {value: 200}
},
}
})
<script src="https://unpkg.com/vue#2.6.11"></script>
<template id="measurement">
<tr>
<td>{{name}} -> {{data.value}}</td>
</tr>
</template>
<div id="app">
<my-table v-for="(m, i) in sortedMeters">
<measurement v-bind:name="i" v-bind:data="m"></measurement>
</my-table>
</div>
Solution 3: Move <table> markup into template string
You could move the entire <table> into a component's template string, where the DOM template caveats could be avoided. Similarly, you could move the <table> into a single file component, but I assume you have a significant need for DOM templates instead.
Vue.component('my-table', {
template: `<div>
<table v-for="(m, idx) in sortedMeters">
<measurement v-bind:data="m"></measurement>
</table>
</div>`,
props: {
sortedMeters: Object
}
})
<div id="app">
<my-table v-bind:sorted-meters="sortedMeters"></my-table>
</div>
Vue.component('measurement', {
template: '#measurement',
props: {
name: String,
data: Object
}
})
Vue.component('my-table', {
template: `<div>
<table v-for="(m,i) in sortedMeters">
<measurement v-bind:name="i" v-bind:data="m" v-bind:key="i"></measurement>
</table>
</div>`,
props: {
sortedMeters: Object
}
})
new Vue({
el: '#app',
data: {
sortedMeters: {
apple: {value: 100},
banana: {value: 200}
},
}
})
<script src="https://unpkg.com/vue#2.6.11"></script>
<template id="measurement">
<tr>
<td>{{name}} -> {{data.value}}</td>
</tr>
</template>
<div id="app">
<my-table :sorted-meters="sortedMeters"></my-table>
</div>
If you replace
<table v-for="(m, idx) in sortedMeters">
<measurement v-bind:data="m"></measurement>
</table>
with
<template v-for="(m, idx) in sortedMeters">
<table>
<measurement v-bind:data="m"></measurement>
</table>
</template>
You'll end up with working code.
But you'll most likely want to use
<table>
<template v-for="(m, idx) in sortedMeters">
<measurement v-bind:data="m"></measurement>
</template>
</table>
or
<table>
<measurement v-for="(m, idx) in sortedMeters" v-bind:data="m"></measurement>
</table>
It's because you've missing <td> inside <tr>. Without it your component produces invalid html markup and extracts "slot" data outside <tr> causing error.
Your template should looks like:
<template id="measurement">
<tr>
<td>{{name}} -> {{data.value}}</td>
</tr>
</template>
You also need to move v-for to measurement:
<table border="1">
<tr is="measurement" v-for="(m,index) in obj" :key="index" v-bind:name="index" v-bind:data="m"></measurement>
</table>
You can use is attribute to set component name.
Working fiddle: https://jsfiddle.net/cyaj0ukh/

Working in vue.js how am I to pull and post data from my google spreadsheet?

I am new to coding. I'm trying to make a simple website that pulls the data from my google spreadsheet. I am using Vue.js as it seems to be reasonably easy to cycle through my data to create a table. However, now I have a mix of code that honestly... I don't quite understand... created by me going through about 500 youtube and stackoverflow tutorials/responses.
But for some reason I can't get the data to pull from my spreadsheet. If someone can point me in the right direction on what exactly I'm doing wrong... I would be so grateful. =)
<template>
<div class="container">
<tbody>
<table style="width:100%">
<tr>
<th v-for="header in headers" :key="header">{{ header }}</th>
</tr>
<tr v-for="row in s" :key="row.a">
<td> {{ row.b}} </td>
<td> {{ row.c}} </td>
<td> {{ row.d}} </td>
<td> {{ row.e}} </td>
</tr>
</table>
<br>
<div v-for="item in items" :key="item.value">
<p>{{ item }}</p>
</div>
</tbody>
</div>
</template>
<style>
const { GoogleSpreadsheet } = require('google-spreadsheet');
const creds = require('creds.json');
import { vueGsheets } from 'vue-gsheets'
import axios from 'axios';
export default {
mixins: [vueGsheets],
data() {
return {
rows:[],
api: {
baseUrl: "https://sheets.googleapis.com/v4/spreadsheets/spreadsheetId/values:ranges=A!B1:F1?key=<key-here>",
"spreadsheetId": '<myid>',
get return() {
return this.return;
},
},
}
},
methods: {
getData(apiUrl) {
axios.get(apiUrl).then((res) => {
this.rows = res.data.valueRanges;
console.log(this.rows);
const { baseUrl } = this.api;
});
}
}
};
</script>
The problem is, that you try to enclose your JavaScript code in a <style> tag and close it with a <script> tag. That doesn´t work.
You have to use this structure:
<template>
// Your HTML
</template>
<script>
// Your Javsscript code
</script>
<style scoped>
// Your CSS
</style>
You can read more about the structure of Vue.js components here: Single File Components

Can't re-render html in vee-validate component

Vue.js v2.6.11 / vee-validate v3.2.2
I have a button that will push new element to form.demand (data in vue app) on click event.
And if form.demand update, html in v-for should be updated.
After I wrap it in vee-validate component , it not works.
form.demand will update, but v-for won't.
I try to put same html in test-component, when form.demand update, v-for update too.
I can't figure out why...
following is my code:
HTML
<div id="content">
<test-component>
<div v-for="demand in form.demand">{{demand}}</div>
</test-component>
<validation-provider rule="" v-slot="v">
<div #click="addDemand">new</div>
<div v-for="(demand,index) in form.demand">
<div>{{demand.name}}</div>
<div>{{demand.count}}</div>
<input type="text" :name="'demand['+index+'][name]'" v-model="form.demand[index].name" hidden="hidden" />
<input type="text" :name="'demand['+index+'][count]'" v-model="form.demand[index].count" hidden="hidden" />
</div>
</validation-provider>
</div>
javascript
Vue.component('validation-provider', VeeValidate.ValidationProvider);
Vue.component('validation-observer', VeeValidate.ValidationObserver);
Vue.component('test-component',{
template: `
<div>
<slot></slot>
</div>
`
})
var app = new Vue({
el: "#content",
data: {
form: {
demand: [],
},
},
methods: {
addDemand(){
this.form.demand.push({
name : "demand name",
count: 1
})
}
})
------------Try to use computed & Add :key----------------
It's still not work. I get same result after this change.
HTML
<validation-provider rule="" v-slot="v">
<div #click="addDemand">new</div>
<div v-for="(demand,index) in computed_demand" :key="index">
<!--.........omitted.........-->
</validation-provider>
Javascript
var app = new Vue({
el: "#content",
// .......omitted
computed:{
computed_demand() {
return this.form.demand;
}
},
})
I think I found the problem : import Vue from two different source. In HTML, I import Vue from cdn. And import vee-validate like following:
vee-validate.esm.js
import Vue from './vue.esm.browser.min.js';
/*omitted*/
validator.js
import * as VeeValidate from './vee-validate.esm.js';
export { veeValidate };
main.js
// I didn't import Vue from vue in this file
import { veeValidate as VeeValidate } from './validator.js';
Vue.component('validation-provider', VeeValidate.ValidationProvider);
HTML
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- at end of body -->
<script src="/static/javascripts/main.js" type="module"></script>
</body>
After I fix this( import vee-validate from cdn, or import Vue by ES6 module).
It works, although it still have infinite loop issue with vee-validate.
Sorry for I didn't notice that import vue from two different source.
Please provide a key in you v-for. see code below
<div v-for="(demand,index) in form.demand" :key="index">
<div>{{demand.name}}</div>
<div>{{demand.count}}</div>
<input type="text" :name="'demand['+index+'][name]'" v-model="form.demand[index].name" hidden="hidden" />
<input type="text" :name="'demand['+index+'][count]'" v-model="form.demand[index].count" hidden="hidden" />
</div>
Or, make a computed property that will hold your form.demands array, like this one
computed: {
form_demands: function() {
return this.form.demand
}
}
then call this computed property in your v-for
<div v-for="(demand,index) in form_demands" :key="index">
<div>{{demand.name}}</div>
<div>{{demand.count}}</div>
<input type="text" :name="'demand['+index+'][name]'" v-model="form.demand[index].name" hidden="hidden" />
<input type="text" :name="'demand['+index+'][count]'" v-model="form.demand[index].count" hidden="hidden" />
</div>
Or, use the vue forceUpdate method
import Vue from 'vue';
Vue.forceUpdate();
Then in your component, just call the method after you add demand
this.$forceUpdate();
It is recommended to provide a key with v-for whenever possible,
unless the iterated DOM content is simple, or you are intentionally
relying on the default behavior for performance gains.

vuejs component hides everything after if not put inside a div

I started using vuejs with parcel. I have a main component App.vue from which I call a subcomponent Hello.vue using <Hello/> in App's template. I have a weird bug if I don't put the <Hello/> inside a div tag, everything that comes after in html doesn't show. The code is below:
index.js
import Vue from "vue";
import App from "./App";
new Vue({
el: "#app",
components: { App },
template: "<App/>"
});
App.vue
<template>
<div id="app">
<h3>bla bla</h3>
<div><Hello/></div>
<!-- if not put inside a div, hides everything after-->
<h2>test</h2>
<p>kldsfnlkdsjfldsfds</p>
<h5>skjdnsqkfdnlkdsqf</h5>
</div>
</template>
<script>
import Hello from "./components/Hello";
export default {
name: "App",
components: {
Hello
}
};
</script>
<style>
</style>
Hello.vue
<template>
<div>
<h1>{{ message }}</h1>
<h2>Hello {{ person.firstname}} {{person.lastname}}</h2>
<label>
Firstname:
<input type="text" v-model="person.firstname">
</label>
<label>
Lastname:
<input type="text" v-model="person.lastname">
</label>
<label>
Message:
<input type="text" v-model="message">
</label>
</div>
</template>
<script>
export default {
data() {
return {
person: {
firstname: "John",
lastname: "Doe"
},
message: "Welcome !"
};
}
};
</script>
<style>
</style>
Here is a screenshot of what I get without wrapping <Hello/> with a <div></div>
And then with a div:
Thanks !
EDIT: I don't get an error in the console. I forgot to add that I tried with webpack and I don't get this bug, so It's most likely related to parcel.
Some browsers do not display elements correctly if they use <foo /> without a closing tag, instead of <foo></foo>.
If items are not rendered with the closing tag, this may be your issue.
Some vue components will generate the closing tag from your template, even though you do not have it in your source, and others will not.
When you use a SFC (Single File Component) you must have only one element inside the <template>. Then, inside that one element you can have as many other elements as you like.
Have a look at the "Example sandbox" Simple to do app in the official documentation: https://v2.vuejs.org/v2/guide/single-file-components.html#Example-Sandbox
The file ToDoList.vue is a good example in here: https://codesandbox.io/s/o29j95wx9

Categories

Resources