I have 3 components. They are dashboard.vue, datatable.vue and modalbody.vue. After login my application reach in dashboard.vue. Where I call datatable.vue to display a list with some props. I have a button in datatable.vue. If I click on that button a modal will open to add new record to add that list (datatable.vue) using modalbody.vue.
Now I need to reload that list (datatable.vue) after inserting new record through modal (modalbody.vue).
How can I do that ?
I am going to show you a simple example how to do it.Hope you will get the logic
Component which has the table:
<template>
<div>
<cmp :modal.sync="modal" #personAdded="addItemInTable"></cmp>
<button #click="addNewPerson">add person</button>
<table>
<tr v-for="row in tableRows">
<td>{{ row.name }}</td>
<td>{{ row.lastName }}</td>
</tr>
</table>
</div>
</template>
<script>
import childComponent from 'ChildComponent.vue'
export default {
components: {
"cmp": childComponent
},
data() {
return {
modal: false,
tableRows: [
{ name: "person1", lastName: "lperson1" },
{ name: "person2", lastName: "lperson2" },
{ name: "person3", lastName: "lperson3" },
{ name: "person4", lastName: "lperson4" },
]
}
},
methods: {
addNewPerson() {
this.modal = true //open the modal
},
addItemInTable(data) {
//saving the data passed from modal
this.tableRows.unshift(data)
this.modal = false
}
}
}
</script>
Modal Component:
<template>
<div id="modal" v-if="modal">
<input type="text" v-model="name">
<input type="text" v-model="">
<button #click="save">Save</button>
<button #click="cancel">Cancel</button>
</div>
</template>
<script>
export default {
props: {
modal: {
default: false
}
},
data() {
return {
name: '',
lastName: ''
}
},
methods: {
save() {
const savedData = {
name: this.name,
lastName: this.lastName
}
this.$emit('personAdded', savedData)
},
cancel() {
this.$emit('update:modal', false)
}
}
}
</script>
<style>
.modal {
position: absolute;
height: 500px;
width: 500px;
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
}
</style>
I dont have run the above code but the logic is correct.Please read below to understand:
For .sync modifier read this
For emitting events ($emit) read this
For reusing components read this
Related
I'm trying to make a dropdown sort and I get this error:
VueCompilerError: v-model cannot be used on a prop, because local prop bindings are not writable. Use a v-bind binding combined with a v-on listener that emits update:x event instead.
Here are 2 components App and MySelect:
<template>
<!-- App Component -->
<div class="app">
<h1>Страница с постами</h1>
<div class="app__btns">
<my-button #click="showDialog">Cоздать пост</my-button>
<my-select v-model="selectedSort" :options="sortOptions" />
</div>
<my-dialog v-model:show="dialogVisible">
<post-form #create="createPost" />
</my-dialog>
<post-list :posts="posts" #remove="removePost" v-if="!isPostsLoading" />
<div v-else>Идет загрузка...</div>
</div>
</template>
<script>
import axios from 'axios'
import PostForm from './components/PostForm.vue'
import PostList from './components/PostList.vue'
export default {
components: { PostList, PostForm },
data() {
return {
posts: [],
dialogVisible: false,
isPostsLoading: false,
selectedSort: '',
sortOptions: [
{ value: 'title', name: 'По названию' },
{ value: 'body', name: 'По содержанию' },
],
}
},
methods: {
createPost(post) {
this.posts.push(post)
this.dialogVisible = false
},
removePost(post) {
this.posts = this.posts.filter((p) => p.id !== post.id)
},
showDialog() {
this.dialogVisible = true
},
async fetchPosts() {
try {
this.isPostsLoading = true
const res = await axios.get(
'https://jsonplaceholder.typicode.com/posts?_limit=10'
)
this.posts = res.data
} catch (error) {
alert('ошибка')
} finally {
this.isPostsLoading = false
}
},
},
mounted() {
this.fetchPosts()
},
}
</script>
<!-- флаг scoped - значит, что стили будут применяться только к этому комопненту -->
<style>
.app {
padding: 20px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.app__btns {
margin: 15px 0;
display: flex;
justify-content: space-between;
}
</style>
<template>
<!-- MySelect component -->
<select v-model="modelValue" #change="changeOption">
<option disabled value="">Выберите из списка</option>
<option v-for="option in options" :key="option.value" :value="option.value">
{{ option.name }}
</option>
</select>
</template>
<script>
export default {
name: 'my-select',
props: {
modelValue: {
type: String,
},
options: {
type: Array,
default: () => [],
},
},
methods: {
changeOption(event) {
this.$emit('update:modelValue', event.target.value)
},
},
}
</script>
<style lang="scss" scoped></style>
I need to update modelValue, so I tried to add
:value="modelValue"
instead of
v-model="modelValue"
and it works, but I'm not sure if this is the correct solution.
If anyone else is encountering this issue when updating their vue version. Please note that this error started to appear on version 3.2.45.
For the implementation pattern, as noted on the documentation, props should be considered readonly within the component. Vue did not enforce it enough prior to version 3.2.45.
Documentation with links to good implementation patterns : https://vuejs.org/guide/components/props.html#one-way-data-flow
I am new in vue and nuxt and here is my code I need to update
<template>
<div class="dashContent">
<div class="dashContent_item dashContent_item--active">
<p class="dashContent_text">123</p>
</div>
<div class="dashContent_item">
<p class="dashContent_text">456</p>
</div>
<div class="dashContent_item">
<p class="dashContent_text">789</p>
</div>
</div>
</template>
<style lang="scss">
.dashContent {
&_item {
display: flex;
align-items: center;
}
&_text {
color: #8e8f93;
font-size: 14px;
}
}
.dashContent_item--active {
.dashContent_text{
color:#fff;
font-size: 14px;
}
}
</style>
I tried something like this:
<div #click="onClick">
methods: {
onClick () {
document.body.classList.toggle('dashContent_item--active');
},
},
but it changed all elements and I need style change only on element I clicked and remove when click on another
also this code add active class to body not to element I clicked
This is how to get a togglable list of fruits, with a specific class tied to each one of them.
<template>
<section>
<div v-for="(fruit, index) in fruits" :key="fruit.id" #click="toggleEat(index)">
<span :class="{ 'was-eaten': fruit.eaten }">{{ fruit.name }}</span>
</div>
</section>
</template>
<script>
export default {
name: 'ToggleFruits',
data() {
return {
fruits: [
{ id: 1, name: 'banana', eaten: false },
{ id: 2, name: 'apple', eaten: true },
{ id: 3, name: 'watermelon', eaten: false },
],
}
},
methods: {
toggleEat(clickedFruitIndex) {
this.fruits = this.fruits.map((fruit) => ({
...fruit,
eaten: false,
}))
return this.$set(this.fruits, clickedFruitIndex, {
...this.fruits[clickedFruitIndex],
eaten: true,
})
},
},
}
</script>
<style scoped>
.was-eaten {
color: hsl(24, 81.7%, 49.2%);
}
</style>
In Vue2, we need to use this.$set otherwise, the changed element in a specific position of the array will not be detected. More info available in the official documentation.
In this codeSandBox demo, the child cmp contains the image file input,
The file is uploaded to and read using reader.readAsDataURL(file) as the background image of the input's parent wrapper div.
The problem is that the uploaded file gets repeated in all siblings.
I want to uploaded file to affect only the component where the child was upload.
Parent.vue
<template>
<div id="app">
<div v-for="n in 5">
<div class="wrapper" :style="bgImg">
<child #imageSelected="updateBgImg($event)"/>
</div>
</div>
</div>
</template>
<script>
import child from "./components/child";
export default {
name: "App",
data() {
return {
bgImgURL: "Image URL",
bgImg: {}
};
},
methods: {
updateBg(url) {
this.bgImgURL = url;
this.bgImg = {
"background-image": "url(" + this.bgImgURL + ")"
}
}
},
components: {
child
}
}
</script>
child.vue
<template>
<div>
<input type="file" #change="getImage">
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
},
methods: {
getImage(e) {
var file = e.target.files[0];
this.createImage(file);
},
createImage(file) {
var reader = new FileReader();
reader.readAsDataURL(file);
var vm = this;
reader.onload = function() {
vm.$emit("imageSelected", this.result);
};
}
}
};
</script>
Thanks
Update the image in your child. Example on codesandbox
Your parent look like this
<template>
<div id="app">
<div v-for="n in 5" :key='n'>
<child/>
<!-- <child #imageSelected="updateBg($event)"/> -->
</div>
</div>
</template>
<script>
import child from "./components/child";
export default {
name: "App",
data() {
return {
msg: "Parent Message",
// testUrl: "Image URL",
// bgImg: {}
};
},
methods: {
// updateBg(url,index) {
// // this.testUrl = url;
// this.bgImg[index] = {
// "background-image": "url(" + url + ")"
// };
// // console.log(this.bgImg);
// },
},
components: {
child
}
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.wrapper {
width: 300px;
height: 100px;
margin-bottom: 30px;
border: 1px solid red;
background-size: cover;
}
</style>
and your child like this
<template>
<div>
<div class="wrapper" :style="img">
<input type="file" #change="getImage">
</div>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
},
data(){
return {
img:''
}
},
methods: {
getImage(e) {
var file = e.target.files[0];
this.createImage(file);
},
createImage(file) {
var reader = new FileReader();
reader.readAsDataURL(file);
var vm = this;
reader.onload = function() {
// console.log(this.result)
vm.img = {
"background-image": "url(" + this.result + ")"
}
// don't need
// vm.$emit("imageSelected", this.result);
};
}
}
};
</script>
Updated
Update image in parent from child. Example on codepen
Your parent
<template>
<div id="app">
<div v-for="n in imgCount" :key="n">
<div class="wrapper" :style="imgs[n]">
<sibling/>
<child :image-id="n" #imageSelected="updateBg($event)"/>
</div>
</div>
</div>
</template>
<script>
import child from "./components/child";
import sibling from "./components/simpleCmp";
export default {
name: "App",
data() {
return {
imgCount: 5,
msg: "Parent Message",
testUrl: "Image URL",
bgImg: {},
imgs: []
};
},
methods: {
updateBg(item) {
// console.log(item.index);
this.$set(this.imgs, item.index, item.bg)
}
},
components: {
child,
sibling
}
};
</script>
your child
<template>
<div>
<input type="file" #change="getImage">
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String,
imageId: Number
},
methods: {
getImage(e) {
var file = e.target.files[0];
if (!file) return;
this.createImage(file);
},
createImage(file) {
var reader = new FileReader();
reader.readAsDataURL(file);
var vm = this;
reader.onload = function() {
// console.log(this.result);
vm.$emit("imageSelected", {
index: vm.imageId,
bg: {
"background-image": "url(" + this.result + ")"
}
});
};
}
}
};
</script>
i`m having a problem with my vue, the problem is im trying to print 2 words, that is 'A.2' and 'B.3', but when im printing it, it just show 'B.3' and 'B.3'. here is my code
this is a simple quiz project, so everytime a user choose option a with true status it should be adding 1 point to the score, i haven`t made that yet.
<template>
<div class="hello">
<h1 v-if="show">hai</h1>
<h1 v-else>hehe</h1>
<p>{{ nama}}</p>
<input type="text" v-model="nama">
<button type="button" v-on:click="hideTitle">Click Me</button>
<h3> 1.Yang Dipakai Di sepatu adalah </h3>
<p>{{ nama}}</p>
<h3 v-for="j in jawaban">
<input type="radio">
{{j}}
</h3>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
data : function() {
return{
nama: 'Luthfi',
show: true
},
{
jawaban: 'A.2',
correct: true
},
{
jawaban: 'B.3',
correct: false
},
{
jawaban: 'C.4',
correct: false
}
},
methods: {
hideTitle() {
this.show = !this.show
}
},
mounted: function () {
this.nama = 'Fitra'
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
i expect there is 4 output from option A to D, but it kept showing me same option
In your code, data() returns only one object that contains
{
nama: 'Luthfi',
show: true
}
You must change this like:
data : function() {
return{
nama: 'Luthfi',
show: true,
jawaban: 'A.22',
correct: true,
jawabann: 'B.3'
}
}
I would like to know how can I implement a CustomHeader with a custom functionality (other than sort, for example).
Basically what I want to know is how to communicate my HeaderComponent with my component that holds the grid. E.g.:
<template>
<div style="height: 100%" class="table-chart" ref="root">
<div class="title" ref="title">{{ objectData.title }}</div>
<div class="ag-dashboard" style="height: 100%; width: 90%; margin: 0 auto">
<ag-grid-vue
:style="{ height: tableHeight }"
:gridOptions="gridOptions"
:columnDefs="columnDefs"
:rowData="rowData"
/>
</div>
</div>
</template>
<script>
export default {
components: {
'HeaderComponent': {
template: '<span>{{this.params.displayName}} <span #click="custom">CLICK</span></span>',
methods: {
custom() {
// emmit an event here or find a way to comunnicate with the function "customEvent" below
}
}
}
},
methods: {
customEvent() {
console.log('Event from header');
}
},
beforeMount() {
// ... setup Ag-Grid and the HeaderComponent in the columns' headerComponentFramework
}
}
</script>
Apreciate any help,
The cleaner way I found of doing this was through an EventBus:
import Vue from 'vue';
const EventBus = new Vue();
export default {
//...
'HeaderComponent': {
// ...
methods: {
custom() {
EventBus.$emit('customEvent');
}
}
// ...
mounted() {
EventBus.$on('customEvent', () => {
// Do whatever...
});
}
}