I got these two radio buttons in my page:
<input type="radio" :id="DIGITAL_INVOICE_DIGITAL" name="digitalInvoice" :value="false" v-model="digitalInvoice"/>
<input type="radio" :id="DIGITAL_INVOICE_PAPER" name="digitalInvoice" :value="false" v-model="digitalInvoice"/>
They are not part of <base-radio-list> and I don't want them to be.
As part of making user choice effective, both of them are not selected in the first time, thus, there need to be a validation to check if the user chose or not.
I want to make this validation using vee-validate, how can I make it ?
Below you can find validation using ValidationProvider from vee-validate.
You can work on top of this codesandbox.
Let me know how it goes.
Test.Vue
<template>
<div class="columns is-multiline">
<div class="column is-6">
<p class="control">
<ValidationProvider
ref="provider"
rules="oneOf:1,2"
v-slot="{ errors }"
>
<label class="radio">
<input
name="digitalInvoice"
value="1"
type="radio"
v-model="digitalInvoice"
/>
Yes
</label>
<label class="radio">
<input
name="digitalInvoice"
value="2"
type="radio"
v-model="digitalInvoice"
/>
No
</label>
<br />
<p class="control">
<b>{{ errors[0] }}</b>
</p>
</ValidationProvider>
<br />
</p>
</div>
</div>
</template>
<script>
import { ValidationProvider, extend } from "vee-validate";
import { oneOf } from "vee-validate/dist/rules";
extend("oneOf", {
...oneOf,
message: "Choose one",
});
export default {
name: "radio-buttons-test",
components: {
ValidationProvider,
},
data() {
return {
digitalInvoice: false,
};
},
mounted() {
this.$refs.provider.validate();
},
};
</script>
App.vue
<template>
<div id="app">
<Test />
</div>
</template>
<script>
import Test from "./components/Test";
export default {
name: "App",
components: {
Test,
},
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
In Vue, the value of the input and the value of the data object are the same.
For the input radios that are more than one element with the same v-model, the name attribute is ignored and the value stored in the data will be equal to the value attribute of the currently selected radio input.
<input type="radio" :id="DIGITAL_INVOICE_DIGITAL" :value="value1" v-model="digitalInvoice"/>
<input type="radio" :id="DIGITAL_INVOICE_DIGITAL" :value="value2" v-model="digitalInvoice"/>
and your vue data is:
data: {
digitalInvoice: "value1"
}
for unchecked try:
data: {
digitalInvoice: ""
}
Related
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>
I really hit a wall with this issue. For an unknown reason (to me) Vuelidate doesn't validate the password field. No matter what you enter it is always invalid and I am not sure why. I tried different solutions but I always return that password is invalid. Does someone know where is the issue?
Here is the code that I am using:
<template>
<form
#submit="checkForm"
:action="this.action"
method="post"
novalidate="true"
>
<div class="app-form-wrapper mb-3 mb-md-0 pt-3 pb-1 py-md-0 rounded">
<!-- Email address -->
<div
class="form-group mb-3"
:class="{ 'form-error': $v.user.email.$error }"
>
<label for="email_login">Email address</label>
<input
type="email"
name="email"
class="form-control py-2 h-auto"
id="email_login"
ref="email"
placeholder="Your email"
aria-describedby="email_error"
v-model.trim.lazy="$v.user.email.$model"
autocomplete="off"
autofocus
/>
<div v-if="$v.user.email.$error">
<div
class="error mt-1"
id="email_error"
aria-live="assertive"
v-if="!$v.user.email.required"
>
Email address is required
</div>
</div>
</div>
<!-- Password -->
<div
class="form-group position-relative mb-0 mb-md-3"
:class="{ 'form-error': $v.user.password.$error }"
>
<div class="d-flex justify-content-between">
<label for="password_login">Password</label>
<a :href="this.forgotUrl" class=" mr-3 mr-md-0 text-muted"
><u>Forgot your password?</u></a
>
</div>
<input
:type="passwordFieldType"
name="password"
class="form-control py-2 h-auto"
id="password_login"
ref="password"
placeholder="Password"
aria-describedby="password_error"
v-model.trim.lazy="$v.user.password.$model"
autocomplete="off"
/>
<button
type="button"
id="switchVisibility"
class="position-absolute"
#click="switchVisibility"
></button>
<div v-if="$v.user.password.$error">
<div
class="error mt-1"
id="password_error"
aria-live="assertive"
v-if="$v.user.password.$dirty && !$v.user.password.required"
>
Password is required
</div>
</div>
</div>
</div>
<!-- CSRF -->
<input type="hidden" name="_token" :value="csrf" />
<!-- Submit -->
<button
class="btn btn-primary btn-lg btn-block"
type="submit"
id="loginBtn"
:disabled="submitStatus === 'PENDING'"
>
Login
</button>
<div class="text-center mt-2">
Don't have an account yet? Sign up here!
</div>
<div
class="bottom-validation--ok mt-2 text-center"
v-if="submitStatus === 'OK'"
>
Thanks for logging in!
</div>
<div
class="bottom-validation--error mt-2 text-center"
v-if="submitStatus === 'ERROR'"
>
Please fill the form correctly.
</div>
<div
class="bottom-validation--sending mt-2 text-center"
v-if="submitStatus === 'PENDING'"
>
Sending...
</div>
<div class="bottom-validation--error mt-2 text-center" v-if="errors">
<div v-for="value in errors">
{{ value }}
</div>
</div>
<hr />
<a
:href="googleUrl"
role="button"
id="btn-google"
class="btn btn-social btn-white btn-block border"
>
<svg
version="1.1"
id="google"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 533.5 544.3"
style="enable-background: new 0 0 533.5 544.3"
xml:space="preserve"
class="mr-2"
>
<path
class="st0"
d="M533.5,278.4c0-18.5-1.5-37.1-4.7-55.3H272.1v104.8h147c-6.1,33.8-25.7,63.7-54.4,82.7v68h87.7
C503.9,431.2,533.5,361.2,533.5,278.4L533.5,278.4z"
/>
<path
class="st1"
d="M272.1,544.3c73.4,0,135.3-24.1,180.4-65.7l-87.7-68c-24.4,16.6-55.9,26-92.6,26c-71,0-131.2-47.9-152.8-112.3
H28.9v70.1C75.1,486.3,169.2,544.3,272.1,544.3z"
/>
<path
class="st2"
d="M119.3,324.3c-11.4-33.8-11.4-70.4,0-104.2V150H28.9c-38.6,76.9-38.6,167.5,0,244.4L119.3,324.3z"
/>
<path
class="st3"
d="M272.1,107.7c38.8-0.6,76.3,14,104.4,40.8l77.7-77.7C405,24.6,339.7-0.8,272.1,0C169.2,0,75.1,58,28.9,150
l90.4,70.1C140.8,155.6,201.1,107.7,272.1,107.7z"
/>
</svg>
<span>Log in with Google</span>
</a>
<a
:href="facebookUrl"
role="button"
id="btn-facebook"
class="btn btn-social btn-facebook btn-block shadow-sm border"
>
<svg
id="facebook"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="mr-2"
>
<path
class="st0"
d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z"
/>
</svg>
<span>Log in with Facebook</span>
</a>
</form>
</template>
<script>
import Vuelidate from "vuelidate";
import { required } from "vuelidate/lib/validators";
//import './useragent.js';
Vue.use(Vuelidate);
export default {
props: {
action: String,
old: Object,
forgotUrl: String,
errors: Array,
facebookUrl: String,
googleUrl: String
},
data() {
return {
user: {
email: "",
password: ""
},
passwordFieldType: "password",
csrf: document
.querySelector('meta[name="csrf-token"]')
.getAttribute("content"),
submitStatus: null
};
},
validations: {
user: {
email: {
required
},
password: {
required
}
}
},
methods: {
checkForm(e) {
this.$v.user.$touch();
// For accessibility: focusing on the field with an error
if (this.$v.user.$invalid) {
e.preventDefault();
for (let key in Object.keys(this.$v.user)) {
const input = Object.keys(this.$v.user)[key];
if (input.includes("$")) return false;
if (this.$v.user[input].$error) {
this.$refs[input].focus();
break;
}
}
this.submitStatus = "ERROR";
} else {
return true;
}
},
switchVisibility() {
this.passwordFieldType =
this.passwordFieldType === "password" ? "text" : "password";
}
},
created() {
this.user = this.old;
}
};
</script>
<style scoped>
.form-error input {
border-color: #c91547;
}
.error,
.bottom-validation--error {
color: #c91547;
}
#switchVisibility {
bottom: 10px;
right: 10px;
background-color: transparent;
border: none;
opacity: 0.4;
font-size: 14px;
font-weight: 600;
}
#switchVisibility:focus,
#switchVisibility:active,
#switchVisibility:hover {
box-shadow: none;
outline: none;
}
input[type="password"] + button:before {
content: "Show";
}
input[type="text"] + button:before {
content: "Hide";
}
</style>
Try below steps it will help you to fix the issue.
Step 1: You forgot to import email from vuelidate/lib/validators
import { required, email } from 'vuelidate/lib/validators'
Step 2:
validations: {
user: {
email: { required, email },
password: { required }
}
},
Step 3: Do the validation on button click
checkForm(e) {
this.$v.$touch();
if (this.$v.$invalid) {
return // stop here if form is invalid
}
},
Step 4: Display error message in your html template
<div class="form-group mb-3" :class="{ 'form-error': $v.user.email.$error }">
<label for="email_login">Email address</label>
<input
type="email"
name="email"
class="form-control py-2 h-auto"
id="email_login"
ref="email"
placeholder="Your email"
aria-describedby="email_error"
v-model.trim.lazy="$v.user.email.$model"
autocomplete="off"
autofocus />
<div v-if="!$v.user.email.required" class="error mt-1" id="email_error" aria-live="assertive">
Email address is required
</div>
<div v-if="!$v.user.email.email" class="error mt-1" id="email_error" aria-live="assertive">
Invalid Email address
</div>
</div>
You can checkout for Vue,Vuex,Vuelidate,translation on Vue-vuex-vuelidate-i18n-registration-login-todo
I've written a small component to abstract some repeated HTML:
<template>
<div class="form-group">
<label for="desc">{{label}}</label>
<input id="desc" readonly type="text" class="form-control input-sm"
v-model={{v_model}}/>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
label: String,
v_model: String
}
}
</script>
<style scoped>
</style>
This gives the error:
Error compiling template:
<div class="form-group">
<label for="desc">{{label}}</label>
<input id="desc" readonly type="text" class="form-control input-sm"
v-model={{v_model}}/> </div>
- invalid expression: Unexpected token { in
{{v_model}}/
Raw expression: v-model="{{v_model}}/"
I'm expecting that things assigned to v-... attributes will need to be done in some other way, but I don't yet know how.
Solution was:
<template>
<div class="form-group">
<label for="desc">{{label}}</label>
<input id="desc" type="text" class="form-control input-sm"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
/>
</div>
</template>
<script>
export default {
name: 'FormGroup',
props: {
label: String,
value: String
},
data: function() {
return {
}
}
}
</script>
<style scoped>
</style>
I am new to Vue but I think that this can be really useful.
I have my first problem this simplified example
var appvue = new Vue({
el: '#appvue',
data: {
selectedProd: []
}
});
body {
background: #20262E;
}
.form-row{
width:24%;
display:inline-block;
}
#appvue {
background: #fff;
padding:10px;
}
#prod_adjust{
margin-top:20px;
background: #eee
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div class="prod-wrap" id="appvue">
<div class="form-row">
<input v-model="selectedProd" name="product-1260" id="product-1260" type="checkbox" value="1260">
<label for="product-1260">
<div class="thumb">
<img src="https://picsum.photos/100/100">
</div>
<div class="details">
<span class="brand">Brand 1</span>
<span class="product-title">Product 1</span>
</div>
</label>
</div>
<div class="form-row">
<input v-model="selectedProd" name="product-1261" id="product-1261" type="checkbox" value="1261">
<label for="product-1261">
<div class="thumb">
<img src="https://picsum.photos/100/100">
</div>
<div class="details">
<span class="brand">Brand 2</span>
<span class="product-title">Product 2</span>
</div>
</label>
</div>
<div class="form-row">
<input v-model="selectedProd" name="product-1263" id="product-1263" type="checkbox" value="1263">
<label for="product-1263">
<div class="thumb">
<img src="https://picsum.photos/100/100">
</div>
<div class="details">
<span class="brand">Brand 3</span>
<span class="product-title">Product 3</span>
</div>
</label>
</div>
<div class="form-row">
<input v-model="selectedProd" name="product-1264" id="product-1264" type="checkbox" value="1264">
<label for="product-1264">
<div class="thumb">
<img src="https://picsum.photos/100/100">
</div>
<div class="details">
<span class="brand">Brand 4</span>
<span class="product-title">Product 4</span>
</div>
</label>
</div>
<div id="prod_adjust">
<p v-for="product in selectedProd">{{product}}</p>
</div>
</div>
I have a lot of products like the checkboxes above. I need to have the list of checked items in another place.
I did this with v-model and v-for - but my main problem is now that there are sending only the value from the checkboxes - I also need img-src, brand, product-title - all these parameters I also can have more attributes in an input.
But how I can pass them in data: { selectedProd:[]} with Vue?
Or I do I need to create a separate JS function which will collect all this data and send it to the array selectedProd?
Thank you in advance
You would want to create an object with the data you need for the product. Then you can use v-for to loop through and display them the way you'd like.
In the example, I included a computed function that automatically returns the ones that get checked. This is done by v-model binding the checkbox to a flag inside your object. In this example, I called it selected, for simplicity.
var appvue = new Vue({
el: '#appvue',
data: {
products: [{
id: 1260,
brand: "Brand A",
product: "Bubblegun",
image: "https://picsum.photos/100/100",
selected: false
}, {
id: 1261,
brand: "Brand B",
product: "Bubblegum",
image: "https://picsum.photos/100/100",
selected: false
}],
},
computed: {
selectedProd() {
return this.products.map(product => {
if (product.selected) return product
})
}
}
})
body {
background: #20262E;
}
.form-row {
width: 24%;
display: inline-block;
}
#appvue {
background: #fff;
padding: 10px;
}
#prod_adjust {
margin-top: 20px;
background: #eee
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.min.js"></script>
<div class="prod-wrap" id="appvue">
<div class="form-row" v-for="product in products">
<input v-model="product.selected" name="product-1260" id="product-1260" type="checkbox">
<label for="product-1260">
<div class="thumb">
<img :src="product.image">
</div>
<div class="details">
<span class="brand">{{product.brand}}</span>
<span class="product-title">{{product.product}}</span>
</div>
</label>
</div>
<div id="prod_adjust">
<p v-for="product in selectedProd">{{product}}</p>
</div>
</div>
If you have the input as a JSON which has all the properties you are asking for (product_ID, img src, brand name) then you can assign the object to v-model instead of product_ID.
Your JSON array should look like this,
productsArray: [
{ productID: 1260,
product-title: "Product 1",
brand: "Brand 1",
img: "https://picsum.photos/100/100"
},
{ productID: 1261,
product-title: "Product 2",
brand: "Brand 2",
img: "https://picsum.photos/100/100"
},]
Then inside v-for you can read each object and when selected you can assign the entire object into selectedProd
I used next way finally
vueproducts = function(){
appvue.selectedProd = [];
var tempprod = {};
$('.form-row input').each(function(){
if ($(this).is(":checked")){
tempprod.id = $(this).val();
tempprod.img = $(this).parent().find('img').attr('src');
tempprod.title = $(this).parent().find('.product-title').html();
appvue.selectedProd.push(tempprod);
tempprod = {};
};
});
}
My requirement is something like this.
I am rendering a question paper from an object using v-for.
Once the user select an answer for a question, the question number (index) has to be v-model with that answer. How can I achieve this? this is my code.
<template lang="html">
<div class="container">
<div class="" v-for="(question,index) in questions">
<h1>Question {{index}}</h1>
<p>{{question.question}}</p>
<input type="radio" name="index" value="1">{{question.answer1}}<br>
<input type="radio" name="index" value="2">{{question.answer2}}<br>
<input type="radio" name="index" value="3">{{question.answer3}}
</div>
<hr>
<button type="button" name="button" class="btn">Save and Submit</button>
</div>
</template>
<script>
export default {
data(){
return{
questions:[
{question:"what is the capital of france?",answer1:"paris",answer2:"normandy",answer3:"rome"},
{question:"what is the capital of france?",answer1:"paris",answer2:"normandy",answer3:"rome"},
{question:"what is the capital of france?",answer1:"paris",answer2:"normandy",answer3:"rome"}]
}
}
}
</script>
Use a v-model to in the radioboxes.
A simple way to do that is to create a selectedAnswer property in each question and bind v-model to it, like:
<input type="radio" value="1" v-model="question.selectedAnswer">{{question.answer1}}<br>
Notice also that I removed the name. You were using the same name attributes to all checkboxes, and HTML will only allow one selected radio per group (per name)
To get an array of selected answers, you could simply create a computed property that maps the selected answers into an array. In the example below, the this.answers computed property is available with the answers.
Full demo below.
new Vue({
el: '#app',
data() {
return {
questions: [{
question: "what is the capital of france?",
answer1: "paris",
answer2: "normandy",
answer3: "rome",
selectedAnswer: null
},
{
question: "what is the capital of france?",
answer1: "paris",
answer2: "normandy",
answer3: "rome",
selectedAnswer: null
},
{
question: "what is the capital of france?",
answer1: "paris",
answer2: "normandy",
answer3: "rome",
selectedAnswer: null
}
]
}
},
computed: {
answers() {
return this.questions.map(q => q.selectedAnswer);
}
}
});
<script src="https://unpkg.com/vue"></script>
<div id="app">
Answers: {{ answers }}
<div class="container">
<div class="" v-for="(question,index) in questions">
<h1>Question {{index}}</h1>
<p>{{question.question}} | selected: {{question.selectedAnswer || 'none'}}</p>
<input type="radio" value="1" v-model="question.selectedAnswer">{{question.answer1}}<br>
<input type="radio" value="2" v-model="question.selectedAnswer">{{question.answer2}}<br>
<input type="radio" value="3" v-model="question.selectedAnswer">{{question.answer3}}
</div>
<hr>
<button type="button" name="button" class="btn">Save and Submit</button>
</div>
</div>
I feel v-model might not be the right solution in this case. Here is what I suggest you to do.
<template lang="html">
<div class="container">
<div class="" v-for="(question,index) in questions">
<h1>Question {{index}}</h1>
<p>{{question.question}}</p>
<input type="radio" :name="index" :value="question.answer1" #click="answerSelect(index, question.answer1)">{{question.answer1}}<br>
<input type="radio" :name="index" :value="question.answer2" #click="answerSelect(index, question.answer2)">{{question.answer2}}<br>
<input type="radio" :name="index" :value="question.answer3" #click="answerSelect(index, question.answer3)">{{question.answer3}}
</div>
<hr>
<button type="button" name="button" class="btn">Save and Submit</button>
</div>
</template>
<script>
export default {
data() {
return {
questions: [
{
question: "what is the capital of france?",
answer1: "paris",
answer2: "normandy",
answer3: "rome"
},
{
question: "what is the capital of france?",
answer1: "paris",
answer2: "normandy",
answer3: "rome"
},
{
question: "what is the capital of france?",
answer1: "paris",
answer2: "normandy",
answer3: "rome"
}
]
};
},
methods: {
answerSelect(questionIndex, answer) {
const questions = [
...this.questions.slice(0, questionIndex),
{ ...this.questions[questionIndex], solution: answer },
...this.questions.slice(questionIndex + 1, this.questions.length)
];
this.questions = questions;
}
}
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Here is a link to the code sandbox.
https://codesandbox.io/s/o5r2xqypo9
<template lang="html">
<div class="container">
<div class="" v-for="(question,index) in questions">
<h1>Question {{index}}</h1>
<p>{{question.question}}</p>
<input type="radio" :name="index" :value="question.answer1" #click="pushAnswers(index, 1)">{{question.answer1}}<br>
<input type="radio" :name="index" :value="question.answer2" #click="pushAnswers(index, 2)">{{question.answer2}}<br>
<input type="radio" :name="index" :value="question.answer3" #click="pushAnswers(index, 3)">{{question.answer3}}
</div>
<hr>
<button type="button" name="button" class="btn">Save and Submit</button>
</div>
</template>
Method
pushAnswers(questionIndex,answer) {
this.answerSet[questionIndex] = answer;
console.log(this.answerSet);
}