Save selected values of input despite switching between two components in VUEJS - vue.js

So I have two components that are imported into my app.vue:
<script>
import Leaderboard from "./components/Comp1.vue";
import Search from "./components/Comp2.vue";
export default {
name: "App",
components: {
Comp1,
Comp2,
},
}
These components are called, when I click on the corresponding button. This all works fine.
But in the components I have some input fields such as in Comp1.vue:
<template>
<div>
<select
class="form-select"
name="event"
id=""
v-model="selectedEvent"
>
<option value="">Please choose an event:</option>
<option v-for="event in eventsList" :key="event">
{{ event }}
</option>
</select>
</div>
</template>
<script>
data: function () {
return {
selectedEvent: "",
</script>
Here I can choose, which event to watch. But after switching to Comp2 and then again choosing Comp1, the selectedEvent is empty. Obviously, because its defined empty in data.
Is there any way to store the selected value in a session variable or would you prefer a different technique?
UI looks like this:

You can maintain an Object in your parent which you can pass as props to a props and then have a two way handshake
<Leaderboard :formInputs="formInputs"></Leaderboard>
<script>
import Leaderboard from "./components/Comp1.vue";
import Search from "./components/Comp2.vue";
export default {
name: "App",
components: {
Comp1,
Comp2,
},
data() {
return {
formInputs: {
compOneInput: '',
compTwpInput: ''
}
},
methods: {
updateData(payload) {
this.formInputs[payload.key] = payload.value;
}
}
and then pass this formInputs to your child Component from where you
you can emit the change whenever you update the input inside that
<template>
<div>
<select
class="form-select"
name="event"
id=""
v-model="selectedEvent"
>
<option value="">Please choose an event:</option>
<option v-for="event in eventsList" :key="event">
{{ event }}
</option>
</select>
</div>
</template>
<script>
export default {
data: function () {
return {
selectedEvent: this.formInputs.compOneInput ? this.formInputs.compOneInput : '',
}
},
watch: {
formInputs(newVal) {
this.selectedEvent = newVal.compOneInput;
},
selectedEvent(newVal, oldVal) {
if(newVal !== oldVal) {
this.$emit('updateData', {key: compOneInput, value: this.selectedEvent});
}
}
}
props: {
formInputs: Object
}
}
</script>
Using the above example for component one , you can implement the same for component two also

you can add a watcher on selectedEvent then store the data in vuex store

Related

I'm emiting a value from the watcher but this value is not received in the parent component Vue 3 Select component

I have a Custom select component which emits a value in the watch method but when I have to get that value, is not shown in the parent component. (VUE 3)
this is my select component:
<template>
<div class="input-group">
<label class="label">{{ label }}</label>
<select v-model="select">
<option v-for="option in options" :value="option.id" :key="option.id">
{{ option.name }}
</option>
<option :value="select" disabled hidden>Select...</option>
</select>
</div>
</template>
<script lang="ts">
import { ref, watch, getCurrentInstance } from "vue";
export default {
name: "Select",
props: {
options: {
type: Array,
required: true,
},
select: {
type: String,
},
label: {
type: String,
},
},
setup(props) {
const { emit } = getCurrentInstance();
const select = ref(props.value);
watch(select, (value) => {
emit("input", value);
});
return {
select,
};
},
};
</script>
This is the implementation in the parent component
<template>
<Select
label="Group Name"
:options="questions_groups"
v-model="groupName"
/>
<template>
<script>
......
.....
const groupName = ref(null);
<script/>
I need to know why I cannot get the value od the v-model variable in the implementation
for vue3 (because it supports multiple models the event name changed) you need to use update:modelValue
watch(select, (value) => {
emit("update:modelValue", value);
})
for clarity...
modelValue is the default model's name, so
<input v-model="searchText" /> is equivalent to <input v-model:modelValue="searchText" />
having multiple models allows use such as:
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
more info at https://vuejs.org/guide/components/v-model.html#component-v-model

VUE, Can't use selected option value in a select component

Im trying to use a selected option value. Can't show the value or save it.
This is my child component
`
<script>
export default {
props: {
options : {
type:Array,
},
selectOpt:undefined,
}
emits : ['input','change','option:selected']
}
</script>
<template>
<div>
<h1>
Hi, I'm a component
</h1>
<select
v-model="selectOpt"
#change="$emit('input', event.target.value)">
<option v-for="option in options"
:key="option"
>{{option}}</option>
</select>
</div>
</template>
`
This is my parent
`
<script >
import Comp from './Comp.vue'
export default {
data() {
return {
options : [1,2,3,4,5,6],
optSelected : undefined,
}
},
components: {
Comp
}
}
</script>
<template>
<Comp v-model="optSelected" :options="options"></Comp>
<p>
--->{{optSelected}}
</p>
</template>
`
I tried changin the 'input' event and 'change' event. not sure what im doing wrong.
i've found a solution that requires a vue-select library that i prefer not to use.
It's a simple detail: in vue 3, you need to use update:modelValue in order to change the v-model in parent component. (Reference: https://v3-migration.vuejs.org/breaking-changes/v-model.html)
And another thing: you souldn't use the prop as a v-model to prevent side effects in your application. You can read more about it here: https://eslint.vuejs.org/rules/no-mutating-props.html
Hope it helps:
<script>
export default {
props: {
options: {
type: Array
},
modelValue: undefined
},
emits: ['update:modelValue'],
watch: {
innerValue(newValue) {
this.$emit('update:modelValue', newValue)
},
modelValue(newValue) {
this.innerValue = newValue;
}
},
data() {
return {
innerValue: this.modelValue
};
}
};
</script>
<template>
<div>
<h1>Hi, I'm a component</h1>
<select v-model="innerValue">
<option v-for="option in options" :key="option">
{{ option }}
</option>
</select>
</div>
</template>
[Edit] Using Fallthrough Attribute:
You can use the v-bind="$atrrs":
<script>
export default {
props: {
options: {
type: Array
},
},
};
</script>
<template>
<div>
<h1>Hi, I'm a component</h1>
<select v-bind="$attrs">
<option v-for="option in options" :key="option">
{{ option }}
</option>
</select>
</div>
</template>
Read more: https://vuejs.org/guide/components/attrs.html#attribute-inheritance-on-multiple-root-nodes

Unable to register custom component globally with vue.js

I have made selectbox component and wants to reuse it in other components. Si I want to register that component globally. I have imported that component in main.js but doesnot works.
main.js
import Selectbox from "#/modules/Selectbox.vue";
Vue.component("Selectbox", Selectbox);
Selectbox.vue
<template>
<div>
<label>{{ label }}</label>
<select #change="$emit('input', $event.target.value)">
<option
v-for="opt in options"
:key="opt.value"
:value="opt.value"
:selected="value === opt.value"
>
{{ errorMessage }}
{{ opt.label || "No label" }}
</option>
</select>
</div>
</template>
<script>
export default {
props: {
label: {
type: String,
required: true
},
},
data() {
return {
errorMessage: "",
option: "lorem",
options: [
{ label: "lorem", value: "lorem" },
{ label: "ipsum", value: "ipsum" }
]
};
},
};
</script>
Test.vue
<template>
<div>
<Selectbox v-model="ward_no"/>
</div>
</template>
<script>
export default {
data() {
return {
ward_no: '',
};
}
}
</script>
There is nothing wrong in the way that you are trying to register global component but you are missing script tag.
UPDATE: After talking to #prabinasht on skype and reviewing her code, I saw that in multiple files she forgot to remove locally imported/registered component and at the same time the component was registered globally too, so that was the problem.
Register the component this way
Vue.component('Selectbox', require('#/modules/Selectbox.vue').default)

how to retrieve and manipulate data from Vuex store

I try to make door configurator using Vue.
First of all I put array of components from DB into Vuex store .get('/api/furnitura')
This gives me array with all components available for different type of doors.
Hinges, handles, locks etc.
And then I am going to have few Vue components. Each component is one configurator of specific door type.
What I found in Vue documentation is that it is possible to access Vuex store variables from each point of the application like that
computed: {
getFurnitura(){
return this.$store.state.furnitura.all
}
},
But I want to manipulate store variables in Vue.
Something like that
Take all from this.$store.state.furnitura.all where type=SOMETYPE and type=ANOTHERTYPE
For one calculator I want retrieve from this.$store.state.furnitura.all only few type of hinges and handles. For another something different.
Then I want to use them in select fields. For example in DB I have about 50 hinges in DB and for one type of door I need to retrieve only 5, for another only 3 and so on.
Having API call for every select field seems not reasonable for me
<select name="hinge_selected" v-model="hinge_selected">
<option v-for="option in hinges" :value="option">
{{ option.name }}
</option>
</select>
<select name="handle_selected" v-model="handle_selected">
<option v-for="option in handles" :value="option">
{{ option.name }}
</option>
</select>
and so on
Here is my code now
resources\js\app.js
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
window.Vue = require('vue');
import store from './store';
Vue.component('modalform', require('./components/modalform.vue').default);
Vue.component('calc-dush-door', require('./components/calculators/dush-door.vue').default);
Vue.component('calc-interior-door', require('./components/calculators/interior-door.vue').default);
Vue.component('calc-sliding-door', require('./components/calculators/sliding-door.vue').default);
const app = new Vue({
el: '#app',
store,
data: { },
directives: { },
computed: {
getFurnitura(){
return this.$store.state.furnitura.all
}
},
methods: { },
mounted() {
console.log("Vue ROOT instance mounted");
this.$store.dispatch('furnitura/getFurnitura');
}
});
resources\js\store\index.js
import Vue from 'vue';
import Vuex from 'vuex';
import furnitura from './modules/glass';
import furnitura from './modules/furnitura';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
glass,
furnitura
}
});
resources\js\store\modules\furnitura.js
import axios from 'axios';
const state = {
all: []
};
const getters = { };
const mutations = {
SET_FURNITURA (state, furnitura) {
state.all = furnitura;
}
};
const actions = {
getFurnitura (context) {
axios
.get('/api/furnitura') // this gives me result of Furnitura::all();
.then(response => {
context.commit('SET_FURNITURA', response.data.records)
});
}
};
export default {
namespaced: true,
state,
getters,
mutations,
actions
};
resources\js\store\components\calculators\sliding-door.vue
<template>
<div>
<select name="hinge_selected" v-model="hinge_selected">
<option v-for="option in hinges" :value="option">
{{ option.name }}
</option>
</select>
<select name="handle_selected" v-model="handle_selected">
<option v-for="option in handles" :value="option">
{{ option.name }}
</option>
</select>
</div>
</template>
<script>
export default {
name: "SlidingDoorCalc",
data: function () {
return {
width: 600,
height: 2000,
hinge_selected: [],
handle_selected: [],
}
},
computed: {
getFurnitura(){
return this.$store.state.furnitura.all
}
},
methods: { },
mounted() { },
}
</script>
You can use regular component methods, as in:
{
// ...
methods: {
getHinges(filterParameter1, filterParameter2) {
// do your filtering and sorting on
// this.$store.state.furnitura.all
},
getHandles(filterParameter1) {
// same as with getHinges
}
}
}
Then, in your component, you call it as:
<select name="hinge_selected" v-model="hinge_selected">
<option v-for="option in getHinges('some_parameter')" :value="option">
{{ option.name }}
</option>
</select>
If you want to use the same filtering logic in multiple methods, use method-style-access Vuex getters, as in:
getters: {
// ...
getHinges: (state) => (filterParameter1, filterParameter2) => {
// filter your state.furnitura.all here
}
}
and call such getters in your component methods:
{
// ...
methods: {
getHinges(filterParameter1, filterParameter2) {
return this.$store.getters.getHinges(filterParameter1, filterParameter2);
},
}
}

VueJs reactivity with parent component property object

I'm having difficulty to get parent component's property object, with dynamically populated properties to make the values available inside of the same component.
A bit hard to explain, so please have a look at the example below:
Parent Component
<script>
export default {
data() {
return {
fields: {},
}
}
}
</script>
Child Component
<template>
<select
#change="update()"
v-model="field"
>
<option
v-for="option in options"
:value="option.value"
>
{{ option.name }}
</option>
</select>
</template>
<script>
export default {
props: {
initialOptions: {
type: Array,
required: true
}
},
data() {
return {
field: '',
options: this.initialOptions
}
},
mounted() {
if (
(this.field === undefined || this.field === '') &&
this.options.length > 0
) {
this.field = this.options[0].value;
}
this.update();
},
methods: {
update() {
this.$emit('input', this.field);
}
}
}
</script>
DOM
<parent-component inline-template>
<div>
<child-component>
:initial-options="[{..}, {..}]"
v-model="fields.type_id"
></child-component>
</div>
<div :class="{ dn : fields.type_id == 2 }">
// ...
</div>
</parent-component>
Using Vue console I can see that fields object gets all of the child component models with their associated values as they emit input when they are mounted, however for some strange reason the :class="{ dn : fields.type_id == 2 }" does not append the class dn when the selection changes to 2. Dom doesn't seem to reflect the changes that are synced between parent and child components.
Any help on how to make it work?
Here is what I was trying to get at in comments. Vue cannot detect changes to properties that are added dynamically to an object unless you add them using $set. Your fields object does not have a type_id property, but it gets added because you are using v-model="fields.type_id". As such, Vue does not know when it changes.
Here, I have added it and the color of the text changes as you would expect.
console.clear()
Vue.component("child-component", {
template: `
<select
#change="update()"
v-model="field"
>
<option
v-for="option in options"
:value="option.value"
>
{{ option.name }}
</option>
</select>
`,
props: {
initialOptions: {
type: Array,
required: true
}
},
data() {
return {
field: '',
options: this.initialOptions
}
},
mounted() {
if (
(this.field === undefined || this.field === '') &&
this.options.length > 0
) {
this.field = this.options[0].value;
}
this.update();
},
methods: {
update() {
this.$emit('input', this.field);
}
}
})
new Vue({
el: "#app",
data: {
fields: {
type_id: null
}
}
})
.dn {
color: red;
}
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<div>
<child-component :initial-options="[{name: 'test', value: 1}, {name: 'test2', value: 2}]" v-model="fields.type_id"></child-component>
</div>
<div :class="{ dn : fields.type_id == 2 }">
Stuff
</div>
</div>
It looks like you are trying to make a re-usable component.
I would ask myself what the value of a re-usable component is when the parent component has to handle more than half of the effort. The component might be better named...
<DifficultToUseSelect/>.
Essentially, you are creating a component that provides, all by itself, all of the following HTML...
<select></select>
Everything else is managed by the parent component.
It would probably be more useful to do any of the following...
Encapsulate often needed options in a specific select component, as in
StateAbbrevsSelect v-model="state"
Pass the name of a data model to a select component. The component would then load and manage its own data via the model.
Pass the URL of a web service to the component, which it then calls to load its options.
Again, the main point I am trying to convey here is that making a re-usable component where more than half of the effort is handled by the parent component is really not very re-usable.