Pre-selected value in Vue-select component in Vue3 - vue.js

I am having trouble having a pre-selected option in the Vue-Select component that I am using as a child component.
If I use this in my parent component it works perfectly. My parent component is:
const myForm = reactive({ name: "", age: "", gender: "", fruit_parent:null, fruit_child:null });
const fruits = ref(['Mango','Apple','Grapes']);
myForm.fruit_child = "Mango";
myForm.fruit_parent = "Grapes";
provide('quoteForm', myForm);
function onSubmit(){
console.log(myForm);
}
<form v-on:submit.prevent="onSubmit">
<fieldset>
<legend>Basic details</legend>
<div><base-input type=text label="Name" v-model="myForm.name" /></div>
<div><base-input type=number label="Age" v-model="myForm.age" /></div>
<div>
<v-select
:options="fruits"
placeholder="Fruit in Parent component"
v-model="myForm.fruit_parent" />
</div>
<div>
<base-select label="Fruit in Child component" :fruits="fruits" v-model="myForm.fruit_child" />
</div>
<div><button type="submit">Submit</button></div>
</fieldset>
</form>
And the child component is:
<script setup lang="ts">
import { ref, inject } from "vue";
import vSelect from "vue-select";
import 'vue-select/dist/vue-select.css';
defineEmits(["update:modelValue"]);
const form = inject('quoteForm');
console.log(form.fruit_child);
const props = defineProps({
label: {
type: String,
default: "",
},
modelValue: {
type: [String, Number],
default: null,
},
fruits: {type: Array, default:[]}
});
function updateCountry(country){
$emit('update:modelValue', country)
}
</script>
<template>
<div>
<v-select
:options="fruits"
:value="modelValue"
:required="!modelValue"
#change="$emit('update:modelValue', $event.target.value)"
#option:selected="(selected) => $emit('update:modelValue', selected)"
:placeholder="label"/>
</div>
</template>
I expect the parent fruit select to have Grapes selected- that works and Child select to have Mango selected, which does not work.

Related

mitt event bus with combobox usage in VueJS 3

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.

How to pass class attribute to child component element

how can I pass class attribute from parent to child component element?
Look here:
https://codesandbox.io/s/pedantic-shamir-1wuuv
I'm adding class to the Component "Input Field"
And my goal is that the 'custom-class' will be implemented on the 'input' element in the child component
But just still using the class attribute, and not setting a new prop like "customClass" and accept it in the props of the component
Thanks!
This depends on the template structure of your ChildComponent
Your Parent Component can look like this:
<div id="app">
<InputField class="anyClass" />
</div>
If your Child looks like this:
<template>
<input ... />
</template
Because if you have only 1 root Element in your template this will inherit the classes given automatically.
if your Template e.g. looks like this: (only available in vue3)
<template>
<input v-bind="$attrs" />
<span> hi </span>
</template
You will need the v-bind="$attrs" so Vue knows where to put the attributes to. Another Solution would be giving classes as props and assigning it to the element with :class="classes"
The pattern for the customs form component in Vue 2 where the props go to a child element looks something like this.
<template>
<div class="input-field">
<label v-if="label">{{ label }}</label>
<input
:value="value"
:class="inputClass"
#input="updateValue"
v-bind="$attrs"
v-on="listeners"
/>
</div>
</template>
<script>
export default {
inheritAttrs: false,
props: {
label: {
type: String,
default: "",
},
value: [String, Number],
inputClass: {
type: String,
default: "",
},
},
computed: {
listeners() {
return {
...this.$listeners,
input: this.updateValue,
};
},
},
methods: {
updateValue(event) {
this.$emit("input", event.target.value);
},
},
};
</script>
The usage of those components could look something like this.
```html
<template>
<div id="app">
<InputField
v-model="name"
label="What is your name?"
type="text"
class="custom"
inputClass="input-custom"
/>
<p>{{ name }}</p>
</div>
</template>
<script>
import InputField from "./components/InputField";
export default {
name: "App",
components: {
InputField,
},
data() {
return {
name: "",
};
},
};
</script>
A demo is available here
https://codesandbox.io/s/vue-2-custom-input-field-4vldv?file=/src/components/InputField.vue
You need to use vue props . Like this:
child component:
<template>
<div>
<input :class="className" type="text" value="Test" v-bind="$attrs" />
</div>
</template>
<script>
export default {
props:{
className:{
type:String,
default:'',
}
}
};
</script>
Parent component:
<div id="app">
<InputField :class-name="'custom-class'" />
</div>
Since you're using vue2 the v-bind=$attrs is being hooked to the root element of your component the <div> tag. Check the docs. You can put this wrapper on the parent element if you need it or just get rid of it.
Here is a working example
Another approach
There is also the idea of taking the classes from the parent and passing it to the child component with a ref after the component is mounted
Parent Element:
<template>
<div id="app">
<InputField class="custom-class" ref="inputField" />
</div>
</template>
<script>
import InputField from "./components/InputField";
export default {
name: "App",
components: {
InputField,
},
mounted() {
const inputField = this.$refs.inputField;
const classes = inputField.$el.getAttribute("class");
inputField.setClasses(classes);
},
};
</script>
Child Element:
<template>
<div>
<input type="text" value="Test" :class="classes" />
</div>
</template>
<script>
export default {
data() {
return {
classes: "",
};
},
methods: {
setClasses: function (classes) {
this.classes = classes;
},
},
};
</script>
Here a working example
In Vue2, the child's root element receives the classes.
If you need to pass them to a specific element of your child component, you can read those classes from the root and set them to a specific element.
Example of a vue child component:
<template>
<div ref="root">
<img ref="img" v-bind="$attrs" v-on="$listeners" :class="classes"/>
</div>
</template>
export default {
data() {
return {
classes: null,
}
},
mounted() {
this.classes = this.$refs.root.getAttribute("class")
this.$refs.root.removeAttribute("class")
},
}
Pretty much it.

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: [],
};
},

Vue: How do you bind a value to an input through a wrapper component?

I know you can use v-model to bind a value to an input in the same component. How do you create a wrapper component for an input and bind a value to it?
Login.vue
<template>
<div id="Login">
<Input v-bind:value="email"/>
<Input v-bind:value="password"/>
</div>
</template>
<script>
import Input from './Input.vue'
import Button from './Button'
export default {
name: 'Login',
components: {
Input,
Button,
},
data: () => ({
email:'test',
password:'test',
}),
methods: {
login: () => { debugger; }, //this.email and this.password are still set to test
}
}
</script>
Input.vue
<template>
<div class="input>
<input v-model="value"/>
</div>
</template>
<script>
export default {
name: 'Input',
props: {
value: String,
},
}
</script>
Current set up results in
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
Is the only way to do this by emitting an event?
If I got it correctly, you can try to create transparent wrapper (in my case AppInput)
SomeParent.vue
<div>
<AppInput v-model="parentModel" />
</div>
AppInput.vue
<template>
<input
class="app-input"
v-bind="$attrs"
:value="value"
v-on="{
...$listeners,
input: event => $emit('input', event.target.value)
}">
</template>
<script>
export default {
name: "AppInput",
inheritAttrs: false,
props: ["value"]
};
</script>
one of articles
The best way is use v-model for wrapper and on/emit for input
<div id="Login">
<Input v-model="email"/>
<Input v-model="password"/>
</div>
...
<div class="input>
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</div>
You can implement v-model directly in the input component by doing so.
<template>
<div class="input>
<input :value="value" #input="$emit('input', $event.target.value)"/>
</div>
</template>
<script>
export default {
name: 'Input',
props: ["value"]
}
</script>
And then use it in your parent component like this:
<template>
<div id="Login">
<Input v-model="email"/>
<Input v-model="password"/>
</div>
</template>
<script>
import Input from './Input.vue'
import Button from './Button'
export default {
name: 'Login',
components: {
Input,
Button,
},
data: () => ({
email:'test',
password:'test',
}),
methods: {
login: () => { debugger; }, //this.email and this.password are still set to test
}
}
</script>
See here

How to pass data between components when using Vue-Router

How do I pass data from one component to another when using Vue router?
I'm building a simple CRUD app that have different components.
My conponents are:
App.vue - where I render the router-view
Contacts.vue - where I have the array of objects of contacts
ContactItem.vue - handle how the contact is displayed (gets contact as a prop from contact.vue
AddContact.vue - add new contact
EditContact.vue - edit selected contact
On the AddContact component, I have a form the user fills and then clicks on the submit button to add the form to the main component in Contacts.vue but when I emit an event and call it on the Contacts.vue component, it doesn't work. I get no output but from devtools I can see the event was triggered from AddContact.vue component.
Here is the Github link
<!-- App.vue -->
<template>
<div>
<Navbar />
<div class="container">
<router-view #add-contact="addContact" />
</div>
</div>
</template>
<script>
import Navbar from "./components/layout/Navbar";
export default {
components: {
Navbar
}
};
</script>
<!-- Contacts.vue -->
<template>
<div>
<div v-for="(contact) in contacts" :key="contact.id">
<ContactItem :contact="contact" />
</div>
</div>
</template>
<script>
import ContactItem from "./ContactItem";
export default {
components: {
ContactItem
},
data() {
return {
contacts: [
{
id: 1,
name: "John Doe",
email: "jdoe#gmail.com",
phone: "55-55-55"
},
{
id: 2,
name: "Karen Smith",
email: "karen#gmail.com",
phone: "222-222-222"
},
{
id: 3,
name: "Henry Johnson",
email: "henry#gmail.com",
phone: "099-099-099"
}
]
};
},
methods: {
addContact(newContact) {
console.log(newContact);
this.contacts = [...this.contacts, newContacts];
}
}
};
</script>
<!-- AddContact.vue -->
<template>
<div>
<div class="card mb-3">
<div class="card-header">Add Contact</div>
<div class="card-body">
<form #submit.prevent="addContact">
<TextInputGroup
label="Name"
name="name"
placeholder="Enter your name..."
v-model="name"
for="name"
/>
<TextInputGroup
type="email"
label="Email"
name="email"
placeholder="Enter your email..."
v-model="email"
/>
<TextInputGroup
type="phone"
label="Phone"
name="phone"
placeholder="Enter your phone number..."
v-model="phone"
/>
<input type="submit" value="Add Contact" class="btn btn-block btn-light" />
</form>
</div>
</div>
</div>
</template>
<script>
import TextInputGroup from "../layout/TextInputGroup";
export default {
components: {
TextInputGroup
},
data() {
return {
name: "",
email: "",
phone: ""
};
},
methods: {
addContact() {
const newContact = {
name: this.name,
email: this.email,
phone: this.phone
};
this.$emit("add-contact", newContact);
}
}
};
</script>
Well there are more ways to send data from component to component:
You could create a new Vue instance in your main.js file and call it eventBus
export const eventBus = new Vue();
After that you can import this bus wherever you need it:
import { eventBus } from "main.js"
Then you can send events over this global bus:
eventBus.$emit("add-contact", newContact);
At the other component you need to import this bus again and listen to this event in your "created" lifecycle:
created(){
eventBus.$on("add-contact", function (value){
console.log(value)
})
}
The other way is to store it centralized in vuex state. With "created" you can call this data. Created gets executed after your vue instance is created