mitt event bus with combobox usage in VueJS 3 - vue.js

I have configured mitt as a global event bus in my main.js:
import { createApp } from "vue";
import App from './App.vue'
import mitt from "mitt";
const emitter = mitt();
const app = createApp(App);
app.config.globalProperties.emitter = emitter;
app.mount("#app");
And I'm publishing an event using the event-bus from the child component as follows:
<template>
<div class= "combobox">
<label for={{selector_name}}>
<p>{{ selector_title }}</p>
</label>
<select :name="selector_name" :id="selector_id" :disabled="disabled" #change="onChange">
<option v-for="(opt, index) in selector_options" :key="`opt ${index}`">
{{ opt }}
</option>
</select>
</div>
</template>
<script>
export default {
name: 'ComboBox',
data() {
return {
disabled: false,
};
},
props: {
selector_title: {
type: String,
required: true
},
selector_name: {
type: String,
required: true
},
selector_id: {
type: String,
default: "combobox"
},
selector_options: {
type: Array,
required: true
},
},
methods: {
onChange(event){
this.emitter.emit('lock-combobox', { id: this.selector_id });
},
},
}
</script>
Then I try to subscribe to the event in the parent component as follows:
<template>
<div id="controls" class="param">
<div class="control" id="combobox1">
<ComboBox
selector_title = "Pattern"
selector_name = "pattern"
:selector_options="['complex', 'simple']"
selector_id="combo1"
ref="combo1"
/>
</div>
<div class="control" id="combobox2">
<ComboBox
selector_title = "Hand"
selector_name = "pattern"
:selector_options="['Left', 'Right']"
selector_id="combo2"
ref="combo2"
/>
</div>
<div class="control" id="speedslider">
<SpeedSlider/>
</div>
<div class="control" id="startstop">
<StartStopButton/>
</div>
<div class = control id="emergency">
<Emergency/>
</div>
</div>
</template>
<script>
import ComboBox from '../components/ComboBox.vue';
export default {
name: "Controls",
components: {
ComboBox,
},
created() {
this.emitter.on('lock-combobox', (event) => {
this.$refs.event.id.disabled = true;
});
},
}
</script>
But whenever I change the option in the Combobox, I get an error:
Uncaught TypeError: undefined has no properties
Can you please tell me how can I solve that? thanks in advance.

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>

How to use v-model on component in vue 3 script setup

I want to add a v-model on a component but I got this warning:
[Vue warn]: Component emitted event "input" but it is neither declared in the emits option nor as an "onInput" prop.
Here is my code:
// Parent.vue
<template>
<h2>V-Model Parent</h2>
<Child v-model="name" label="Name" />
<p>{{ name }}</p>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const name = ref('')
</script>
// Child.vue
<template>
<input
class="input"
type="text"
:placeholder="props.label"
:value="props.value"
v-on:input="updateValue($event.target.value)"
/>
</template>
<script setup>
import { defineProps, defineEmit } from 'vue'
const props = defineProps({
label: String,
value: String
})
const emit = defineEmit('input')
function updateValue(value) {
emit('input', value)
}
</script>
I was trying to reproduce this tutorial but I'am stuck and got no idea what I am missing.
I want to display {{ name }} in the Parent.vue component. Do you got an idea how to solve this?
In vue 3 value prop has been changed to modelValue and the emitted event input to update:modelValue:
// Child.vue
<template>
<input
class="input"
type="text"
:placeholder="props.label"
:value="props.modelValue"
v-on:input="updateValue($event.target.value)"
/>
</template>
<script setup>
const props = defineProps({
modelValue: String
})
const emit = defineEmits(['update:modelValue'])
function updateValue(value) {
emit('update:modelValue', value)
}
</script>
I like to use with computed as well
<template>
<div>
<input v-model="model">
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
modelValue: {
type: [String, Number],
default: ''
}
})
const emit = defineEmits(['update:modelValue'])
const model = computed({
get () {
return props.modelValue
},
set (value) {
return emit('update:modelValue', value)
}
})
</script>
I have the similar issues and finally I got it work. Here are one solution for one or multiple checkbox for Vue 3 and TypeScript.
ref: https://v2.vuejs.org/v2/guide/forms.html?redirect=true#Checkbox
solution : for one or multiple checkbox
CheckBox Component:
<template>
<input
type="checkbox"
:value="inputValue"
:disabled="isDisabled"
v-model="model"
:class="[defaultClass, inputClass, checkboxClass]"
/>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
export default defineComponent({
components: {},
props: {
inputValue: {
type: String,
required: false,
default: '',
},
modelValue: {
type: [Object, Boolean] as PropType<String[] | Boolean>,
required: false,
default: (() => ({})) || false,
},
isDisabled: {
type: Boolean,
required: false,
default: false,
},
checkboxClass: {
type: String,
required: false,
default: '',
},
},
data() {
return {
defaultClass: 'h-4 w-4 rounded text-primary shadow-sm',
};
},
emits: ['update:modelValue'],
computed: {
model: {
get() {
return this.modelValue;
},
set(value) {
this.$emit('update:modelValue', value);
},
},
inputClass() {
if (this.isDisabled) {
return 'bg-dark-17 border-dark-13';
}
return 'bg-dark-23 border-dark-10 hover:bg-dark-25 focus:border-primary';
},
},
});
</script>
import CheckBox and use it
import CheckBox in other components;
<div>
<div v-for="(option, index) in options" :key="index">
<div
class="flex items-center justify-between p-6 py-4 border-b border-b-dark-13"
>
<div class="w-10">
<Checkbox :inputValue="option.name" v-model="selectedOptions" />
</div>
</div>
</div>
</div>
data() {
return {
selectedOptions: [],
};
},

My event doesn't emit from my parent component

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>

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.

VueJS + VueRouter + Bootstrap-vue (modal from modal) = <failed to convert exception to string>

I'll try display modal from modal (I'll create separate components for each modal). When second modal is closed I want to return to first modal. But after I'm display first modal second time I got error 'failed to convert exception to string' from administration.js. I'm using VueRouter too.
administration.js (main entry with router-view) -> partner.vue (with partnerEditor.vue component)
partnerEditor.vue
<template>
<div>
<b-modal ref="partnerEditor" title="Partner" :busy="isBusy" cancel-title="cancel" ok-variant="info" ok-title="save" v-on:ok="onCreate">
<div class="form-group">
<div class="input-group mb-3">
<select class="form-control">
<option v-for="contact in contacts">{{ contact.name }}</option>
</select>
<div class="input-group-append">
<b-btn v-on:click="$refs.contactEditor.show()"></b-btn>
</div>
</div>
</div>
</b-modal>
<contactEditor ref="contactEditor" v-on:hide="$refs.partnerEditor.show()"></contactEditor>
</div>
</template>
<script>
export default {
data() {
return {
contacts: [],
isBusy: false
}
},
methods: {
show() {
this.$refs.partnerEditor.show();
}
},
components: {
contactEditor: () => import('./contactEditor.vue'),
},
}
</script>
contactEditor.vue
<template>
<b-modal ref="contactEditor" title="Contact" :busy="isBusy" cancel-title="cancel" ok-variant="info" ok-title="save" v-on:ok="onCreate" v-on:hide="$emit('hide')">
<div class="form-group">
<label>name</label>
<input type="text" class="form-control" placeholder="" />
</div>
</b-modal>
</template>
<style></style>
<script>
export default {
model: {
event: 'hide'
},
data() {
return {
isBusy: false
}
},
methods: {
show() {
this.$refs.contactEditor.show();
},
}
}
</script>
routes
const router = new VueRouter({
routes: [
{
name: 'configuration', path: '/configuration', component: () => import('./administrator/configuration/index.vue'),
children: [
{ name: 'configuration-partner', path: '/configuration/partner', component: () => import('./administrator/configuration/partner.vue') }
]
}
]
});