VueJs data binding issue - vue.js

I try to bind attributes and some data to my template but the code below doesn't work. What I need is to render n-amount of templates depends of amount of printedForms objects and implement in each template data from proper object.
Please give any ideas what wrong with my code.
P.S. I've warning in console as follows:
[Vue warn]: Error when evaluating expression "printedForm.docNumber": TypeError: Cannot read property 'docNumber' of undefined (found in component: )
<div id="app">
<printing-form v-for="printedForm in printedForms" track-by="id"></printing-form>
</div>
<template id="printingForm-template">
<img v-bind="printedForm.attributes">
<div>{{ printedForm.docNumber }}</div>
</template>
My VueJs code below:
Vue.component('printing-form', {
template: '#printingForm-template'
});
new Vue({
el: '#app',
data: {
printedForms: [
{
id: 1,
docNumber: 7,
attributes: {
src: '/waybill/img/4p_bus.png',
width: 1400,
height: 980
}
},
{
id: 2,
docNumber: 7777,
attributes: {
src: '/waybill/img/4p_cargo.png',
width: 1400,
height: 980
}
},
{
id: 3,
docNumber: 10000,
attributes: {
src: '/waybill/img/4p_selfMove.png',
width: 1400,
height: 980
}
}
]
}
});

you need to bind a printedForm property :printed-form="printedForm" like that
<printing-form v-for="printedForm in printedForms"
track-by="id" :printed-form="printedForm"></printing-form>
and define it in component props
Vue.component('printing-form', {
template: '#printingForm-template',
props: ['printedForm']
});
Vue props
Notice when using camelCased prop names as attributes, you need to use their kebab-case (hyphen-delimited) equivalents
Vue Docs

Related

Pass array data to component and make

I try to get familiar with Vue2.
As you can see below I try to pass in the "markers"-Array but the map does not change. How can I change the markers array? How can I make the markers array reactive so that map markers change?
Thanks for helping me!
views/Map.vue
<template>
<div id="MapWrapper">
<Map :markers = "markers"/>
</div>
</template>
<script>
import Map from 'src/components/MapSoal.vue'
export default {
name: 'MapView',
components: {
Map
},
data () {
return {
markers: [
{name: "greenMarker", lngLat: [13.8022177, 51.0069449], color: "green", text: "<h1>This is the green marker</h1>"},
{name: "orangeMarker", lngLat: [13.8022177 - 0.05, 51.0069449], color: "orange", text: "<h1>this is the orange marker</h1>"},
]
}
}
}
</script>
components/MapSoal.vue
<template>
<MglMap
:accessToken="accessToken"
:mapStyle="mapStyle"
:center="center"
:zoom="zoom"
>
<MglMarker v-for="marker in markers" :key="marker.name" :coordinates="marker.lngLat" :color="marker.color">
<MglPopup>
<VCard v-html="marker.text"></VCard>
</MglPopup>
</MglMarker>
<MglGeolocateControl></MglGeolocateControl>
<MglNavigationControl></MglnavigationControl>
</MglMap>
</template>
export default {
data() {
return {
accessToken: 'SOME_API_KEY',
mapStyle: 'mapbox://styles/mapbox/streets-v11?optimize=true',
center: [13.8022177, 51.0069449],
zoom: 9, // starting zoom
markers: []
};
},
}
You're not declaring markers as a prop in your Map component, you're declaring markers in your data object, so the Map component's got a separate data variable named markers, completely unrelated to your parent component's data. Try removing your markers array from data in your Map component and add it in a props object in the component like this:
props: {
markers: {
type: Array
}
},
or
props: ['markers'],
so that your Map component looks like this:
<template>
<MglMap
:accessToken="accessToken"
:mapStyle="mapStyle"
:center="center"
:zoom="zoom"
>
<MglMarker v-for="marker in markers" :key="marker.name" :coordinates="marker.lngLat" :color="marker.color">
<MglPopup>
<VCard v-html="marker.text"></VCard>
</MglPopup>
</MglMarker>
<MglGeolocateControl></MglGeolocateControl>
<MglNavigationControl></MglnavigationControl>
</MglMap>
</template>
export default {
props: ['markers'],
data() {
return {
accessToken: 'SOME_API_KEY',
mapStyle: 'mapbox://styles/mapbox/streets-v11?optimize=true',
center: [13.8022177, 51.0069449],
zoom: 9, // starting zoom
};
},
}

Vue Component Not Displaying Nested Div

I have a component that works fine. However, when I appended <div :id="hint"></div> to the component this specific DIV isn't rendering. I'm sure it has something to do with the way I'm using a DIV to reference the template in my HTML but I don't know how to refine it.
<div
ref="boxAnswers"
is="box-answers"
v-for="box in boxes.slice().reverse()"
v-bind:key="box.id"
v-bind:level="box.level"
v-bind:hint="box.hint"
></div>
Vue.component('box-answers', {
props: ['level','hint'],
template: '<div class="droppable answer ui-widget-header" :id="level"></div><div :id="hint"></div>'
});
new Vue({
el: '#mainapp',
data: {
boxes: [
{ id: 1, level: 'baselevel-1', hint: 'hint-1' },
{ id: 2, level: 'baselevel-2', hint: 'hint-2' },
{ id: 3, level: 'baselevel-3', hint: 'hint-3' },
{ id: 4, level: 'baselevel-4', hint: 'hint-4' },
{ id: 5, level: 'baselevel-5', hint: 'hint-5' }
]
}
});

Trigger child component method from parent event

I am very new to VueJS and JavaScript and your help would be much appreciated.
My method "greet" is not working and I am unsure why. When I click on the button "change to bar" I get the error:
[Vue warn]: Property or method "greet" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
found in
---> <Chatjsvue> at src\components\vueChartjs\Chatjsvue.vue
<App> at src\App.vue
<Root>
chartjs.vue
<template src="../../views/chartjshtml/chartsjs.html"></template>
<script>
import LineChart from '#/assets/javascripts/chartjsline'
export default {
components: {
'line-chart': LineChart
}
}
</script>
chartsjs.html
<div class="wrapper">
<div>
<ul>
<li><button v-on:click="greet()">change to bar</button></li>
<li><line-chart></line-chart></li>
</ul>
</div>
</div>
chartsjsline.js
import { Line } from 'vue-chartjs'
export default {
extends: Line,
data() {
return {
datacollection: {
//Data to be represented on x-axis
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
datasets: [{
label: 'Activities ChartJS Line',
backgroundColor: '#f87979',
pointBackgroundColor: 'white',
borderWidth: 1,
pointBorderColor: '#249EBF',
//Data to be represented on y-axis
data: [40, 20, 30, 50, 90, 10, 20, 40, 50, 70, 90, 100]
}]
},
//Chart.js options that controls the appearance of the chart
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
},
gridLines: {
display: true
}
}],
xAxes: [{
gridLines: {
display: false
}
}]
},
legend: {
display: true
},
responsive: true,
maintainAspectRatio: false
},
}
},
methods: {
greet() {
alert('hello');
}
},
mounted() {
//renderChart function renders the chart with the datacollection and options object.
this.renderChart(this.datacollection, this.options)
}
}
Your <line-chart> component (instance of LineChart) is a child component of your Chatjsvue component.
The child methods remain bound to those instances.
However it is extremely easy from the parent component to access its child component, and from there to execute their methods:
Keep a reference to your child component, using the ref special attribute: <line-chart ref="myLineChart"></line-chart>
Within a parent method, access the referred child using this.$refs.myLineChart.
Then you have access to everything on this child instance, including its methods: this.$refs.myLineChart.greet()
Working example:
Vue.component('chart-js', {
template: '#chartjs',
methods: {
lineChildGreet() {
// Access the child component through $refs.
// Then you can execute its methods.
this.$refs.myLineChart.greet();
},
},
});
Vue.component('line-chart', {
template: '#lineChart',
methods: {
greet() {
alert('hello');
},
},
});
var app = new Vue({
el: '#app',
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<chart-js></chart-js>
</div>
<template id="chartjs">
<div class="wrapper">
<div>
<ul>
<li><button v-on:click="lineChildGreet">greet on child component</button></li>
<!-- Keep a REFerence to the child component -->
<li><line-chart ref="myLineChart"></line-chart></li>
</ul>
</div>
</div>
</template>
<template id="lineChart">
<span>LineChart</span>
</template>
You need to add the greet() method to the chartjs.vue component, instead of chartsjsline.

JSON and v-if troubles with Vue.js

In the following example neither of the v-if related divs seem to get rendered before or after clicking the Add button. It seems like Vue.js isn't running any updates when the pizzas JSON object is updated.
Is there a solution to this problem without resorting to changing the pizzas variable into being an array?
<div id="app">
<div v-for="pizza in pizzas">
{{ pizza }}
</div>
<div v-if="totalPizzas === 0">
No pizza. :(
</div>
<div v-if="totalPizzas > 0">
Finally, some pizza! :D
</div>
<button #click="add">Add</button>
</div>
var app = new Vue({
el: '#app',
data: {
pizzas: {}
},
methods: {
add: function() {
this.pizzas['pepperoni'] = { size: 16, toppings: [ 'pepperoni', 'cheese' ] };
this.pizzas['meaty madness'] = { size: 14, toppings: [ 'meatballs', 'sausage', 'cajun chicken', 'pepperoni' ] };
},
totalPizzas: function() {
return Object.keys(this.pizzas).length;
}
}
});
There are several things to be improved in your code. Most of them are about syntax. For example, methods should be called, but computed properties can be queried directly: that's why it's #click="add()", but totalPizzas === 0 makes sense only if it's a computed property.
The crucial thing to understand, however, is how reactivity works in VueJS. See, while you change your object innards, adding new properties to it, this change is not detected by VueJS. Quoting the docs:
Vue does not allow dynamically adding new root-level reactive
properties to an already created instance. However, it’s possible to
add reactive properties to a nested object using the Vue.set(object, key, value) method:
Vue.set(vm.someObject, 'b', 2)
You can also use the vm.$set instance method, which is an alias to the
global Vue.set:
this.$set(this.someObject, 'b', 2)
Sometimes you may want to assign a number of properties to an existing
object, for example using Object.assign() or _.extend(). However, new
properties added to the object will not trigger changes. In such
cases, create a fresh object with properties from both the original
object and the mixin object:
// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
And this is how it might work:
var app = new Vue({
el: '#app',
data: {
pizzas: {}
},
computed: {
totalPizzas: function() {
return Object.keys(this.pizzas).length;
}
},
methods: {
add: function() {
this.pizzas = Object.assign({}, this.pizzas, {
pepperoni: { size: 16, toppings: [ 'pepperoni', 'cheese' ] },
['meaty madness']: { size: 14, toppings: [ 'meatballs', 'sausage', 'cajun chicken', 'pepperoni' ] }
});
},
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<div v-for="pizza in pizzas">
Size: {{ pizza.size }} inches
Toppings: {{ pizza.toppings.join(' and ') }}
</div>
<div v-if="totalPizzas === 0">
No pizza. :(
</div>
<div v-if="totalPizzas > 0">
Finally, some pizza! :D
</div>
<button #click="add()">Add</button>
</div>

In vue2 v-for nested component props aren't updated after element is removed in parent

For my app I'm using two Vue components. One that renders a list of "days" and one that renders for each "day" the list of "locations". So for example "day 1" can have the locations "Berlin", "London", "New York".
Everything gets rendered ok but after removing the "Day 1" from the list of days the view isn't rendered corrected. This is what happens:
The title of the day that was removed is replaced -> Correct
The content of the day that was removed isn't replaced -> Not correct
Vue.component('day-list', {
props: ['days'],
template: '<div><div v-for="(day, index) in dayItems">{{ day.name }} Remove day<location-list :locations="day.locations"></location-list><br/></div></div>',
data: function() {
return {
dayItems: this.days
}
},
methods: {
remove(index) {
this.dayItems.splice(index, 1);
}
}
});
Vue.component('location-list', {
props: ['locations', 'services'],
template: '<div><div v-for="(location, index) in locationItems">{{ location.name }} <a href="#" #click.prevent="remove(index)"</div></div>',
data: function() {
return {
locationItems: this.locations
}
},
methods: {
remove(index) {
this.locationItems.splice(index, 1);
}
}
});
const app = window.app = new Vue({
el: '#app',
data: function() {
return {
days: [
{
name: 'Day 1',
locations: [
{name: 'Berlin'},
{name: 'London'},
{name: 'New York'}
]
},
{
name: 'Day 2',
locations: [
{name: 'Moscow'},
{name: 'Seul'},
{name: 'Paris'}
]
}
]
}
},
methods: {}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
<day-list :days="days"></day-list>
</div>
Please use Vue-devtools if you are not already using it. It shows the problem clearly, as seen in the image below:
As you can see above, your day-list component comprises of all the days you have in the original list, with locations listed out directly. You need one more component in between, call it day-details, which will render the info for a particular day. You may have the location-list inside the day-details.
Here is the updated code which works:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
<day-list :days="days"></day-list>
</div>
Vue.component('day-list', {
props: ['days'],
template: `
<div>
<day-details :day="day" v-for="(day, index) in days">
Remove day
</day-details>
</div>`,
methods: {
remove(index) {
this.days.splice(index, 1);
}
}
});
Vue.component('day-details', {
props: ['day'],
template: `
<div>
{{ day.name }}
<slot></slot>
<location-list :locations="day.locations"></location-list>
<br/>
</div>`
});
Vue.component('location-list', {
props: ['locations', 'services'],
template: `
<div>
<div v-for="(location, index) in locations">
{{ location.name }}
[x]
</div>
</div>
`,
methods: {
remove(index) {
this.locations.splice(index, 1);
}
}
});
const app = window.app = new Vue({
el: '#app',
data: function() {
return {
days: [{
name: 'Day 1',
locations: [{
name: 'Berlin'
}, {
name: 'London'
}, {
name: 'New York'
}]
}, {
name: 'Day 2',
locations: [{
name: 'Moscow'
}, {
name: 'Seul'
}, {
name: 'Paris'
}]
}]
}
},
methods: {}
});
One other thing - your template for location-list has an error - you are not closing the <a> element. You may use backtick operator to have multi-line templates as seen in the example above, to avoid template errors.
Also you are not supposed to change objects that are passed via props. It works here because you are passing objects which are passed by reference. But a string object getting modified in child component will result in this error:
[Vue warn]: Avoid mutating a prop directly...
If you ever get this error, you may use event mechanism as explained in the answer for this question: Delete a Vue child component