VueJS how to push form params to vue router? - vue.js

I'm trying to create an edit form. How can I get the parameters from the url (vue-router) and pass them to the html form? And how can I push my form data to url?
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<form class="form-horizontal">
<!-- MultiSelect -->
<div class="form-group">
<label class="col-xs-2 col-md-1 control-label">User</label>
<div class="col-xs-10 col-md-3">
<multiselect
v-model="user"
:options="userList"
:searchable="true"
label="Name"
track-by="Name">
</multiselect>
</div>
</div>
<!-- MultiSelect -->
<div class="form-group">
<label class="col-xs-2 col-md-1 control-label">Team</label>
<div class="col-xs-10 col-md-3">
<multiselect
v-model="team"
:options="teamList"
:searchable="true"
label="Name"
track-by="Name">
</multiselect>
</div>
</div>
<!-- DatePicker -->
<div class="form-group">
<label class="col-xs-2 col-md-1 control-label">Birthday</label>
<div class="col-xs-10 col-md-3">
<date-picker
v-model="calendarDate"
:shortcuts="shortcuts"
:first-day-of-week="1"
appendToBody
></date-picker>
</div>
</div>
<!-- Button -->
<div class="form-group">
<label class="col-md-1 control-label"></label>
<div class="col-md-4">
<button #click="EditUser" class="btn btn-primary">Edit</button>
</div>
</div>
</form>
So I want to bind data between the models and vue-router. When I open edit.html#/user=5&team=3&bday=1991-01-10 appropriate multiselect fields must be filled. How to do it?

You can use query in your route URL:
Store all of your multiselect fields in a computed variable, and add a watcher to push the new parameters to your URL when there are any changes in the parameter:
computed: {
queryParams({ user, team, calendarDate }) {
return {
user: user,
team: team,
bday: calendarDate
}
}
},
watch: {
queryParams() {
this.$router.push( name: this.$route.name, query: this.queryParams })
}
}
When your page is created, use a function to get the query and set the form variables:
created() {
this.setQueryParams();
},
methods() {
setQueryParams() {
const query = this.$route.query;
this.user = query.user;
this.team = query.team;
this.calendarDate = query.bday;
// you might need to do some formatting for the birthday and do some checks to see if the parameter exists in the query so you don't get errors
}
}

Related

VeeValidate with Yup: input type="number" value is converted to string on submit

I use VeeValidate and Yup for form validation and don't know why my input field with type="number" is converted to string on submit.
When I input 78 and submit the form the output of the console.log in the onSubmit(values) function is the following:
values: {
prioritaet: "78"
}
Am I doing something wrong here or is this the normal behavior of VeeValidate and Yup? I would like to have a number instead of a string after submit.
My code looks like this:
<template>
<div class="container m-3">
<div class="row bg-primary align-items-center py-2">
<div class="col">
<h3 class="text-light mb-0">Format {{ this.action }}</h3>
</div>
<div class="col-auto">
<h5 class="text-light mb-0">{{ this.formatTitle }}</h5>
</div>
</div>
<Form #submit="onSubmit" :validation-schema="formatSchema" v-slot="{ errors }" ref="formatForm">
<div class="row mt-4">
<div class="col">
<h5>Formatdaten</h5>
</div>
</div>
<div class="row mb-2">
<div class="col-4">
<label for="prioritaet-input">Priorität: </label>
</div>
<div class="col">
<Field type="number" name="prioritaet" id="prioritaet-input" class="w-100" />
</div>
</div>
<div class="row justify-content-end">
<div class="col-auto me-auto">
<button class="btn btn-outline-primary">Änderungen übernehmen</button>
</div>
<div class="col-auto">
<button class="btn btn-outline-primary">Abbrechen</button>
</div>
<div class="col-auto">
<button type="sumbit" class="btn btn-outline-primary">Speichern</button>
</div>
</div>
<div class="row mt-4">
<template v-if="Object.keys(errors).length">
<span class="text-danger" v-for="(message, field) in errors" :key="field">{{ message }}</span>
</template>
</div>
</Form>
</div>
</template>
<script>
import { Form, Field } from "vee-validate";
import deLocale from "../assets/yup-localization.js";
import * as Yup from "yup";
import { markRaw } from "vue";
import { mapActions, mapState } from "vuex";
Yup.setLocale(deLocale);
export default {
name: "FormatBearbeitungsSchirm",
props: ["material_id"],
data() {
let action = "neu";
let formatTitle = "Format neu";
let formatSchema = markRaw(Yup.object().shape({
prioritaet: Yup.number().min(1).max(100).integer().label("Priorität"),
}));
return { formatSchema, action, formatTitle };
},
created() {
},
components: {
Form,
Field,
},
methods: {
onSubmit(values) {
console.log("values", values);
},
},
};
</script>
It looks like there is currently no support for specifying the .number modifier on the internal field model value of <Field>, so the emitted form values would always contain a string for number-type fields.
One workaround is to convert the value in the template, updating <Form>'s values slot prop in <Field>'s update:modelValue event:
<Form #submit="onSubmit" v-slot="{ values }">
<Field 👆
type="number"
name="prioritaet" 👇
#update:modelValue="values.prioritaet = Number(values.prioritaet)"
/>
<button>Submit</button>
</Form>
demo
Another simple workaround is to convert the property inside onSubmit before using it:
export default {
onSubmit(values) {
values.prioritaet = Number(values.prioritaet)
// use values here...
}
}
You must use the .number modifier.
You can read about it here
If you want user input to be automatically typecast as a Number, you can add the number modifier to your v-model managed inputs:
const app = new Vue({
el: "#app",
data: () => ({
mynumber1: undefined,
mynumber2: undefined
}),
methods: {
submit() {
console.log(typeof this.mynumber1, this.mynumber1)
console.log(typeof this.mynumber2, this.mynumber2)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14/dist/vue.js"></script>
<div id="app">
<form>
<!-- number modifier -->
<input type="number" v-model.number="mynumber1" placeholder="Type here" />
<!-- no modifier -->
<input type="number" v-model="mynumber2" placeholder="Type here" />
<input type="button" #click="submit" value="submit" />
</form>
</div>

Bind data with router-link in the vue

I have declared a data variable inside my component. There is a select box that dynamically changes this data value. what I want to use this data value inside my router-link. but somehow it is not working.
Here is my code for the same:
<template>
<div class="row bg-blue content-padding pdt-70 relative d-flex">
<div class="col-md-8">
<div class="row mgb-60">
<form>
<div class="col-8 form-group mgb-30">
<label for="work-profile" class="color-white">Work Profile*</label>
<select id="work-profile" v-model="page" name="work-profile" class="form-control">
<option value="self-employed">Self Employed</option>
<option value="salaried">Salaried</option>
</select>
</div>
<div class="col-12 form-group">
<router-link :to=["/"+page]>
<button type="submit" class="btn form-button button-blue d-flex-inline justify-content-center align-items-center color-white bg-blue">Get Started</button>
</router-link>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "LandingPage",
data: function()
{
return{
page:'salaried'
}
},
components: {
},
};
</script>
Replace <router-link :to=["/"+page]> to <router-link :to="'/'+page"> you do not need [] with binding to

Vue Conditional Classes from Prop Variable

So, currently, I have a set of radio buttons on my home.vue page and binding to a child component like so -
<div class="radio-toolbar prev-selector">
<input type="radio" id="one" value="Default" v-model="preview" />
<label for="one">Default</label>
<input type="radio" id="two" value="Padded" v-model="preview" />
<label for="two">Padded</label>
<input type="radio" id="three" value="Full" v-model="preview" />
<label for="three">Full</label>
<span>Picked: {{ preview }}</span>
</div>
<div class="media-wrapper">
<CardStyle
v-bind:card="this.preview"
/>
</div>
Which is then passing that "preview" data to the CardStyle Prop.
The prop is receiving it as ['card'] and I can use the data within my component.
However, am wondering how I can use the potential value of "this.preview" (which could be 'default', 'padded' or 'full') as a dynamic class to my child component without having to convert them to a true/false outcome and use -
:class="{ default: isDefault, padded: isPadded, full: isFull }"
(I have classes called .default, .padded and .full ready to use if it is as simple as passing the data in somehow.)
As long as this.preview has a value of the class name you want to use, just use :class="this.preview".
I built a couple of sample components that demonstrate how to solve your problem.
Parent.vue
<template>
<div class="parent">
<h5>Select Card Style</h5>
<div class="row">
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="radio" id="one" value="default" v-model="preview" />
<label class="form-check-label" for="one">Default</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" id="two" value="padded" v-model="preview" />
<label class="form-check-label" for="two">Padded</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" id="three" value="full" v-model="preview" />
<label class="form-check-label" for="three">Full</label>
</div>
</div>
</div>
<card-style :preview="preview" />
</div>
</template>
<script>
import CardStyle from './CardStyle.vue'
export default {
components: {
CardStyle
},
data() {
return {
preview: 'default'
}
}
}
</script>
CardStyle.vue
<template>
<div class="card-style">
<hr>
<div class="row">
<div class="col-md-6">
<span :class="preview">{{ preview }} Card Style</span>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
preview: {
type: String,
required: true
}
},
}
</script>
<style scoped>
.default {
background-color: burlywood;
}
.padded {
background-color:lightskyblue
}
.full {
background-color:lightcoral
}
</style>
You can build a computed variable for your field class in your component. This will allow you to select or define what classes to use based on your conditions or case.
computed: {
myclasses() {
return [
'class1',
'class2',
[this.condition ? 'class3' : 'class4'],
{ class5: this.othercondition },
];
},
},
Then, just use it your template:
<div :class="myclasses"></div>

Submit values from multiple components

I am using vuejs-wizard to create registration page, I have each tab in component like this
<form-wizard color="#fcab1a" title="" subtitle="" finish-button-text="Register">
<tab-content title="Personal Info" icon="icon-location3 fa-2x">
<personal-info></personal-info>
</tab-content>
<tab-content title="Contact Info" icon="icon-box fa-2x">
<contact-info></contact-info>
</tab-content>
<tab-content title="Address" icon="icon-alarm fa-2x">
<address></address>
</tab-content>
</form-wizard>
personal info:
<template>
<div class="card">
<div class="card-header">
<h5 class="card-title">Personal Info</h5>
</div>
<div class="card-body">
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Full Name <span class="text-danger">*</span></label>
<input type="text" value="" class="form-control" v-model="name" />
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Age <span class="text-danger">*</span></label>
<input type="number" value="" class="form-control" v-model="age" />
</div>
</div>
</div>
</div>
</div>
</template>
contact info:
<template>
<div class="card">
<div class="card-header">
<h5 class="card-title">Contact Info</h5>
</div>
<div class="card-body">
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Mobile <span class="text-danger">*</span></label>
<input type="text" value="" class="form-control" v-model="mobile" />
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Email <span class="text-danger">*</span></label>
<input
type="number"
value=""
class="form-control"
v-model="email"
/>
</div>
</div>
</div>
</div>
</div>
</template>
so my question is, what is the best way to submit the form, do I need vuex to store the state or is there a better/easier way ?
Thanks
It depends...
The answer depends on various factors. For example, are you using vuex already, size of the app, test-ability of app, and even how are fields get validated (asynch/api validations?).
When it's a simple app, and I only have direct parent=>child relationships, I tend to skip adding the Vuex as a dependency. (but I mostly deal with SPAs, so I usually use it) YMMV.
In this case, that would require that the fields are defined in the parent. Then adding props and emitters for each value to the children, and a listener on the parent. As you start to add more fields though, you might find this tedious, and opt to pass fields in an object either in groups, or as whole (and only pick the ones you need in tab), and then you can implement your own v-model in the tab components which can make it pretty easy to pass the object around
If you're using a more recent Vue version (2.6+), you could use vue.observable to
share a store between multiple components without the bells/whistles of Vuex
There's a good article that shows how to build a vuex clone with it, but in reality it's much, much simpler than that to create a store that would suit your needs. Let me know in the comments if you're interested in how to implement it, and I can describe it.
Rolling with custom store
it's really as simple as this
Create a store.js file with...
import Vue from 'vue';
const store = Vue.observable({
name: null,
email: null,
age: null,
mobile: null,
});
export default store;
then in any component that you want to have access to it, add the store during create
import store from "../store";
export default {
name: "PersonalInfo",
created() {
this.$store = store;
}
};
now the all the store is available to you in the template through $store
<input type="text" value class="form-control" v-model="$store.name">
codesandbox example
You lose the benefits, such as "time-traveling" over mutations that Vuex offers. Because you're not dealing with the scale that the flux pattern (vuex) was meant for, I would use this solution in an app this size.
Ya you should just use vuex, it combines like data so that it isn't spread out across multiple files. If you are going to be sending this information back to your backend, it makes it easier to have most backend connections in one place. without the wizard thing I redid your code with a store like this. Note the computed property instead of using data. By doing it as a computed property you don't have to write any code to change the variables stored inside of the store.
Personal.vue
<template>
<div class="card">
<div class="card-header">
<h5 class="card-title">Personal Info</h5>
</div>
<div class="card-body">
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Full Name <span class="text-danger">*</span></label>
<input
type="text"
value=""
class="form-control"
v-model="register.name"
/>
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Age <span class="text-danger">*</span></label>
<input
type="number"
value=""
class="form-control"
v-model="register.age"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
register() {
return this.$store.state.register;
},
},
};
</script>
Contact.vue
<template>
<div class="card">
<div class="card-header">
<h5 class="card-title">Contact Info</h5>
</div>
<div class="card-body">
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Mobile <span class="text-danger">*</span></label>
<input
type="text"
value=""
class="form-control"
v-model="register.mobile"
/>
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Email <span class="text-danger">*</span></label>
<input
type="number"
value=""
class="form-control"
v-model="register.email"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
register() {
return this.$store.state.register;
},
},
methods: {
submit() {
this.$store.dispatch("register", {
person: this.register,
});
},
},
};
</script>
<style></style>
store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
register: {},
},
actions: {
// register({commit}, data){
//put in some stuff here.
//},
},
});
If you decide to go the store route, all you have to do is this
1. npm install vuex
2. add a folder inside of your src folder called store
3. add a file named index.js
4. go to main.js and add this line"import store from "./store";"
5. where it says new "Vue({" add "store" this will register it.
Vuex is super easy and makes life way easier as your project gets bigger.
The .sync modifier provides two way binding pattern to props, you can read about it here https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier.
In the parent component you can use the .sync modifier this way:
<ChildComponent :name.sync="parentNameProperty" />
...
data: () => ({ parentNameProperty: '' }),
...
Then in the child component you receive name as prop and you can emit an event to update the value in the parent component, by using watch or a method:
...
props: {
name: {
type: String,
default: ''
}
}
...
this.$emit(update:parentNameProperty, newValue)
...
Vuex is a great way to handle state, but is fine to use the above pattern for small applications.

Disabled submit button, but valid form data will not enable it (vee-validate)

I'm attempting to disable my form's submit button until valid data is entered for each of the controls.
After entering correct data for each of the fields, the submit button remains disabled.
Markup:
<div id="app">
<form action='#' method='POST'>
<div class="columns">
<div class="column">
<div class="field">
<div class="control">
<label class="label">Last Name</label>
<input v-validate="'required'" :class="{'input': true, 'is-danger': errors.has('lastname') }" class="input" name="lastname" type="text">
<span v-show="errors.has('lastname')" class="help is-danger">{{ errors.first('lastname') }}</span>
</div>
</div>
</div>
<div class="column">
<div class="field">
<div class="control">
<label class="label">Desk Number</label>
<input v-validate="'required'" :class="{'input': true, 'is-danger': errors.has('desknum') }" class="input" name="desknum" type="text">
<span v-show="errors.has('desknum')" class="help is-danger">{{ errors.first('desknum') }}</span>
</div>
</div>
</div>
</div>
<button :disabled='!isComplete' class="button is-link" name='submit_data' value='go'>Submit</button>
</form>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vee-validate/2.0.9/vee-validate.min.js">
<script>
Vue.use(VeeValidate);
new Vue({
el: "#app",
template: '#app',
data() {
return {
p_pat_name_first: null,
p_pat_name_last: null,
p_pat_date_birth: null,
p_pat_gender: null,
p_pat_twin: null,
p_emory_id: null,
p_mother_name_first: null,
p_mother_name_last: null,
p_parent_phone_primary: null,
};
},
computed: {
isComplete() {
return
this.p_pat_name_first &&
this.p_pat_name_last &&
this.p_pat_date_birth &&
this.p_pat_gender &&
this.p_pat_twin &&
this.p_mother_name_first &&
this.p_mother_name_last &&
this.p_parent_phone_primary;
}
}
});
</script>
Fiddle
What am I doing wrong that is not allowing the Submit button to enable itself when the form is complete and valid?
Well, simply put, your condition in isComplete() refers to values in your data that have no bearing on the form. None of the fields in your form have a v-model, so changing them has no effect on the variables referenced in inComplete().
The normal way in vee-validate to check if any fields are not valid is like this:
<button :disabled="errors.any()" type="submit">Submit</button>
That will only disable the Submit button after the form becomes invalid, so on page load it will look enabled until the user does something to the form that makes it invalid.
See a working example here (with one input having v-model set): https://jsfiddle.net/ryleyb/v7a2tzjp/8/