My event doesn't emit from my parent component - vue.js

I have a parent component that triggers a method up being click that in terms should emit an event.
<template>
<div #click.self="toggleState" id="nav-icon3" :class="state">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</template>
<script>
export default {
data(){
return{
open : Boolean,
}
},
methods:{
toggleState: function(element){
this.open = element.target.classList.toggle('open');
console.log('toggle parent');
this.$emit('onToggle', this.open);
},
state: function(){
return this.open == true ? 'open' : 'false';
}
}
}
</script>
I try to catch this event here. Don't worry it's in a template and all I just cropped some of the code at the top. But my console.log('toggle nav'); never gets called. Why is this?
<div class="grid-item-3">
<!-- <LanguageSelect :default="defaultLanguage" /> -->
<ButtonItem :link="'mailto:caghan.bardakci#blockburn.io'" class="button" :theme="buttonTheme">{{buttonText}}</ButtonItem>
</div>
<div class="grid-item-4">
<HamburgerCross #onToggle="toggleHamburgerMenu" />
<HamburgerMenu :links="links" ref="hamburgerMenu" />
</div>
</div>
</template>
<script>
import ButtonItem from '#/components/widgets/clickables/button_item.vue'
import LanguageSelect from '#/components/widgets/language_select.vue'
import HamburgerCross from '#/components/widgets/navigation/hamburger_menu_cross.vue'
import HamburgerMenu from '#/components/widgets/navigation/hamburger_menu.vue'
export default {
components:{
ButtonItem,
LanguageSelect,
HamburgerCross,
HamburgerMenu,
},
data(){
return{
title: 'BLOCKBURN',
links: {'About' : '/about', 'Dapp' : '/dapp', 'Game' : '/game', 'Roadmap' : '/roadmap'},
defaultLanguage: 'uk',
buttonText: 'BUY BURN IEO',
buttonTheme: 'secondary',
}
},
methods:{
toggleHamburgerMenu(event, value){
console.log('toggle nav');
//this.$refs.hamburgerMenu.toggle();
}
}
}
</script>

Related

function not updating vue property

I have this component:
<template>
<div class="hello">
<div>
My prop: {{ myprop }}?
</div>
<div>
<button class="fas fa-lock-open lock" #click="changeText()">Click</button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'StartPage',
props: {
myprop: {
type: String
}
},
model: {
prop: 'myprop',
event: 'click'
},
methods: {
changeText () {
this.$emit('click', 'sometext')
console.log('this.myprop', this.myprop)
}
}
})
</script>
Im using vue v3. Everytime I click on the button, I still see the text "My prop: ?" in the browser.
And in the console I can see: "this.myprop undefined" every time I click on the button.
What am I doing wrong?
As per my understanding, You are trying to update the prop text on click of button from the child component. If Yes, you can achieve it simply by emitting a new text and updating that in the parent component.
Live Demo :
const ShowPropText = {
template: `<div class="hello">
<div>
My prop: {{ myprop }}
</div>
<div>
<button class="fas fa-lock-open lock" #click="changeText()">Click</button>
</div>
</div>`,
props: ['myprop'],
methods: {
changeText() {
this.$emit('click-event', 'sometext')
}
}
}
const app = Vue.createApp({
components: {
'show-prop-text': ShowPropText
},
data() {
return {
text: 'This is default text'
}
},
methods: {
methodCall(e) {
this.text = e;
}
}
})
app.mount('#app')
<script src="https://cdn.jsdelivr.net/npm/vue#next"></script>
<div id="app">
<show-prop-text :myprop="text" #click-event="methodCall"></show-prop-text>
</div>

Show on click / hide on blur wrapper component

I have several widgets that I'd like to toggle on click/blur/submit.
Let's take a simple example with an input (Vue 2 style)
Input.vue
<template>
<input
ref="input"
:value="value"
#input="input"
#blur="input"
#keyup.escape="close"
#keyup.enter="input"
/>
</template>
<script>
export default {
props: ['value'],
methods: {
input() {
this.$emit("input", this.$refs.text.value);
this.close();
},
close() {
this.$emit("close");
},
}
}
</script>
ToggleWrapper.vue
<template>
<div #click="open = true">
<div v-if="open">
<slot #close="open = false"></slot> <!-- Attempt to intercept the close event -->
</div>
</div>
</template>
<script>
export default {
data() {
return {
open: false,
}
},
}
</script>
Final usage:
<ToggleWrapper>
<Input v-model="myText" #submit="updateMyText" />
</ToggleWrapper>
So when I click on ToggleWrapper it appears, but if I close it, it doesn't disappear because it's not getting the close event.
Should I use scoped events ?
How can I intercept the close event by adding the less possible markup on the final usage ?
I think it makes sense to use a scoped slot to do this. But you can also try this kind of solution.
Input.vue
<template>
<input
ref="input"
:value="value"
#input="input"
#blur="input"
#keyup.escape="close"
#keyup.enter="input"
/>
</template>
<script>
export default {
props: ['value'],
methods: {
input() {
this.$emit("input", this.$refs.text.value);
this.close();
},
close() {
this.$parent.$emit('close-toggle')
},
}
}
</script>
ToggleWrapper.vue
<template>
<div #click="open = true">
Click
<div v-if="open">
<slot></slot> <!-- Attempt to intercept the close event -->
</div>
</div>
</template>
<script>
export default {
data() {
return {
open: false,
}
},
created() {
this.$on('close-toggle', function () {
this.open = false
})
}
}
</script>
In a Vue3 style, I would use provide and inject (dependency injection). This solution leaves the final markup very light and you still have a lot of control, see it below :
Final usage :
<script setup>
import { ref } from 'vue'
import ToggleWrapper from './ToggleWrapper.vue'
import Input from './Input.vue'
const myText = ref('hi')
const updateMyText = ($event) => {
myText.value = $event
}
</script>
<template>
<ToggleWrapper>
<Input :value="myText" #submit="updateMyText" />
</ToggleWrapper>
<p>value : {{myText}}</p>
</template>
ToggleWrapper.vue
<template>
<div #click="open = true">
<div v-if="open">
<slot></slot>
</div>
<span v-else>Open</span>
</div>
</template>
<script setup>
import { provide, inject, ref } from 'vue'
const open = ref(false)
provide('methods', {
close: () => open.value = false
})
</script>
Input.vue
<template>
<input
:value="value"
#input="input"
#blur="close"
#keyup.escape="close"
#keyup.enter="submit"
/>
</template>
<script setup>
import { inject, ref } from 'vue'
const props = defineProps(['value'])
const emit = defineEmits(['close', 'input', 'submit'])
const methods = inject('methods')
const value = ref(props.value)
const input = ($event) => {
value.value = $event.target.value
emit("input", $event.target.value);
}
const close = () => {
methods.close()
emit('close')
}
const submit = () => {
emit('submit', value.value)
close()
}
</script>
See it working here

Why is component shown just for a fraction of a second?

As a beginner in Vue, I am struggling with a problem that - even this should not be too hard - I can't solve. The principle is as follows:
I want to create a survey that consists of different topics. The user should be able to choose between these topics (component A and component B). This works fine.
But: When I click on the button "Show Component C", this component is only displayed for a fraction of a second. Why is this, what mistake have I made and how can I solve the problem?
Thanks a lot for your help!
App.vue
<button #click="setSelectedComponent('ComponentA')">Component A</button>
<button #click="setSelectedComponent('ComponentB')">Component B</button>
<component-b
v-if="selectedComponent === 'ComponentB'"
> </component-b>
<component-a
v-if="selectedComponent === 'ComponentA'"
></component-a>
<start
v-if="selectedComponent === 'form-empty'"
></start>
</template>
<script>
import ComponentB from './components/ComponentB.vue';
import ComponentA from './components/ComponentA.vue';
import Start from './components/Start.vue';
export default {
components: {
ComponentB,
ComponentA,
Start,
},
data() {
return {
selectedComponent: 'form-empty',
}
},
methods: {
setSelectedComponent(cmp) {
this.selectedComponent = cmp;
},
}
}
</script>
Start.vue
<template>
<form>
<div>
<h1>Which Component Do You Want To Select?</h1>
</div>
</form>
</template>
**
Component A
<template>
<form>
<h1>Component A</h1>
<div class="form-control">
<input type="range" min ="0" max="100" v-model=value>
</div>
<div>
<button #click="evaluateForm">Save Data</button>
</div>
<h4>Value: {{value}}</h4>
</form>
<component-c v-if="varia === 'yes'"></component-c>
</template>
<script>
import ComponentC from './ComponentC.vue';
export default {
components: {
ComponentC
},
methods: {
evaluateForm() {
this.varia='yes'
}
},
computed: {
result() {
return parseInt(this.abc) + parseInt(this.cde)
}
},
data() {
return {
value: '',
varia: ''
}
}
}
</script>
Component B
<template>
<form>
<h1>Component B</h1>
<div>
<input type="range" min ="0" max="100" v-model=value>
</div>
<div>
<button #click="evaluateForm">Save Data</button>
</div>
<h4>Value: {{value}}</h4>
</form>
<component-c v-if="varia === 'yes'"></component-c>
</template>
<script>
import ComponentC from './ComponentC.vue';
export default {
components: {
ComponentC
},
methods: {
evaluateForm() {
this.varia='yes'
}
},
computed: {
result() {
return parseInt(this.abc) + parseInt(this.cde)
}
},
data() {
return {
value: '',
varia: ''
}
}
}
</script>
Component C
<template>
<form>
<div class="form-control">
<h1>The value is: </h1>
</div>
</form>
</template>
I tested this out locally and the problem comes from the fact that you are using <form> in ComponentA and ComponentB. If you switch those to <div> or <form #submit.prevent> you'll see that it works as you expected.
Here is some documentation on the <form> element to learn more about how it works: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form
Component A
<template>
<div>
<h1>Component A</h1>
<div>
<input type="range" min ="0" max="100" v-model=value>
</div>
<div>
<button #click="evaluateForm">Save Data</button>
</div>
<h4>Value: {{value}}</h4>
</div>
</template>
<script>
export default {
methods: {
evaluateForm() {
this.$emit('xxx');
}
},
computed: {
result() {
return parseInt(this.abc) + parseInt(this.cde)
}
},
data() {
return {
value: '',
}
}
}
</script>
App.Vue
<template>
<button #click="setSelectedComponent('ComponentA')">Component A</button>
<button #click="setSelectedComponent('ComponentB')">Component B</button>
<component
:is="selectedComponent"
></component>
<component-a
#xxx="startComponentC"
v-if="varia === 'yes'"
></component-a>
<component-c
v-if="varia === 'yes'"
></component-c>
</template>
<script>
import ComponentB from "./components/ComponentB.vue";
import ComponentA from "./components/ComponentA.vue";
import Start from "./components/Start.vue";
export default {
components: {
ComponentB,
ComponentA,
Start,
},
data() {
return {
selectedComponent: "start",
varia:""
};
},
methods: {
setSelectedComponent(cmp) {
this.selectedComponent = cmp;
},
startComponentC() {
this.varia="yes"
this.selectedComponent="stopConditionforComponentAandB"
}
},
};
</script>

Watching properties in child component does not work

I have 2 components, a parent and a child. I want to modify the value of a prop in my parent component (by calling a method when a button is clicked) and send it to the child component. In the child component, I want to watch for changes in my prop, so that anytime it changes, it does something (for testing purposes, I tried to console.log() the prop).
Parent:
<template>
<div>
<h5>Your Feeds</h5>
<div id="feeds">
<div class="card" v-for="feed in feeds">
<div class="card-body" :id="feed['_id']" >
{{ feed['name'] }}
<button v-on:click="loadFeed(feed['_id'])">Button</button>
</div>
</div>
</div>
</div>
</template>
<script>
import GridComponent from "./GridComponent";
export default {
name: "FeedsListComponent",
data() {
return {
feeds: []
}
},
mounted() {
axios
.get("/getFeeds")
.then(response => (this.feeds = response.data))
.catch(error => console.log(error))
},
methods: {
loadFeed(id) {
this.feedId = id
}
},
components: {
GridComponent
}
}
</script>
Child:
<template>
<div id="grid">
<v-grid
theme="compact"
:source="rows"
:columns="columns"
></v-grid>
</div>
</template>
<script>
import VGrid from "#revolist/vue-datagrid";
export default {
name: "Grid",
props: ['feedId'],
data() {
return {
columns: [],
rows: [],
};
},
watch: {
feedId: function(val, oldVal) {
console.log(val)
console.log(oldVal)
console.log(this.feedId)
//here I want to send an ajax request with feedId to one of my controllers in order to get
//the data needed for rows and colums
}
},
components: {
VGrid,
},
};
</script>
I put together a sample that is working in order to help you diagnose why yours isn't working:
Parent.vue
<template>
<div class="parent">
<h3>Parent</h3>
<div class="row">
<div class="col-md-6">
<button class="btn btn-secondary" #click="incrementCounter">Change parent message</button>
</div>
</div>
<child :propMessage="message" />
</div>
</template>
<script>
import Child from '#/components/stackoverflow/watch-prop/Child'
export default {
components: {
Child
},
data() {
return {
counter: 0
}
},
computed: {
message() {
return 'Message' + this.counter;
}
},
methods: {
incrementCounter() {
this.counter++;
}
}
}
</script>
Child.vue
<template>
<div class="child">
<hr>
<div class="row">
<div class="col-md-6">
<label>Message in child from watched prop:</label>{{ dataMessage }}
</div>
</div>
</div>
</template>
<script>
export default {
props: {
propMessage: {
type: String,
required: true
}
},
data() {
return {
dataMessage: this.propMessage
}
},
watch: {
propMessage(newMessage) {
this.dataMessage = newMessage;
}
}
}
</script>
<style scoped>
label {
font-weight: bold;
margin-right: 0.5rem;
}
</style>

VueJS display dynamic modal component

I have posts and replys s.t. replies belong to posts via the attribute reply.posts_id.
I am attempting to show the reply form as a modal for the user to enter a reply. However, I want to create a generic Modal component that I can use everywhere with content that is specified in another component built for a specific context.
Reply to post is the first place I woul like this to work.
Currently, the Vuex correctly returns Modal visible:true when the reply button is clicked, but the modal does not render and I get the error message showing that the Modal component is not found:
Unknown custom element: <ModalReplyForm> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I am using vuex to manage the visibility of the modal. Here are the relevant files:
store.js:
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
...
Vue.use(Vuex)
export default new Vuex.Store({
state: {
status: '',
...
modalVisible: false,
modalComponent: null
},
mutations: {
...
showModal(state, componentName) {
console.log('showing the modal')
state.modalVisible = true;
state.modalComponent = componentName;
},
hideModal(state) {
console.log('hiding the modal')
state.modalVisible = false;
}
},
actions: {
...
}
},
getters: {
isAuthenticated: state => !!state.user,
authStatus: state => state.status,
user: state => state.user,
token: state => state.token,
posts: state => {
return state.posts;
}
...
}
})
App.vue
<template>
<div id="app">
<app-modal></app-modal>
<NavigationBar />
<div class="container mt-20">
<router-view />
</div>
<vue-snotify></vue-snotify>
</div>
</template>
<script>
import AppModal from '#/components/global/AppModal';
import NavigationBar from '#/components/layout/NavigationBar'
export default {
name: "App",
components: {
AppModal,
NavigationBar
}
};
</script>
<style>
body {
background-color: #f7f7f7;
}
.is-danger {
color: #9f3a38;
}
</style>
Post.vue (houses the button to call the reply modal):
<template>
<div class="row ui dividing header news">
<!-- Label -->
<div class="m-1 col-md-2 ui image justify-content-center align-self-center">
<img v-if="post.avatar_url" :src="post.avatar_url" class="mini rounded"/>
<v-gravatar v-else :email="post.email" class="mini thumbnail rounded image rounded-circle z-depth-1-half"/>
</div>
<!-- Excerpt -->
<div class="col-md-9 excerpt">
...
<!-- Feed footer -->
<div class="feed-footer row">
<div class="small"> {{ post.created_at | timeAgo }}</div>
<button type="button" flat color="green" #click="showModal('ModalReplyForm')">
<i class="fa fa-reply" ></i>
...
<div v-show="postOwner(post)" class="">
<button type="button" flat color="grey" #click="deletePost(post.id)">
<i class="fa fa-trash " ></i>
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex';
import PostsService from '../../services/PostsService'
import RepliesService from '../../services/RepliesService'
import Replies from '#/components/Reply/Replies'
import ReplyForm from '#/components/Reply/ReplyForm'
export default {
name: "Post",
props: {
post: {
type: Object,
required: true
}
},
components: {
Replies,
ReplyForm
},
computed: {
me() {
return this.$store.getters.user
}
},
methods: {
...mapMutations(['showModal']),
...
}
};
</script>
AppModal.vue - generic Modal component
<template>
<div class="c-appModal">
<div class="c-appModal__overlay" v-if="visible"></div>
<div class="c-appModal__content" v-if="visible" #click.self="hideModal"></div>
<div class="c-appModal__innerContent">
<component :is="component"></component>
</div>
</div>
</template>
<script>
import Vue from 'vue';
import { mapState, mapMutations } from 'vuex';
export default {
name: 'AppModal',
data() {
return {
component: null
}
},
computed: {
...mapState({
visible: 'modalVisible',
modalComponent: 'modalComponent'
}),
},
methods: {
...mapMutations(['hideModal'])
},
watch: {
modalComponent(componentName) {
if (!componentName) return;
Vue.component(componentName, () => import(`#/components/modals/${componentName}`));
this.component = componentName;
}
},
created() {
const escapeHandler = (e) => {
if (e.key === 'Escape' && this.visible) {
this.hideModal();
}
};
document.addEventListener('keydown', escapeHandler);
this.$once('hook:destroyed', () => {
document.removeEventListener('keydown', escapeHandler);
});
},
};
</script>
ModalReplyForm - specific reply modal content
<template>
<div>
<div class="c-modalReply">
<div>
<label for="reply">Your comment</label>
<div class="field">
<textarea name="reply" v-model="reply" rows="2" placeholder="Compose reply"></textarea>
</div>
</div>
<button class="c-modalReply__cancel" #click="hideModal">Cancel</button>
<button class="c-modalReply__post" :disabled="!isFormValid" #click="createReply">Reply</button>
</div>
</div>
</template>
<script>
import RepliesService from '#/services/RepliesService'
import { mapMutations } from 'vuex';
export default {
name: "ModalReplyForm",
// props: {
// post: {
// type: Object,
// required: true
// }
// },
data() {
return {
reply: ""
};
},
computed: {
isFormValid() {
return !!this.reply;
},
currentGroup() {
return this.$store.getters.currentPost;
}
},
methods: {
...mapMutations([
'hideModal'
]),
async createReply () {
let result = await RepliesService.addReply({
reply: {
body: this.reply,
postId: this.post.id
}
});
this.$emit("reply-created");
this.hideModal();
}
}
};
</script>
Unknown custom element: - did you register the
component correctly? For recursive components, make sure to provide
the "name" option.
This message says that you never imported/defined ModalReplyForm, which you have not.
In my own generic modal, I ended up having to import all the components that might appear within the modal itself.
If you add a:
import ModalReportForm from ...
and a:
components: {
ModalReplyForm
}
to AppModal.vue, the modal should then do what you expect.