VUE.JS 3 Changing boolean value of one sibling component from another - vue.js

I have two components - component A and component B that are siblings.
I need to change the boolean value inside of Component-A from the Watcher in Component-B.
Component A code:
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
editIsClicked: false,
}
}
}
</script>
Component B code:
<template>
<v-pagination
v-model="currentPage"
:length="lastPage"
:total-visible="8"
></v-pagination>
</template>
<script>
export default {
props: ["store", "collection"],
watch: {
currentPage(newVal) {
this.paginatePage(newVal);
// NEED TO TOGGLE VALUE HERE - when i switch between pages
},
},
},
};
</script>

The Vue Documentation proposes communicating between Vue Components using props and events in the following way
*--------- Vue Component -------*
some data => | -> props -> logic -> event -> | => other components
*-------------------------------*
It's also important to understand how v-model works with components in Vue v3 (Component v-model).
const { createApp } = Vue;
const myComponent = {
props: ['modelValue'],
emits: ['update:modelValue'],
data() {
return {
childValue: this.modelValue
}
},
watch: {
childValue(newVal) {
this.$emit('update:modelValue', newVal)
}
},
template: '<label>Child Value:</label> {{childValue}} <input type="checkbox" v-model="childValue" />'
}
const App = {
components: {
myComponent
},
data() {
return {
parentValue: false
}
}
}
const app = createApp(App)
app.mount('#app')
<div id="app">
Parent Value: {{parentValue}}<br />
<my-component v-model="parentValue"/>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>

I have made a new playground. Hope it helps you now to understand the logic.
You can store data in the main Vue App instance or use a Pinia store for it.
But I would suggest you to start without Pinia to make your app simpler. Using Pinia will make your App much more complicated and your knowledge of Vue seems to be not solid enough for that.
const { createApp } = Vue;
const myComponentA = {
props: ['editIsClicked', 'currentPage'],
template: '#my-component-a'
}
const myComponentB = {
emits: ['editIsClicked'],
data() {
return {
currentPage: 1,
}
},
watch: {
currentPage(newVal) {
this.$emit('editIsClicked', newVal)
}
},
template: '#my-component-b'
}
const App = {
components: {
myComponentA, myComponentB
},
data() {
return {
editIsClicked: false,
currentPage: 1
}
},
methods: {
setEditIsClicked(val) {
this.editIsClicked = true;
this.currentPage = val;
}
}
}
const app = createApp(App)
app.mount('#app')
#app { line-height: 2; }
.comp-a { background-color: #f8f9e0; }
.comp-b { background-color: #d9eba7; }
<div id="app">
<my-component-a :edit-is-clicked="editIsClicked" :current-page="currentPage"></my-component-a>
<my-component-b #edit-is-clicked="setEditIsClicked"></my-component-b>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<script type="text/x-template" id="my-component-a">
<div class="comp-a">
My Component A: <br />editIsClicked: <b>{{editIsClicked}}</b><br/>
currentPage: <b>{{currentPage}}</b><br/>
</div>
</script>
<script type="text/x-template" id="my-component-b">
<div class="comp-b">
My Component B: <br />
<label>CurrentPage:</label> <input type="number" v-model="currentPage" />
</div>
</script>

Related

How can I use the method from other component

I want use the method 'pause()' in the component 'vue-count-to',but the webstorm tips Unresolved function or method pause() .How can I use the method in 'vue-count-to'?Thank you
<template>
<div>
<countTo ref="countTo1" :startVal='startVal' :endVal='endVal' :duration='3000'>
</countTo>
<input type="text" v-model="endVal">
<Button v-on:click="handleClick" >reset</Button>
</div>
</template>
<script>
import CountTox from 'vue-count-to';
export default {
components: {
countTo: CountTox},
data () {
return {
startVal: 0,
endVal: 2017,
autoplay: false
}
},
methods: {
handleClick() {
this.$refs.countTo1.pause();
}
}
}
</script>
you can use $root.$emit() and $root.$on()
const componentA = {
template: `
<button #click="callMethodInComponentB">
call method in component-b
</button>
`,
methods: {
callMethodInComponentB() {
this.$root.$emit('call-to-component-b', 2);
}
}
}
const componentB = {
template: `
<h1>Value: {{ value.toString() }}</h1>
`,
data(){
return {
value: 0
}
},
methods: {
plus(add) {
this.value += add
}
},
mounted() {
this.$root.$on('call-to-component-b', add => {
this.plus(add)
});
}
};
new Vue({
el: '#app',
components: {
componentA,
componentB
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<main id="app">
<component-a></component-a>
<component-b></component-b>
</main>
define the method in App.vue and access it from probs and emit

Vue: Variable not safed, when other component is shown

I have two components. One of it gives the value height to the other one, when "submit" is clicked. When "submit" is clicked the first component should be hidden and the second one visible.
It works so far, but it seems like height is not safed in the second component.
Thanks a lot!!
without the v-if it works perfect!
//ComponentOne
<template>
<body>
<div id="aside">
<footer>
<b-button v-on:click="submit">Submit</b-button>
</footer>
</div>
</body>
</template>
<script>
import { EventBus } from '#/main.js'
export default {
data() {
return {
submitp1: false,
height: 5,
width: 6,
}
},
methods: {
submit: function () {
this.submitp1 = !(this.submitp1)
EventBus.$emit('submitp1emit', this.submitp1)
EventBus.$emit('1to2', this.height)
}
},
}
</script>
//ComponentTwo
<template>
<div >
number <br />
height: {{height}}
</div>
</template>
<script>
import { EventBus } from '#/main.js'
export default {
data: function () {
return {
height: '',
}
},
mounted() {
const self = this
EventBus.$on('1to2', function{ height) {
self.height = height
})
}
}
</script>
//main.js
<template>
<div id="app">
<ComponentOne v-if="submitp1 == false" />
<ComponentTwo v-if="submitp1 == true" />
</div>
</template>
<script>
import { EventBus } from '#/main.js'
import ComponentOne from '#/components/p1Comp/ComponentOne.vue'
import ComponentTwo from '#/components/p1Comp/ComponentTwo.vue'
export default {
components: {
ComponentOne,
ComponentTwo
}
data: function () {
return {
submitp1: false
}
},
mounted() {
const self = this
EventBus.$on('submitp1emit', function (submitp1emit) {
self.submitp1 = submitp1emit
})
}
}
</script>
From the Vue documentation:
v-if is “real” conditional rendering because it ensures that event
listeners and child components inside the conditional block are
properly destroyed and re-created during toggles.
https://v2.vuejs.org/v2/guide/conditional.html#v-if-vs-v-show
The toggled component is simply not there. As already mentioned, you can use "v-show" instead.

How to update data from vue-tables-2 after action from Template?

I'm using a custom component as a column on vue-tables-2, to do that I'm using a vue-component as described here: vue-components
I've created a button that opens a modal to the user confirm some information, and after that I make a request to the backend and the record is changed on the database.
Now I want to refresh the data on the table, but I don't know how to do that. The documentation said about using the $ref, but this is not an option because my component is not the parent.
How can I do that?
Links to the code:
Component using 'vue-tables-2'
<template>
<div>
<div id="payment">
<input type="checkbox" v-model="onlyPending" #change="filterPay()">Apenas pendentes</input>
<v-server-table url="/api/payments" :columns="columns" :options="options" ></v-server-table>
</div>
</div>
</template>
<script>
import pay from './ModalConfirmPay.vue'
import {Event} from 'vue-tables-2';
export default {
name: "AeraListPayment",
props: ['groupId'],
data: function(){
let groupId = this.groupId;
return {
columns: ['name','value','course','due_date','paid','installment','pay'],
options: {
responseAdapter : function(data) {
data.data = data.data.map(payment => {
payment.paid = payment.paid ? "pago" : "pendente";
return payment;
})
return data;
},
headings: {
installment: 'Parcela',
paid: 'Status',
value: 'Valor',
due_date: 'Vencimento',
pay: 'Ação',
course: 'Curso',
name: 'Nome'
},
templates : {
pay
},
customFilters: ['onlyPending','groupId'],
initFilters:{groupId:groupId,onlyPending:true}
},
onlyPending: true
}
},
methods: {
filterPay(){
Event.$emit('vue-tables.filter::onlyPending', this.onlyPending);
}
}
}
</script>
Component that is being used as a custom column:
<template>
<div>
<button #click.prevent="show">Pagar</button>
<modal :name="modalName">
<p>Confirma o pagamento de {{data.value}} ?</p>
<p>Parcela: {{data.installment}}</p>
<p>Vecimento: {{data.due_date}}</p>
<button #click.prevent="pay">Confirmar</button>
<button #click.prevent="hide">Cancelar</button>
</modal>
</div>
</template>
<script>
import PaymentService from '../../services/PaymentService'
let service = new PaymentService();
export default {
name:"ModalConfirmPay",
props: ["data"],
computed: {
modalName: function () {
// `this` aponta para a instância Vue da variável `vm`
return `confirm-pay-${this.data.clientGroup_id}-${this.data.installment}`
}
},
methods: {
show () {
this.$modal.show(this.modalName);
},
pay ( ) {
service.pay(this.data)
.then(this.hide());
},
hide () {
this.$modal.hide(this.modalName);
}
}
}
</script>
First, defined an EventBus if you don't have
EventBus.vue
import Vue from 'vue'
export default new Vue()
In ListPayment.vue, import EventBus and listen for refresh-table event. Note that I add ref="table" to vue-tables-2 element
<template>
<v-server-table ref="table" ... />
</template>
<script>
import EventBus from './EventBus.vue'
export default {
mounted() {
EventBus.$on('refresh-table', this.refreshTable)
},
beforeDestroy() {
EventBus.$off('refresh-table', this.refreshTable)
},
methods: {
refreshTable() {
this.$refs.table.refresh();
}
}
}
</script>
Finally, emit event in modal
pay() {
service.pay(this.data)
.then(() => {
EventBus.$emit('refresh-table')
})
.then(this.hide());
}

Emitting data via EventBus

I would like to emit some data from one component to the other (children components).
In my main.js I created: export const Bus = new Vue({}).
Then in my first child component ,I've got an input with v-model and I would like to pass that v-model.
<template>
<div>
<input type="text" v-model="message" />
<button type="button" #click="submit">Submit</button>
</div>
</template>
<script>
import { Bus } from './../main.js';
export default {
data () {
return {
message: ''
}
},
methods: {
submit() {
if(this.message !== ''){
this.$router.push('location');
Bus.$emit('name', this.message);
}
}
}
}
</script>
My second component:
import { Bus } from './../main.js';
export default {
data() {
return {
recievedMessage: ''
}
},
created() {
Bus.$on('name', (message) => {
this.recievedMessage = message;
})
}
}
Then I try to display passed data: {{ recievedMessage }}, but unfortunately it doesn't work.
Assuming you set up a global EventHub in your main.js, the second component isn't listening because it has not been initialized in the whole Vuejs life-cycle.
However, if you intended on your child component being rendered within the parent then you will need to import the component into the parent.
Parent Component
<template>
<div>
<input type="text" v-model="message" />
<button type="button" #click="submit">Submit</button>
<child-component />
</div>
</template>
<script>
import ChildComponent from '#/components/ChildComponent'
import { Bus } from './../main.js';
export default {
components:{
ChildComponent
}
data () {
return {
message: ''
}
},
methods: {
submit() {
if(this.message !== ''){
this.$router.push('location');
Bus.$emit('name', this.message);
}
}
}
}
</script>
UPDATED
Child Component
<template>
<div>{{recievedMessage}}</div>
</template>
<script>
import { Bus } from './../main.js';
export default {
data() {
return {
recievedMessage: ''
}
},
created() {
Bus.$on('name', this.eventHandlerMethod)
},
methods: {
eventHandlerMethod (message) {
this.recievedMessage = message;
}
}
}
</script>
The listener is calling "eventHandlerMethod" which update the data instance.

Vue component data not updating from props

I'm building a SPA with a scroll navigation being populated with menu items based on section components.
In my Home.vue I'm importing the scrollNav and the sections like this:
<template>
<div class="front-page">
<scroll-nav v-if="scrollNavShown" #select="changeSection" :active-section="activeItem" :items="sections"></scroll-nav>
<fp-sections #loaded="buildNav" :active="activeItem"></fp-sections>
</div>
</template>
<script>
import scrollNav from '.././components/scrollNav.vue'
import fpSections from './fpSections.vue'
export default {
data() {
return {
scrollNavShown: true,
activeItem: 'sectionOne',
scrollPosition: 0,
sections: []
}
},
methods: {
buildNav(sections) {
this.sections = sections;
console.log(this.sections)
},
changeSection(e) {
this.activeItem = e
},
},
components: {
scrollNav,
fpSections
}
}
</script>
this.sections is initially empty, since I'm populating this array with data from the individual sections in fpSections.vue:
<template>
<div class="fp-sections">
<keep-alive>
<transition
#enter="enter"
#leave="leave"
:css="false"
>
<component :is="activeSection"></component>
</transition>
</keep-alive>
</div>
</template>
<script>
import sectionOne from './sections/sectionOne.vue'
import sectionTwo from './sections/sectionTwo.vue'
import sectionThree from './sections/sectionThree.vue'
export default {
components: {
sectionOne,
sectionTwo,
sectionThree
},
props: {
active: String
},
data() {
return {
activeSection: this.active,
sections: []
}
},
mounted() {
this.buildNav();
},
methods: {
buildNav() {
let _components = this.$options.components;
for(let prop in _components) {
if(!_components[prop].hasOwnProperty('data')) continue;
this.sections.push({
title: _components[prop].data().title,
name: _components[prop].data().name
})
}
this.$emit('loaded', this.sections)
},
enter(el) {
twm.to(el, .2, {
autoAlpha : 1
})
},
leave(el, done) {
twm.to(el, .2, {
autoAlpha : 0
})
}
}
}
</script>
The buildNav method loops through the individual components' data and pushes it to a scoped this.sections array which are then emitted back to Home.vue
Back in Home.vue this.sections is populated with the data emitted from fpSections.vue and passed back to it as a prop.
When I inspect with Vue devtools the props are passed down correctly but the data does not update.
What am I missing here? The data should react to props when it is updated in the parent right?
:active="activeItem"
this is calld "dynamic prop" not dynamic data. You set in once "onInit".
For reactivity you can do
computed:{
activeSection(){ return this.active;}
}
or
watch: {
active(){
//do something
}
}
You could use the .sync modifier and then you need to emit the update, see my example on how it would work:
Vue.component('button-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
props: ['counter'],
watch: {
counter: function(){
this.$emit('update:counter',this.counter)
}
},
})
new Vue({
el: '#counter-sync-example',
data: {
foo: 0,
bar: 0
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="counter-sync-example">
<p>foo {{ foo }} <button-counter :counter="foo"></button-counter> (no sync)</p>
<p>bar {{ bar }} <button-counter :counter.sync="bar"></button-counter> (.sync)</p>
</div>