Getting the data back from child to parent element in Vue - vue.js

Before last updates I used a custom component to work with sliders. Works perfectly, but after upgrading I got a problem.
This is the wayI use my custom component:
<ta-slider2 v-model="stelling1"/>
This passes the value to the custom component and received the data back.
This is my custom component
<template>
<div class="mt-3">
<div><br><br></div>
<v-slider
prop="value"
v-model="interface"
:value="value"
:color="color"
always-dirty
min="-100"
max="100"
thumb-label="always"
>
<template v-slot:append>
<v-icon color="blue">add_circle_outline</v-icon>
</template>
<template v-slot:prepend>
<v-icon color="error">remove_circle_outline</v-icon>
</template>
</v-slider>
</div>
</template>
<script>
export default {
name: "ta-slider2",
props: {
value: null
},
data() {
return {
}
},
computed: {
interface: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
},
color() {
if (this.value < 0) return 'red'
if (this.value > 0) return 'blue'
return 'red'
},
},
}
</script>
<style scoped>
</style>
Worked perfect but now the line with v-model="interface" gives problems. Googled for hours but can not find a solution. Who can help me out?

Related

VueJS - use sidebar component event to control navbar

I am building a web app using Vue3 + vuetify3 running on Vite. I have a v-navigation-drawer within my Sidebar.vue component (child) and I'm emitting mouse events to have my App.vue (parent) listen to it and allow another child components (in this case Topbar.vue) to use that to move the title to the right as the sidebar opens and closes. The problem I'm seeing is that I can log the events in App.vue from the Sidebar as true/false but when I check those values in Topbar.vue they're undefined. I have tried different approaches but can't come up with a solution. If somebody can give me some pointers on how to figure this out I would really appreciate it.
For the sake of space, I'm not including all the css rules and all the items in the sidebar, just the functional sections worth looking at but again, the sidebar is not the issue but the way the topbar is not getting the value from the App.vue.
Sidebar.vue
<template>
<v-navigation-drawer v-model="drawer" class="sidepanel" expand-on-hover rail>
<v-list class="sidebarlist" density="compact" nav>
<router-link to="/">
<v-list-item
class="sidebar-item"
:style="{
backgroundColor: selectedRoute === 'dashboard' ? '#9100e9' : '',
}"
#click="selectedRoute = 'dashboard'"
prepend-icon="mdi-home"
title="Dashboard"
value="dashboard"
></v-list-item>
</router-link>
</v-list>
</v-navigation-drawer>
</template>
<script lang="ts">
export default {
data() {
return {
drawer: true,
selectedRoute: "",
};
},
methods: {
onHover() {
this.$emit("mouseenter");
},
onLeave() {
this.$emit("mouseleave");
},
},
};
</script>
<style scoped>
.sidebar-opened {
opacity: 1;
transition: 0.3s ease-out;
}
.sidebar-item:hover {
background-color: #4b247a;
transition-duration: 0.4s;
}
/* and more */
</style>
App.vue
<template>
<v-app :theme="theme">
<TopbarComp :title="$route.meta.title" :sidebarHovered="sidebarHovered"/>
<SideBarComp #mouseenter="onHover" #mouseleave="onLeave"/>
<router-view />
</v-app>
</template>
<script lang="ts">
import { ref } from "vue";
import SideBarComp from "./components/Sidebar.vue";
export default {
components: {
SideBarComp,
},
methods: {
onHover() {
// Slide Topbar to the right
this.sidebarHovered = true;
console.log('Sidebar entered')
},
onLeave() {
// Slide Topbar back in place
this.sidebarHovered = false;
}
},
setup() {
const theme = ref("dark");
const sidebarHovered = ref(false);
return {
theme,
sidebarHovered
};
}
};
</script>
Topbar.vue
<template>
<v-toolbar fixed app color="#2D2D2D">
<v-toolbar-items>
<v-toolbar-title
class="text-uppercase pa-5"
:class="{ 'slide-right': topbarSlide || sidebarHovered }"
#mouseenter="onHover"
#mouseleave="onLeave"
>
<span class="font-weight-light" style="color: #9E9E9E;">{{ firstWord + ' '}} </span>
<span>{{ restOfWords }}</span>
</v-toolbar-title>
</v-toolbar-items>
</v-toolbar>
</template>
<script lang="ts">
export default {
props: ['title', 'sidebarHovered'],
data() {
return {
toptitle: this.title || '',
topbarSlide: false,
};
},
computed: {
firstWord() {
return this.toptitle.split(" ")[0];
},
restOfWords() {
return this.toptitle.split(" ").slice(1).join(" ");
},
},
methods: {
onHover() {
this.topbarSlide = true;
console.log('The value of the Sidebar is: ',this.sidebarHovered)
console.log('The value of the Topbar is: ', this.topbarSlide)
},
onLeave() {
this.topbarSlide = false;
},
},
};
</script>
<style>
.slide-right {
transform: translateX(20%) !important;
transition: transform 0.3s ease-out !important;
}
</style>

How to pass custom props to component in Vue from function?

I want to pass isReadonly boolean value from first component to second.
And it does not work.
Edited after cafertayyar answer.
Method isReadonly moved from methods to computed.
First component:
<template>
<PreliminaryInformationUsageCode :is-readonly="isReadonly" />
</template>
<script>
import PreliminaryInformationUsageCode from './form/PreliminaryInformationUsageCode.vue'
export default {
name: 'FormPage',
computed: {
form() {
return this.$store.getters['form/form']
},
isReadonly: function() {
//return true
return false
}
},
components: {
PreliminaryInformationUsageCode,
},
}
</script>
Second component:
<template>
<v-select
v-model="usageCodesSelected"
:items="usageCodes"
item-text="name"
item-value="code"
label="Label"
multiple
hint="Hint"
persistent-hint
v-bind:readonly="isReadonly"
>
<template v-slot:selection="{ item, index }">
<v-chip v-if="index === 0">
<span>{{ item.name }}</span>
</v-chip>
<span
v-if="index === 1"
class="grey--text text-caption"
>
(+{{ usageCodesSelected.length - 1 }} дополнительно)
</span>
</template>
</v-select>
</template>
<script>
export default {
name: 'PreliminaryInformationUsageCode',
props: {
isReadonly: {
Boolean
},
},
data: function() {
return {
usageCodesSelected: [
],
usageCodes: [
],
}
},
}
</script>
Use this:
<PreliminaryInformationUsageCode :is-readonly="isReadonly"/>
and instead of using isReadonly function, define a computed like:
computed: {
isReadonly() {
return this.form.status.seq != 10;
}
}

How do you use buefy's b-taginput in a custom component so that it works like v-model, It is only working one way binding?

I'm new to vue and decided to try out buefy for some useful components.
To try and keep my code organized I'm trying to make a custom component using the b-taginput.
I have it so that the component loads with the tags in someArrayofTags, but when I'm typing into the b-taginput, it does not add new tags into someArrayofTags. Hence I lose the two-way binding / updating. I would like to know where I am going wrong and how I could adjust this.
I'm not too well versed to understand how they have implemented it, but i do see that it is composed of autocomplete and b-tags https://github.com/buefy/buefy/blob/dev/src/components/taginput/Taginput.vue
I'm trying to use the custom component as such
<mytaglist v-model="someArrayofTags"></mytaglist>
I know v-model is just v-bind on value and #input events. My code is below.
<template>
<b-field label="tag inputs">
<b-taginput
:value="value"
#input=someMethod($event.target.value)
size="is-small"
ref="ti"
>
<template slot="selected" slot-scope="value">
<b-tag
v-for="(tag, index) in value.tags"
:key="index"
:type="getType(tag)"
rounded
:tabstop="false"
ellipsis
closable
#close="$refs.ti.removeTag(index, $event)"
>
{{ tag }}
</b-tag>
</template>
</b-taginput></b-field
>
</template>
<script>
export default {
props: ['value'],
data() {
return {
normal: ["wnl","clear"]
};
},
methods: {
someMethod(tags) {
alert(tags)
this.$emit("input", tags)
},
getType(tag) {
if (this.normal.includes(tag)) {
return "is-danger";
} else {
return "is-success";
}
},
},
};
</script>
Thanks
After going through the source for buefy, I found that I could watch and update the values based on a v-model within the new component.
The code below works for me, but if anyone could provide a better solution I will leave it open.
<template>
<b-field label="tag inputs">
<b-taginput
v-model="newValue"
size="is-large"
ref="ti"
>
<template slot="selected" slot-scope="value">
<b-tag
v-for="(tag, index) in value.tags"
:key="index"
:type="getType(tag)"
rounded
:tabstop="false"
ellipsis
closable
#close="$refs.ti.removeTag(index, $event)"
>
{{ tag }}
</b-tag>
</template>
</b-taginput></b-field
>
</template>
<script>
export default {
props: ['value'],
data() {
return {
normal: ["wnl","clear","deep & quiet"],
newValue: this.value
};
},
methods: {
getType(tag) {
if (this.normal.includes(tag)) {
return "is-danger";
} else {
return "is-success";
}
},
},
watch: {
newValue(value) {
this.$emit('input', value)
},
value(value) {
this.newValue = value
}
}
};
</script>
<style>
</style>

How to update counter inside a v-loop in vue.js?

How can I i update the counters separatly inside v-for? Now it updates both when incrementing, since the list can be dynamic i cant set variables for all the types.
The code i run inside the v-for is:
<a #click.prevent="decrease(quote, min)">
Decrease
</a>
<input name="types" type="text" v-model="quote" >
<a #click.prevent="increase(quote, max)">
Increase
</a>
It looks like:
And here is the vue script:
<script>
var app = new Vue({
el: "#contents",
data: {
quote: 1,
},
computed: {
},
methods: {
decrease: function(quote, min){
app.quote--;
if(app.quote < min){
app.quote = min;
}
},
increase: function(quote, max){
if(app.quote > max){
app.quote = max;
}
},
},
});
</script>
You should do something like this:
<template>
<div>
<div v-for="item in d" :key="item">
<Counter></Counter>
</div>
</div>
</template>
<script>
import Counter from "./Counter.vue";
export default {
name: "HelloWorld",
components: { Counter },
props: {
msg: String
},
data: () => {
return {
d: [1, 2]
};
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
/// counter component with its own state
<template>
<div>
<div>
<span>{{count}}</span>
<button v-on:click="increment">increment</button>
<button v-on:click="decrement">decrement</button>
</div>
</div>
</template>
<script>
export default {
name: "Counter",
data: () => {
return {
count: 0
};
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
demo: https://codesandbox.io/s/silly-solomon-0hk16?file=/src/components/Counter.vue:0-516
The idea is that you should put the logic inside each component, but in your case you set just one state ant try to share it for many instance, instead you should create a reusable component with its own state, like in my case : count

How to make a function from another brother component be called when pressed?

Good afternoon, tell me please, I wrote a calendar displaying the events that the user sets, and now I want to make a detailed display of the event when I click it. The problem is that the events are in my component and the whole display logic is in another, how can I use them together.I want to make it so that the getDetailInformation() function in the ModalWindowDetail component component is called by clicking on an event in another component Calendar.vue. I use the event bus, but nothing works for me, I don’t understand why. Please help me solve this problem.
Screenshot of Calendar and error in console
Code of Calendar on GitHub
App.vue:
<template>
<div class="all">
<app-calendar #sendTextEvent="text = $event"></app-calendar>
<app-detail v-if="modalWindowDetail"
:eventText="text"></app-detail>
</div>
</template>
<script>
import appCalendar from './components/Calendar.vue'
import appDetail from './components/ModalWindowDetail.vue'
export default {
data(){
return{
text: String
}
},
components: {
appCalendar,
appDetail
},
computed: {
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
}
}
};
</script>
Calendar.vue component which display calendar:
<template>
<div class="overflow-div">
<transition :name="nameOfClass" >
<div :key="currentPage" class="fade_wrapper">
<div v-for="(week, i) in getCalendar" class="d_day">
<li v-for="day in week" class="li_day">
<div class="day">{{ day }}</div>
<div v-for="event in buildEvents(i, day)"
class="event"
v-bind:class="{ 'eventBrown': eventBrown(event),
'eventPurple': eventPurple(event),
'eventOrange': eventOrange(event),
'eventBlue': eventBlue(event) }"
v-on:click="openModalDetail(event)">{{ event }}
</div>
</li>
</div>
</div>
</transition>
</div>
</template>
<script>
import json from './Calendar_data.json'
import { mapState } from "vuex";
import { eventBus } from './../main.js'
export default {
mounted(){
eventBus.$on('getDetailInformation', this.openModalDetail())
},
computed: {
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
},
},
methods: {
openModalDetail(text){
this.$emit('sendTextEvent', text);
}
};
</script>
The component in which the getDetailInformation() is located ModalWindowDetail.vue:
<template>
<div class="underModalWindow">
<div class="modalWindow">
<img src="src/assets/x.png" width="20px" height="20px">
<div class="nameofModal">Вся детальная информация о событии</div>
<div v-for="(key, name) in eventDetail" class="detailEvent">{{ name }}: {{ key }}</div>
<button>Окей</button>
</div>
</div>
</template>
<script>
import { eventBus } from './../main.js'
export default {
props: ['eventText'],
data(){
return{
options: [
{ text: 'Встреча', value: '8' },
{ text: 'День Рождения', value: '4' },
{ text: 'Праздник', value: '1' },
{ text: 'Другое', value: '16' }
],
eventDetail: Object,
}
},
computed: {
eventsData() {
return this.$store.state.eventData;
},
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
},
},
created(){
eventBus.$emit('getDetailInformation', this.getDetailInformation())
},
methods: {
getDetailInformation(){
let arrOfEvents = this.eventsData.events;
for(let z = 0; z < arrOfEvents.length; z++){
let memo = arrOfEvents[z].memo;
console.log(this.memo)
if(memo === this.eventText){
let dataStartOfEvent = arrOfEvents[z].starts_at;
let getStartDataOfEvent = new Date(dataStartOfEvent);
let dataEndOfEvent = arrOfEvents[z].ends_at;
let getEndDataOfEvent = new Date(dataEndOfEvent);
if((getStartDataOfEvent.getHours() - 3) > 0){
this.$store.commit('changeModalWindowDetail', this.modalWindowDetail);
this.eventDetail = {
'Событие': this.eventText,
'Начало события': getStartDataOfEvent.toLocaleTimeString(),
'Конец события': getEndDataOfEvent.toLocaleTimeString(),
'Тип события': this.getType(arrOfEvents[z].type)
}
}else if(getStartDataOfEvent.getDate() != getEndDataOfEvent.getDate()){
this.$store.commit('changeModalWindowDetail', this.modalWindowDetail);
this.eventDetail = {
'Событие': this.eventText,
'Начало события': getStartDataOfEvent.toLocaleDateString(),
'Конец события': getEndDataOfEvent.toLocaleDateString(),
'Тип События': this.getType(arrOfEvents[z].type)
}
}
}
}
}
}
};
</script>
You should remove the () from the function name in eventBus.$on('getDetailInformation', this.openModalDetail()) - you want to reference the function, not to call it and use the result as a reference.
Also, your function getDetailInformation() does not return anything - but you seem to expect that it returns a text. You should correct this.
And finally, I think that #sendTextEvent="text = arguments[0]" would be more appropriate - and using a dedicated method/function will be the best.