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.
Related
I have two component vue
My first component / parent component like this :
<template>
...
<page-listing
:lastPage="lastPage"
:perPage="perPage"
:page="page"
:changePage="changePage"
></page-listing>
...
</b-row>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
import PageListing from "#/views/app/reports/PageListing";
export default {
components: {
"page-listing": PageListing
},
methods: {
...mapActions(["getReport"]),
changePage(pageNum) {
this.loadPage(pageNum);
},
loadPage(page){
this.geReport({page});
},
},
computed: {
...mapGetters({
perPage: "reportPerpage",
lastPage: "reportLastPage",
}),
page: {
get() {
return this.$store.getters.reportPage
},
set(newValue) {
this.$store.dispatch('setReportPage', newValue);
}
},
},
watch: {
search() {
this.page = 1;
},
},
mounted() {
this.loadPage();
}
};
</script>
My second component /child component like this :
<template>
...
<b-colxx xxs="12">
<b-pagination-nav
:number-of-pages="lastPage"
:link-gen="linkGen"
:value="page"
#change="(a)=>changePage(a)"
:per-page="perPage"
align="center"
use-router
>
</b-pagination-nav>
</b-colxx>
...
</template>
<script>
export default {
props: [
"items",
"lastPage",
"perPage",
"page",
"changePage",
],
methods: {
linkGen(pageNum) {
return pageNum === 1 ? '?' : `?page=${pageNum}`
},
}
};
</script>
The pagination works. When I load the page, the pagination appears and I click page 2, the list success appears. But page number two is not selected. I have to click 2x so it can be selected
How can I solve this problem?
Please help. Thanks
Note :
I use https://bootstrap-vue.org/docs/components/pagination-nav?page=2
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>
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.
I have a simple setup in my App which consists of 2 components. The HelloWorld component and then a dialog component. The dialog component accepts props passed from the HelloWorld component. The data in HelloWorld is being rendered by looping over an array. What i am trying to achieve is when i click on one of the elements i want the dialog to be pre filled with the name and age of the element i clicked on. I am not sure how i can do that?
Check out this CodeSandbox for the complete Setup.
This is my Vuex Store:-
state: {
userData: [
{
name: "Sam",
age: "24"
},
{
name: "Max",
age: "28"
}
],
dialog: false
},
getters: {
getUserData: state => state.userData,
getDialog: state => state.dialog
},
mutations: {
openDialog(state) {
state.dialog = true;
}
}
This is my HelloWorld Component:-
<template>
<v-container>
<v-layout justify-center>
<v-card>
<v-card-text>
<div v-for="user in getUserData" :key="user.age">
<span>{{user.name}}</span>
<span>{{user.age}}</span>
<v-icon #click="openDialog">create</v-icon>
</div>
</v-card-text>
</v-card>
</v-layout>
<Dialog/>
</v-container>
</template>
<script>
import { mapGetters, mapMutations } from "vuex";
import Dialog from "./Dialog";
export default {
name: "HelloWorld",
components: {
Dialog
},
data() {
return {};
},
computed: {
...mapGetters({
getUserData: "getUserData"
})
},
methods: {
...mapMutations({
openDialog: "openDialog"
})
}
};
</script>
This is my Dialog Component:-
<template>
<v-dialog v-model="getDialog" max-width="300px">
<v-card>
<v-card-text>
<v-text-field v-model="title"></v-text-field>
<div class="mt-5">{{age}}</div>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script>
import { mapGetters } from "vuex";
export default {
props: {
title: {
type: String
},
age: {
type: Number
}
},
data() {
return {};
},
computed: {
...mapGetters({
getDialog: "getDialog"
})
}
};
</script>
Appreciate all the help. Thank you.
<div v-for="(user, index) in getUserData" :key="user.age">
<span>{{user.name}}</span>
<span>{{user.age}}</span>
<v-icon #click="openDialogAndGetCurrentUser(index)">create</v-icon>
</div>
openDialogAndGetCurrentUser(index) {
this.openDialog();
/* define these in your data to be passed to child */
this.currentuserName = this.getUserData[index].name;
this.currentuserAge = this.getUserData[index].age;
}
<Dialog
:title="currentuserName"
:age="currentuserAge"/>
I'm trying to create a "Dropdown List" component. This component should take in a title and a route. I'm having trouble setting up the javascript for this component within the parent. Here is what I'm working with.
PARENT COMPONENT:
<template>
<UserDropdownList v-for="item in items"></UserDropdownList>
</template>
<script>
import SignUp from "../forms/SignUp";
import SignIn from "../forms/SignIn";
import UserDropdownList from "./UserDropdownList";
import { mixin as clickaway } from 'vue-clickaway';
export default {
mixins: [ clickaway ],
components: {
SignUp,
SignIn,
UserDropdownList
},
data: function () {
return {
items: [
{ title: 'Profile', route: "/profile" },
{ title: 'Users', route: "/users" }
]
}
},
computed: {
isLoggedIn () {
return this.$store.getters.isLoggedIn
},
userName () {
return this.$store.getters.currentUser.userName
},
isDropdownOpen () {
return this.$store.getters.dropdownIsOpen
}
},
methods: {
signOut: function(event) {
this.$store.commit("CLOSE_DROPDOWN");
this.$store.commit("LOGOUT");
this.$router.push('/');
},
openDropdown: function() {
if (event.target.id != "button") {
this.$store.commit("OPEN_DROPDOWN");
}
},
closeDropdown: function() {
this.$store.commit("CLOSE_DROPDOWN");
}
}
}
</script>
USER DROPDOWN LIST
<template>
<li v-on:click="closeDropdown"><router-link to={{ item.route }} id="button">{{ item.title }}</router-link></li>
</template>
<script>
export default {
}
</script>
<style>
</style>
ERRORS:
Property or method "item" 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.
Error in render: "TypeError: Cannot read property 'title' of undefined"
Anyone understand what I am doing wrong?
you didn't pass item as a props to UserDropdownList
first, pass item as a prop to UserDropdownList
<template>
<UserDropdownList v-for="item in items" v-bind:item="item"></UserDropdownList>
</template>
then, setup UserDropdownList to receive item prop
export default {
props: ['item']
}