Vue js dynamic data component - javascript

I'm passing some dynamic data from a parent component to a child component using props .. So I would like to know how I can add myColor prop to total value and show it an render the result in a final value.
I've already update the post with the parent component (shapes) and the child component (colors)
I'm using Vue 2 and webpack.
//parent component
<v-layout row wrap primary-title v-for="shape in shapes" :key="shape.id">
<v-layout column>
<v-flex >
<v-subheader>{{shape.name}} {{shape.price}}€ {{selectedShape.price}}</v-subheader>
</v-flex>
</v-layout>
</v-layout>
<my-colors :myShape="selectedShape.price"></my-colors>
<script>
import Colors from './Colors.vue';
export default {
components: {
Colors
},
data() {
return {
selectedShape: {},
shapes: [{
id: 1,
name: "Square",
price: 4,
href: "../../static/square.jpg"
}, {
id: 2,
name: "Circle",
price: 6,
href: "../../static/circle.jpg"
}]
}
},
computed: {
totalShape: function() {
var totalShape = 0;
for (var i = 0; i < this.shapes.length; i++) {
if (this.shapes[i].selected) {
totalShape += this.shapes[i].price;
}
}
return totalShape;
}
},
methods: {
getSelectedShape() {
return this.selectedShape;
},
}
}
</script>
//child component
<v-layout>
<v-layout>
<v-flex >
<h3 >Total price:</h3>
</v-flex>
</v-layout>
<v-layout>
<v-flex
<v-subheader> {{total}} {{myShape}} €</v-subheader>
</v-flex>
</v-layout>
</v-layout>
<script>
export default {
props: ['myShape'],
data: () => ({
checked1: '',
showCart: false,
colors: [{
id: 1,
name: "white",
price: 2,
checked: '',
}, {
id: 2,
name: "black",
price: 2.0,
checked: '',
}, {
id: 3,
name: "Grey",
price: 2.25,
checked: '',
}, {
id: 4,
name: "Blue",
price: 1.6,
checked: '',
}, {
id: 5,
name: "Red",
price: 2.5,
checked: '',
}, {
id: 6,
name: "Yellow",
price: 2.75,
checked: '',
}],
}),
computed: {
total: function() {
var total = 0;
for (var i = 0; i < this.colors.length; i++) {
if (this.colors[i].checked) {
total += this.colors[i].price;
}
}
return total;
},
},
}
</script>

I do not understand your needs from this script, but be aware of one way data flow in Vue. So, you can send data from parent component to child component in which its will be accessible through props, but not from child component to parent. Use Vuex if you need two-way data flow between components.
var child = {
template: '#child',
props: ['fromParent']
}
Vue.component('parent', {
template: '#parent',
components: {
child: child
},
props: ['fromInstance']
})
new Vue({
el: '#app',
data: {
instanceData: {
text: 'Original value'
}
},
created () {
var self = this
setTimeout(_ => self.instanceData.text = 'Changed value', 3000)
}
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<parent :from-instance="this.instanceData"></parent>
</div>
<template id="parent">
<div>
<child :from-parent="this.fromInstance"></child>
</div>
</template>
<template id="child">
<p>{{this.fromParent.text}}</p>
</template>
Example with SELECT:
var child = {
template: '#child',
props: ['selected']
}
Vue.component('parent', {
template: '#parent',
components: {
child: child
},
props: ['options'],
data () {
return {
parentCar: 'none'
}
},
methods: {
update (e) {
this.parentCar = e.target.value
}
}
})
new Vue({
el: '#app',
data: {
items: {
audi: 'Audi',
bmw: 'BMW',
mercedes: 'Mercedes',
}
}
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<parent :options="this.items"></parent>
</div>
<template id="parent">
<div>
<select #change="update">
<option value="none" selected>Car</option>
<option v-for="(value, key) in options" :value="key">
{{ value }}
</option>
</select>
<child :selected="this.parentCar"></child>
</div>
</template>
<template id="child">
<p>{{ selected }}</p>
</template>
Example with checked / unchecked checkbox:
var child = {
template: '#child',
props: ['checked']
}
Vue.component('parent', {
template: '#parent',
components: {
child: child
},
data () {
return {
checkbox: false
}
}
})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<parent></parent>
</div>
<template id="parent">
<div>
<input type="checkbox" v-model="checkbox">
<child :checked="this.checkbox"></child>
</div>
</template>
<template id="child">
<p>{{ checked }}</p>
</template>

Related

vue #/src/assets/images/1.jpg: hasn't been transpiled yet error

**I'm getting this error - vue #/src/assets/images/1.jpg: hasn't been transpiled yet error. I'm looping through tha App component static array. The src is specified correctly though. Using require vue method.
https://codesandbox.io/s/eloquent-grass-j1usw8?file=/src/components/v-carousel-item.vue
**
// APP
<template>
<v-carousel :carousel_data="sliderItems" />
</template>
<script>
import vCarousel from "./components/v-carousel.vue";
export default {
name: "App",
data() {
return {
sliderItems: [
{ id: 1, name: "img1", img: "1.jpg" },
{ id: 2, name: "img2", img: "2.jpg" },
{ id: 3, name: "img3", img: "3.jpg" },
],
};
},
components: {
vCarousel,
},
};
</script>
// Parent
<template>
<div class="container">
<div class="v-carousel">
<v-carousel-item
v-for="item in carousel_data"
:key="item.id"
:item_data="item"
/>
</div>
</div>
</template>
<script>
import vCarouselItem from "./v-carousel-item.vue";
export default {
components: {
vCarouselItem,
},
props: {
carousel_data: {
type: Array,
default: () => [],
},
},
};
</script>
// Child
<template>
<div class="v-carousel-item">
<img :src="require(`../assets/images/` + item_data.img)" alt="" />
</div>
</template>
<script>
export default {
props: {
item_data: {
type: Object,
default: () => {},
},
},
};
</script>
You want to require the images upfront.
export default {
name: "App",
data() {
return {
sliderItems: [
{ id: 1, name: "img1", img: require("#/assets/images/1.jpg") },
{ id: 2, name: "img2", img: require("#/assets/images/2.jpg") },
{ id: 3, name: "img3", img: require("#/assets/images/3.jpg") },
],
};
},
Then update the carousel item component.
<div class="v-carousel-item">
<img :src="item_data.img" alt="" />
</div>
Example: https://codesandbox.io/s/little-bush-ino5zc?file=/src/components/v-carousel-item.vue:11-91

How to hang click on only one element using v-for?

Hi I'm new to vue and I'm trying to complete one task. I have dynamic component toggles which I render with v-for. Can you suggest how can I pass an extra click to only one button (button 'border-left')? Desirable illustrative examples
<script>
import Vue from "vue";
import BorderLeftComonent from "./BorderLeftComonent.vue";
import TextBalloonComponent from "./TextBalloonComponent.vue";
import DashedComponent from "./DashedComponent.vue";
export default Vue.extend({
data() {
return {
component: "button[0].name",
color: "",
buttons: [
{
label: "A",
isActive: false,
type: "border-left",
name: "BorderLeftComonent",
},
{
label: "A",
isActive: false,
type: "text-balloon",
name: "TextBalloonComponent"
},
{
label: "A",
isActive: false,
type: "dashed",
name: "DashedComponent"
},
],
};
},
methods: {
toggleShowPopup() {
this.isOpen = !this.isOpen;
},
activeBtn(event, index) {
this.buttons[index].isActive = !this.buttons[index].isActive;
}
},
computed: {
currentComponent() {
return this.component;
},
cssVars() {
return {
'--border-left': this.color,
}
}
},
</script>
template is presented here
<template>
<div id="btn-box">
<button
v-for="(button, index) in buttons"
:key="index"
:class="button.isActive ? 'on' : 'off'"
#click="component = button.name, activeBtn($event, index)">
<div :class="`btn btn-${button.type}`">{{ button.label }}</div>
</button>
</div>
</template>
the method i need to pass to only one button
toggleShowPopup() {
this.isOpen = !this.isOpen;
}
You need conditional event binding. Try this:
<button
v-for="(button, index) in buttons"
:key="index"
:class="button.isActive ? 'on' : 'off'"
#click="component = button.name, activeBtn($event, index), button.type === 'border-left' && toggleShowPopup()">
<div :class="`btn btn-${button.type}`">{{ button.label }}</div>
</button>
This way might be a little more cleaned-up:
//import BorderLeftComonent from "./BorderLeftComonent.vue";
//import TextBalloonComponent from "./TextBalloonComponent.vue";
//import DashedComponent from "./DashedComponent.vue";
/*export default */new Vue/*.extend*/({
el: '#container',
template: `<div>
<div id="btn-box">
<button
v-for="(button, index) in buttons"
:key="index"
:class="button.isActive ? 'on' : 'off'"
:data-index="index"
#click="button.method">
<div :class="${"`"}btn btn-${"$"}{button.type}${"`"}">{{ button.label }}</div>
</button>
</div>
</div>`,
data() {
return {
component: "button[0].name",
color: "",
buttons: [
{
label: "A",
isActive: false,
type: "border-left",
name: "BorderLeftComonent",
method: this.toggleShowPopup,
},
{
label: "A",
isActive: false,
type: "text-balloon",
name: "TextBalloonComponent",
method: this.handleClick,
},
{
label: "A",
isActive: false,
type: "dashed",
name: "DashedComponent",
method: this.handleClick,
},
],
};
},
methods: {
toggleShowPopup(event) {
this.isOpen = !this.isOpen;
this.handleClick(event)
},
activeBtn(event) {
index = event.currentTarget.dataset.index
this.buttons[index].isActive = !this.buttons[index].isActive;
},
handleClick(event) {
index = event.currentTarget.dataset.index
button = this.buttons[index]
this.component = button.name;
this.activeBtn(event)
},
},
computed: {
currentComponent() {
return this.component;
},
cssVars() {
return {
'--border-left': this.color,
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="container"></div>

Vue 3 how to rerender properly when prop value is updated

I have a Vue component to display a list of data. It should receive the data and sortBy as props and render correctly. However, I have an extra button in the component that I want to update sortBy and re-render the new list but I don't know how to assign new data to computed property sortedData. Thanks a lot if I can have some advices.
<template>
<div>
<template v-for="(item, index) in sortedData" :key="index">
{{ item.name }}
</template>
<button #click.prevent="sortWith('color')">sort Color</button>
</div>
</template>
<script>
export default {
props: {
data: {
type: Array,
default: []
},
sortBy: {
type: String,
default: 'name'
},
},
methods: {
sort(array, sortBy) {
return array.sort(function (a, b) {
return b[sortBy] - a[sortBy]
})
},
sortWith(sortBy) {
// Need to sort the list and re-render new order
}
}
computed: {
sortedData() {
return this.sort(this.data, this.sortBy)
}
}
}
</script>
Try like following snippet:
const app = Vue.createApp({
data() {
return {
items: [{name: 'aaa', color: 'red'}, {name: 'ccc', color: 'purple'}, {name: 'fff', color: 'yellow'}, {name: 'bbb', color: 'blue'}, {name: 'eee', color: 'green'}],
sortingBy: 'name',
sortingOrder: false
}
},
methods: {
setSorting(item) {
this.sortingBy = item
this.sortingOrder = !this.sortingOrder
}
}
})
app.component('child', {
template: `
<div>
<template v-for="(item, index) in sortedData" :key="index">
<div>{{ item.name }} - {{ item.color }}</div>
</template>
<div class="sort">
<p>Sort By (child) :</p>
<div v-for="(title, i) in Object.keys(data[0])" :key="i" class="fields">
{{ title }}
</div>
</div>
</div>
`,
props: {
data: {
type: Array,
default: []
},
sortBy: {
type: String,
default: 'name'
},
sortOr: {
type: String,
default: false
},
},
data() {
return {
sortField: this.sortBy,
sortOrder: this.sortOr
}
},
methods: {
sort(array, sortBy) {
return array.sort((a, b) => {
let x = a[sortBy].toLowerCase()
let y = b[sortBy].toLowerCase()
if (this.sortOrder) {
return x > y ? -1 : 1;
} else {
return x > y ? 1 : -1;
}
return 0
})
},
sorting(item) {
this.sortField = item
this.sortOrder = !this.sortOrder
}
},
computed: {
sortedData() {
return this.sort(this.data, this.sortField)
}
},
watch: {
sortBy: function (val) {
this.sortField = val
},
sortOr: function (val) {
this.sortOrder = val
},
}
})
app.mount('#demo')
.sort {
display: flex;
align-items: center;
}
.fields {
margin-left: 1em;
}
<script src="https://unpkg.com/vue#3.2.29/dist/vue.global.prod.js"></script>
<div id="demo">
<div class="sort">
<p>Sort By (parent) :</p>
<div v-for="(title, i) in Object.keys(items[0])" :key="i" class="fields">
{{ title }}
</div>
</div>
<child :data="items" :sort-by="sortingBy" :sort-or="sortingOrder"></child>
</div>

Vue.js update parent data with an input from a child component

I'm using Vue.js 2 and I'm trying to update the description of a file using an input in a child component. I've been reading a few related questions and read some of the official docs along with .sync but I'm struggling to get the result I want since files is a list of objects.
Here's what I've been trying.
Vue.component('myComponent', {
props: ["file"],
data() {
return {
myDescription: '',
}
},
mounted() {
this.myDescription = this.file.description;
},
template: `
<div>
<label>{{ file.name }}</label>
<br>
<input type="text" #input="update" :value="myDescription"></input>
<br><br>
</div>
`,
methods: {
update() {
this.$emit("update-description", this.myDescription, this.file);
},
}
})
var app = new Vue({
el: '#app',
methods: {
updateDescription(description, file) {
console.log(description);
}
},
data: {
files: [{
id: 1,
name: "Hello",
description: "",
},
{
id: 2,
name: "World",
description: "Foo",
},
{
id: 3,
name: "John",
description: "Bar",
}
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div> {{ files }} </div>
<br>
<my-component v-for="file in files" :key="file.id" :file="file" #update-description="updateDescription" />
</div>
You're almost there, you can see in the code you've provided that the child component event is being emitted but the value is empty. The problem is you're not updating myDescription, if you change your :value to v-model then it will update, as v-model uses two way binding.
Also, if you want to update the file description, you can just do:
file.description = description;
Vue.component('myComponent', {
props: ["file"],
data() {
return {
myDescription: '',
}
},
mounted() {
this.myDescription = this.file.description;
},
template: `
<div>
<label>{{ file.name }}</label>
<br>
<input type="text" #input="update" v-model="myDescription"></input>
<br><br>
</div>
`,
methods: {
update() {
this.$emit("update-description", this.myDescription, this.file);
},
}
})
var app = new Vue({
el: '#app',
methods: {
updateDescription(description, file) {
console.log(description);
file.description = description;
}
},
data: {
files: [{
id: 1,
name: "Hello",
description: "",
},
{
id: 2,
name: "World",
description: "Foo",
},
{
id: 3,
name: "John",
description: "Bar",
}
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div> {{ files }} </div>
<br>
<my-component v-for="file in files" :key="file.id" :file="file" #update-description="updateDescription" />
</div>

Vue, re-ordering list when selecting rendered checkbox behaves funky

I have got an issue I can't get my head around, I have tried to reproduce the error below, however the below code works just fine!
So below is what is supposed to happen.
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<middleman :items="items" :selected="selected" #update="update"></middleman>
</div>
<script type="text/x-template" id="checkbox">
<label :for="$vnode.key">
{{ value }}
<input type="checkbox"
:value="value"
:id="$vnode.key"
#change="$emit('update', $event)"
:checked="isSelected(value)">
</label>
</script>
<script type="text/x-template" id="middleman">
<div>
<checkbox v-for="(item, index) in filtered"
:selected="selected"
:value="item.value"
#update="$emit('update', $event)"
:key="`checkbox-${index}`"></checkbox>
</div>
</script>
<script>
Vue.component('checkbox', {
props: ['value', 'selected'],
template: '#checkbox',
methods: {
isSelected(value) {
return this.selected.indexOf(value) > -1;
}
}
})
Vue.component('middleman', {
props: ['items', 'selected'],
template: '#middleman',
computed: {
filtered() {
return [...this.items].sort((a, b) => this.selected.includes(b.value) - this.selected.includes(a.value))
}
}
})
new Vue({
el: '#app',
data: {
items: [
{value: 'BMW', count: 1},
{value: 'AUDI', count: 1},
{value: 'VAUXHALL', count: 1},
{value: 'FIAT', count: 1},
{value: 'HONDA', count: 1},
{value: 'LANDROVER', count: 1},
],
selected: ['AUDI', 'HONDA']
},
methods: {
update(event) {
if (event.target.checked) {
this.selected.push(event.target.value);
return;
}
const index = this.selected.indexOf(event.target.value);
this.selected.splice(index, 1);
}
}
});
</script>
<style>
label {
display: block;
}
</style>
The above is pretty much what I have got. There is a root component, a middleman, and then the checkbox...
However mine is doing this:
Here is the code, very similar but using single file components:
// Root.vue
<template>
<div class="app-search">
<div class="app-search__facets">
<facet v-for="(facet, key) in facets"
#update="updateFacet(...$event, key)"
:facet="facet"
:label="key"
:data="items[key]"
:key="key"/>
</div>
</div>
</template>
<script>
import facet from '../Facets/Facet';
export default {
components: {
facet
},
data: function () {
return {
facets: {
manufacturer: [{"control": "checkbox", "type": "value", "size": 20, "selected": []}]
},
items: {
manufacturer: [
{value: 'BMW', count: 1},
{value: 'AUDI', count: 1},
{value: 'VAUXHALL', count: 1},
{value: 'FIAT', count: 1},
{value: 'HONDA', count: 1},
{value: 'LANDROVER', count: 1}
]
}
}
},
methods: {
updateFacet(action, value, facet) {
switch (action) {
case 'add':
this.facets[facet].selected.push(value);
break;
case 'remove':
let index = this.facets[facet].selected.indexOf(value);
this.facets[facet].selected.splice(index, 1);
break;
}
},
}
}
</script>
// Facet.vue
<template>
<div class="app-search__facet">
<p class="app-search__facet--title">{{ label }} ({{ selected }})</p>
<component :is="facet.control"
v-for="(value, index) in filter"
:data="value"
:value="facet.selected"
:key="`facet-${label}-${index}`"
#value="update($event)" />
</div>
</template>
<script>
import '#/prototypes/toPascalCase'
export default {
props: {
data: {
required: true,
type: Array
},
facet: {
required: true,
type: Object
},
label: {
required: true,
type: String
}
}
computed: {
selected() {
return this.facet.selected.length
},
filter() {
const selected = this.facet.selected;
return [...this.data].sort((a, b) => selected.includes(b.value) - selected.includes(a.value));
}
},
methods: {
component(control) {
return () => import(`./Controls/${control.toPascalCase()}`)
},
update(event) {
switch(this.facet.control) {
case 'checkbox':
const value = event.target.value;
if (event.target.checked && this.facet.selected.indexOf(value) === -1) {
this.$emit('update', [ 'add', value ])
break;
}
this.$emit('update', [ 'remove', value ])
break;
default:
console.log('Can\'t handle this facet');
break;
}
},
},
}
</script>
// Checkbox.vue
<template>
<label :for="$vnode.key" class="app-search__checkbox">
{{ data.value }} ({{ data.count }})
<input type="checkbox"
:id="$vnode.key"
:value="data.value"
#change="$emit('value', $event)"
:checked="isSelected">
</label>
</template>
<script>
export default {
props: {
data: {
required: true,
type: Object
},
value: {
required: true
}
},
computed: {
isSelected() {
return this.value.indexOf(this.data.value) > -1;
}
}
}
</script>
Anyone got any idea what is going on here?
The problem is caused by the combination of using v-for index to create a :key and sorting items after each select/deselect action. Do not use index in key - use something unique for each item, for example item.value
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.js"></script>
<div id="app">
<middleman :items="items" :selected="selected" #update="update"></middleman>
</div>
<script type="text/x-template" id="checkbox">
<label :for="$vnode.key">
{{ value }}
<input type="checkbox"
:value="value"
:id="$vnode.key"
#change="$emit('update', $event)"
:checked="isSelected(value)">
</label>
</script>
<script type="text/x-template" id="middleman">
<div>
<checkbox v-for="(item, index) in filtered"
:selected="selected"
:value="item.value"
#update="$emit('update', $event)"
:key="item.value"></checkbox>
</div>
</script>
<script>
Vue.component('checkbox', {
props: ['value', 'selected'],
template: '#checkbox',
methods: {
isSelected(value) {
return this.selected.indexOf(value) > -1;
}
}
})
Vue.component('middleman', {
props: ['items', 'selected'],
template: '#middleman',
computed: {
filtered() {
return [...this.items].sort((a, b) => this.selected.includes(b.value) - this.selected.includes(a.value))
}
}
})
new Vue({
el: '#app',
data: {
items: [
{value: 'BMW', count: 1},
{value: 'AUDI', count: 1},
{value: 'VAUXHALL', count: 1},
{value: 'FIAT', count: 1},
{value: 'HONDA', count: 1},
{value: 'LANDROVER', count: 1},
],
selected: ['AUDI', 'HONDA']
},
methods: {
update(event) {
if (event.target.checked) {
this.selected.push(event.target.value);
return;
}
const index = this.selected.indexOf(event.target.value);
this.selected.splice(index, 1);
}
}
});
</script>
<style>
label {
display: block;
}
</style>

Categories