Vue - how to pass array in this case? - javascript

This is my sample code. I wanted to create a small form builder.
I will have many select fields. How can I pass an array into a loop? My code doesn't work, but I hope you know what effect I want to get.
<template>
<div>
<div v-for="(input, index) in formBuilder" :key="index">
<h1>{{ input.name }}</h1>
<div>
Options:<br />
{{ formBuilder.options }}
</div>
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
data() {
return {
formBuilder: [
{
name: "Name",
options: this.versions,
},
{
name: "Host",
options: this.countryList,
},
],
};
},
computed: mapState(["versions", "countryList"]),
};
</script>
EDIT.
Below, I have added a version that works. But can it be done in a better way? And is this the correct way?
It works:
<template>
<div>
<div v-for="(input, index) in formBuilder" :key="index">
<h1>{{ input.name }}</h1>
<div>
Options:<br />
{{ input.options }}
</div>
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
data() {
return {
formBuilder: [
{
name: "Name",
options: [],
},
{
name: "Host",
options: [],
},
],
};
},
created() {
this.formBuilder[0].options = this.versions;
this.formBuilder[1].options = this.countryList;
},
computed: mapState(["versions", "countryList"]),
};
</script>

As https://stackoverflow.com/users/381282/michal-lev%c3%bd mention. computed property is your "correct solution".
computed: {
...mapState(['versions', 'countryList']),
formBuilder() {
return [
{ name: "Name", options: this.versions },
{ name: "Host", options: this.countryList },
]
}
}
Explaination:
If you put the code in created it will only prepare formBuilder once when the component created.
If you use computed the formBuilder will be recomputed everytime this.versions or this.coutryList get updated.

Related

Vue 3: Unable to update parent data from child component checkbox

I'm trying to move a checkbox to a child component, but I can't get it to update the data stored in the parent.
Parent:
<template>
<CheckboxComponent
:value="profile.doYouAgree"
label="Do you agree?"
#input="profile.doYouAgree = $event.target.value"
/>
<div>{{ profile.doYouAgree }}</div>
</template>
<script>
import CheckboxComponent from "./components/CheckboxComponent.vue";
import { reactive } from "vue";
const profile = reactive({
name: "A Name",
email: "someone#me.com",
doYouAgree: false,
});
export default {
name: "App",
components: {
CheckboxComponent,
},
setup() {
return {
profile,
};
},
};
</script>
Child:
<template>
<div class="hello">
<label for="checkbox">{{ label }}</label>
<input
type="checkbox"
:value="modelValue"
right
#input="$emit('update:modelValue', $event.target.value)"
/>
</div>
</template>
<script>
export default {
name: "CheckboxComponent",
props: {
label: {
type: String,
default: "",
},
modelValue: {
type: Boolean,
},
},
};
</script>
In the following sandbox, I am expecting the false to turn to true when the box is checked:
https://codesandbox.io/s/gifted-worker-vm9lyt?file=/src/App.vue
There's a couple of problems here:
$event.target.value is a string rather than a boolean. Change this to $event.target.checked
Your parent is listening to input but your child is emitting update:modelValue. Save yourself a lot of hassle and use v-model in the parent:
<CheckboxComponent
v-model="profile.doYouAgree"
label="Do you agree?"
/>

Vue - How can I list the array in the first object of the array with Vuex using mapState

I want to list the array named names of the first object in players using mapState with Vuex. In the current code, the objects in the players are listed according to their titles, but I want to filter them only according to the names in the first object on that page. On second page I want to list them according to the names that I will add to the second object. I hope I was able to explain my problem. How can I do this in the filter? Or what if there is a better way to do this?
Players.vue
<template>
<div class="Players">
<CostumText class="Players-title" tag="h1">Kulüpler</CostumText>
<div class="Players-search">
<input type="text" v-model="search" placeholder="Kulüp ara.." />
<label>Futbolcu ara:</label>
</div>
<div class="Players-inner">
<router-link
:to="players.pathName"
class="Players-inner-wrapper"
v-for="players in filteredList"
v-bind:key="players.id"
>
<div class="Players-inner-cards">
<Clubs class="Players-inner-cards-svg" v-bind:name="players.id" />
<CostumText tag="strong" lang="tr" class="Players-inner-cards-text">
{{ players.title }}
</CostumText>
</div>
</router-link>
</div>
<router-view />
</div>
</template>
<script>
import { mapState } from 'vuex'
import CostumText from '#/components/CostumText'
import Clubs from '#/components/Clubs.vue'
export default {
name: 'Players',
components: {
CostumText,
Clubs
},
data() {
return {
search: ''
}
},
computed: {
...mapState(['players']),
filteredList() {
return this.players.filter((player) =>
player.title.toLowerCase().includes(this.search.toLowerCase())
)
}
},
modules: {}
}
</script>
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
players: [
{
id: 1,
names: ['kerem', 'sirin', 'ali', 'ayse', 'ahmet'],
title: 'Ali',
pathName: 'ali'
},
{
id: 2,
title: 'Ayse',
pathName: 'ayse'
},
{
id: 3,
title: 'Ahmet',
pathName: 'ahmet'
}
]
},
getters: {},
mutations: {},
actions: {},
modules: {}
})
You can modify the filteredList to be
computed: {
...mapState(['players']),
filteredList() {
const filteredPlayers = this.players.filter(player => {
let flag = false;
if(player.names) {
player.names.forEach(name => {
if(name.toLowerCase().includes(this.search.toLowerCase()) flag = true;
});
}
return flag;
});
return filteredPlayers;
},
Here is how you display names
<div class="Players-inner-cards">
<Clubs class="Players-inner-cards-svg" v-bind:name="players.id" />
<CostumText tag="strong" lang="tr" class="Players-inner-cards-text">
{{ players.names.valueOf() }}
</CostumText>
</div>

Vue : Cannot pass the value from parent to child component

Hi I was learning vue and I am trying to make a todo app, I cannot show the todo list items in my todo app, I can't figure out what is the problem is. This is my App.vue:
<template>
<TodoList id="app" v-bind:todos="todos" />
</template>
<script>
import TodoList from './components/list.vue';
export default {
name: 'App',
data() {
return {
todos: [
{
id: 1,
title: 'Go workout',
completed: false
},
{
id: 2,
title: 'Do laundry',
completed: false
},
{
id: 3,
title: 'Cook food',
completed: false
}
]
};
},
components: {
TodoList
}
};
</script>
This is my list.vue:
<template>
<div>
<h2>Todo List</h2>
<ul>
<li v-bind:key="todo.id" v-for="todo in todos">
<Todo v-bind:todo="todo" />
</li>
</ul>
</div>
</template>
<script>
import Todo from './todo.vue';
export default {
name: 'TodoList',
components: {
Todo
},
created() {
console.log(this);
}
};
</script>
This component contains the Todo item, this is todo.js:
<template>
<div>{{ todo.title }}</div>
</template>
<script>
export default {
name: 'Todo',
props: ['todo'],
created() {
console.log('todo', this);
}
};
</script>
But todo is probably not being created, as the created lifecycle is not being called here. Help me figure what is the problem is.
You forgot the props attribute in your TodoList component:
export default {
name: 'TodoList',
props: ['todos'],
components: {
Todo
},
created() {
console.log(this);
}
};

Get static JSON single product data in Vue JS by ID

I am creating a Vue JS app which will display a list of products that when clicked on will link through to a dynamic product by its ID (passed via Vue Router params). This bit works fine but what I need to do is once on that dynamic route, display all the data for that product by its ID. I'm still pretty new to Vue and confused by what approach to take. I don't think I need axios as this won't be an online app so no access to APIs. I also don't know if I need to go as far as using vuex. At present I'm trying to use a simple method to grab the data from my JSON file by the ID passed through the routes parameters. Here's what I have so far...
router.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import Products from './views/Products.vue'
import Product from './views/ProductSingle.vue'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/products',
name: 'products',
component: Products
},
{
path: '/product/:id',
name: 'product',
component: Product,
props: true
}
]
})
Slider.vue - this component is called in Views/Products.vue and is what links through to each single product
<template>
<carousel
:items="3"
:loop="true"
:center="true"
:margin="0"
:dots="false"
:nav="true"
>
<div v-for="product in products" :key="product.productId">
<transition name="slide">
<router-link
:to="{
name: 'product',
params: { id: product.productId }
}"
>
<img src="http://lorempixel.com/100/100/city" :alt="product.name" />
<!-- <img
:src="require('../assets/images/sample-product/' + data.image)"
/>-->
</router-link>
</transition>
</div>
</carousel>
</template>
<script>
import json from '#/json/data.json'
import carousel from 'vue-owl-carousel'
export default {
data() {
return {
products: json
}
},
components: {
carousel
}
}
</script>
ProductSingle.vue
<template>
<div>
<p v-for="product in getData($route.params.id)" :key="product.productId">
{{ product }}
</p>
</div>
</template>
<script>
import json from '#/json/data.json'
export default {
data() {
return {
products: json
}
},
methods: {
getData(id) {
let data = this.products
data.filter(item => {
return item.productId == id
})
}
}
}
</script>
What I as expecting here is that my getData method would return the data for the product at the ID denoted through $route.params.id yet nothing is returned. A console.log(getData($route.params.id)) shows the following:
[{…}]
Which when expanded out shows 0: {__ob__: Observer} and if you expand that observer out there is indeed that data for
image: (...)
name: (...)
productId: (...)
My data.json file looks like the below:
[
{
"productId": 1,
"name": "Test 1",
"image": "sample.jpg"
},
{
"productId": 2,
"name": "Test 2",
"image": "sample.jpg"
},
{
"productId": 3,
"name": "Test 3",
"image": "sample.jpg"
},
{
"productId": 4,
"name": "Test 4",
"image": "sample.jpg"
}
]
Could really do with some guidance on how to approach this problem.
Many thanks in advance
You're missing a return here:
data.filter(item => {
return item.productId == id
})
Try this:
return data.filter(item => {
return item.productId == id
})
If you're not using Api or Vuex then you need to have all the data on a place where they can be fetched from multiple components.
data.json is fine for this.
On your child component, do the following. There is no need for data iteration.
<template>
<div>
{{ singleProduct }}
{{ singleProduct.productId }}
</div>
</template>
<script>
import json from '#/json/data.json'
export default {
name: 'ProductSingle',
data() {
return {
products: json,
singleProduct: undefined
}
},
computed: {
productId () {
return this.$route.params.id // From route
}
},
created () {
this.assignSingleProduct();
},
methods: {
assignSingleProduct() {
let data = this.products
data.filter(item => {
return item.productId == this.productId
})
// Assign to the data
this.singleProduct = data
}
}
}
</script>

VueJS mutating od duplicate v-model (:value, #input)

I have a problem with using the v-model in my own component. Namely, I dont want to use State or Bus.
At present, the component returns the single value correctly in App.js, it duplicates itself.
I can not deal with it, please help and explain me the problem.
Thanks a lot!
App.js:
<template>
<b-container>
<SectionSelector :AddSection="AddSection"/>
<component
v-for="(section, index) in sections"
:key="index"
:is="section.type"
:sectionIndex="index"
:sectionData="section[index]"
#sectionDataEmit="sectionDataEmit"/>
</b-container>
</template>
<script>
import SectionSelector from './components/SectionSelector.vue';
import FullText from './components/sections/FullText.vue';
import FullImage from './components/sections/FullImage.vue';
import ImageRightTextLeft from './components/sections/ImageRightTextLeft.vue';
import ImageLeftTextRight from './components/sections/ImageLeftTextRight.vue';
export default {
data() {
return {
sections: []
}
},
methods: {
AddSection(sectionData) {
this.sections.push(sectionData);
},
updateSection(sectionIndex, sectionData) {
this.sections[sectionIndex] = sectionData;
},
sectionDataEmit(emitData) {
// eslint-disable-next-line
console.log(emitData.position, emitData.content);
this.sections[emitData.position].fields.text = emitData.content;
}
},
components: {
SectionSelector,
FullText,
FullImage,
ImageRightTextLeft,
ImageLeftTextRight
}
}
</script>
SectionSelector.vue:
<template>
<b-row>
<b-dropdown id="dropdown-1" text="Select" class="m-md-2">
<b-dropdown-item v-for="(section, index) in sections"
:key="index"
#click="PushSection(index)"> {{ section.type }} </b-dropdown-item>
</b-dropdown>
</b-row>
</template>
<script>
export default {
props: ['AddSection'],
data() {
return {
sections: [
{
type: 'FullText',
fields: {
text: ''
}
},
{
type: 'FullImage',
fields: {
url:''
}
},
{
type: 'ImageRightTextLeft',
fields: {
img: '',
text: ''
}
},
{
type: 'ImageLeftTextRight',
fields: {
img: '',
text: ''
}
}
]
}
},
methods: {
PushSection(index) {
this.AddSection(this.sections[index])
}
}
}
</script>
FullText.vue:
<template>
<b-row>
<h3>Full text {{ sectionIndex+1 }}</h3>
<b-textarea
:value="sectionData"
#input="sectionDataEmit"></b-textarea>
</b-row>
</template>
<script>
export default {
props: ['sectionIndex', 'sectionData'],
methods: {
sectionDataEmit(value) {
let emitData = {
position: this.sectionIndex,
content: value
}
this.$emit('sectionDataEmit', emitData)
}
}
}
</script>
At present, the code causes duplication sections.fields.text (visible in the chrome extension Vue)
There is a place in your code that uses object[index]=. Do not do that with Vue data objects. Instead use Vue.set(object, index, value).
updateSection(sectionIndex, sectionData) {
Vue.set(sections,sectionIndex, sectionData);
},
This is based on the rule that Vue cannot react to new properties added to the object after data is initialized.

Categories