How to dynamically change input type - vuejs2

On my vue page I have three different inputs:
<input
id="id"
class="flex-grow bg-white p-4 text-sm items-center focus:outline-none"
type="text"
name="id"
v-model="id"
placeholder="Enter your email or phone number..."
/>
<input
v-show="phone"
id="phone"
class="flex-grow bg-white p-4 text-sm items-center focus:outline-none"
type="tel"
name="phone"
v-model="phone"
required
placeholder="Enter your phone number..."
/>
<input
v-show="email"
id="email"
class="flex-grow bg-white p-4 text-sm items-center focus:outline-none"
type="email"
name="email"
v-model="email"
required
placeholder="Enter your email..."
/>
Now the first id field is the default field that shows on my page. When the user starts typing I have a watcher to watch the value of "id". If it's a string then it swaps the default text field for the email field.
If it's numeric then it swaps it for the tel field.
This is the watcher:
watch: {
id(newVal, oldVal) {
if (this.startsWithLetter(newVal)) {
this.$nextTick(() => {
this.phone = null
this.email = newVal
this.$el.querySelector('#email').focus()
})
} else {
this.$nextTick(() => {
this.email = null
this.phone = newVal
this.$el.querySelector('#phone').focus()
})
}
},
},
Now this works however I've realised the focus simply does not work when the new field is swapped in. The new field doesn't get focus and the soft keyboard hides itself.

I'm not sure exactly of what you're trying to achieve here. Seems a bit hard to understand for me.
Meanwhile, here is a simple example on how to have a dynamic input type depending of the content of the input.
<template>
<div>input: <input v-model="userInput" :type="phoneOrText" /></div>
</template>
<script>
export default {
data() {
return {
userInput: '',
}
},
computed: {
phoneOrText() {
return /\d/.test(this.userInput) ? 'tel' : 'text'
},
},
}
</script>
The input will be text if the input contains only text and will swap to tel if it contains any numbers thanks to the /\d\ regex (any digit). I've chosen password for the screenshots because it is more visual but it can be anything. tel is working great on phone too and does not require any focus() or selection.
Not sure if it helps but right now, it seems a bit too cryptic for me to help you more.

Related

validate form inputs bootstrap4

I have this code in my vue template
<div class="form-row" :class="{'was-validated': this.checkPassword()}">
<div class="col-6">
<label>Password</label>
<input :type="showPassword ? 'text' : 'password'" class="form-control" ref="password" required v-model="password">
<div class="valid-feedback" v-if="!error">
Password match
</div>
<div class="invalid-feedback" v-else>
Password not match
</div>
</div>
<div class="col-6">
<label>Confirm password</label>
<div class="input-group">
<input :type="showPassword ? 'text' : 'password'" class="form-control" ref="passwordCheck" required v-model="passwordCheck" #change="checkPassword()">
<div class="input-group-append">
<button class="btn btn-secondary" #click.prevent="copyToClipboard()"><i class="fas fa-clipboard"></i></button>
</div>
</div>
</div>
</div>
I want to show like the bootstrap4 documentation a green input field if the password matches or a red one if the password aren't matching. I'm trying by adding the was-validated class to the form-row if the demanded method return true but when the view where the password inputs are rendered the two input fields are always red. How I can fix this to give the correct feedback to the user?
Please always share all the relevant parts of the component otherwise, it's hard to tell where the problem resides. Here, you haven't shared your <script> section. Anyway, I guess this should put you on the right track.
<template>
<div class="form-row" :class="{'was-validated': this.checkPassword()}">
<div class="col-6">
<label>Password</label>
<label>
<input :type="showPassword ? 'text' : 'password'" class="form-control" :class="getPasswordClass()"
ref="password" required v-model="password">
</label>
</div>
<div class="col-6">
<label>Confirm password</label>
<div class="input-group">
<label>
<input :type="showPassword ? 'text' : 'password'" class="form-control" :class="getPasswordClass()"
ref="passwordCheck" required v-model="passwordCheck" #input="checkPassword()">
</label>
<div class="input-group-append">
<button class="btn btn-secondary" #click.prevent="copyToClipboard()"><i
class="fas fa-clipboard"></i></button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
password: null,
passwordCheck: null,
error: true,
showPassword: true,
}
},
methods: {
checkPassword() {
this.error = this.password !== this.passwordCheck;
},
getPasswordClass() {
if (!this.password || !this.passwordCheck) {
return '';
}
return this.error ? 'is-invalid' : 'is-valid'
},
copyToClipboard() {
//
}
}
}
</script>
So, in my opinion, you should only set the dynamic class (is-valid or is-invalid) when both inputs are provided. In this example I've added that to both password and passwordCheck fields but I think it's enough to just apply it to the passwordCheck because that's the one checked against the password.
If you only want to check after user leaves the field you could adjust the code like this:
In the template remove:
:class="{'was-validated': this.checkPassword()}"
And update:
#blur="checkPassword()
In the data() add:
blurred: false,
In the methods update:
methods: {
checkPassword() {
this.blurred = true;
this.error = this.password !== this.passwordCheck;
},
getPasswordClass() {
if (!this.blurred) {
return '';
}
return this.error ? 'is-invalid' : 'is-valid'
},
...
}

Vue.js and Vee Validate - How to update error message with error from an API

I am trying to set the error message from the vee-validate to one from an API.
<div class="col-md-12">
<label for="company-contact-name" class="label-input">Company Contact Name</label>
<input v-validate="validations.user.name" v-model="data.user.name" id="company-contact-name" class="form-control" type="text" name="name" placeholder="Enter contact name" />
<div id="name-error" class="msg-error text-danger">{{ errors.first('name') }}</div>
</div>
<div class="col-md-12">
<label for="email" class="label-input">E-mail address</label>
<input v-validate="validations.user.email" v-model="data.user.email" id="email" class="form-control" type="email" name="email" placeholder="Enter e-mail" />
<div id="email-error" class="msg-error text-danger">{{ errors.first('email') }}</div>
</div>
So, If the API returns an email error, I would like to edit the above "errors.first('email')" to the API error. Then, when the user starts to correct the field, the Vee Validate would show its configured errors.
This is an example of a possible array of errors:
[
{id: "name", title: "Name is invalid. It should have only letters"},
{id: "name", title: "Name is too short. It should have more than three characters"},
{id: "email", title: "Email has already been taken"}
]
What can be done to handle the API error messages?
Thanks for your time and attention.
Perhaps something like this - create a new validator which checks whether the API has returned an error message and if yes - returns that error message. Then use this new validator as a first validator for your field, but also add the built-in validator for email addresses.
<input
v-validate="api_email|email"
v-model="user.email"
id="email"
class="form-control"
type="email"
name="email"
placeholder="Enter e-mail" />
<script>
import { Validator } from 'vee-validate';
export default
{
data()
{
api_error: '',
user:
{
email: ''
}
},
mounted()
{
Validator.extend('api_email',
{
getMessage: this.emailError,
validate: this.validateEmail
});
},
methods:
{
validateEmail(value, args)
{
return !this.api_error;
},
emailError(field, args)
{
return this.api_error;
}
}
}
</script>
UPDATE
If you want to support an array of errors then perhaps you can do it like this
<div id="email-error" class="msg-error text-danger" v-for="err in [errors.first('email')].concat(array_with_api_errors.map(item => item.title))">{{ err }}</div>

Vue JS insert and remove html elements dynamicaly

I want some thing like this,
If I hit "Add Another" button another set of input boxes should render below this set and this set's "Add Another" button should change to the "Remove This School" and the maximum number of input rows should limited to the 5.
I hope you understand my requirement.What I want is that the user can enter 5 schools or less than 5 schools.If he accidentally add some wrong information he should have a remove button to remove that record.How do I achieve this using Vue JS.
This is my code.
<template>
<div id="app">
<p>Please enter the schools you attained (Maximum five)</p>
School Name : <input type="text" name="" value="">
Location : <input type="text" name="" value="">
<button type="button" name="button" #click="addAnotherInputBox">Add Another</button>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
counter:0
}
},
methods:{
addAnotherInputBox(){
}
}
}
</script>
Here you go, although you should have filled the logic of your add button, but you can do that by your own
Here is a working fiddle: https://jsfiddle.net/Sletheren/Lo7t0myL/
<template>
<div id="app">
<p>Please enter the schools you attained (Maximum five)</p>
School Name : <input v-model="name" type="text" name="" value="">
Location : <input v-model="location" type="text" name="" value="">
<button type="button" name="button" #click="addAnotherInputBox">Add Another</button>
<ul class="schools">
<li v-for="school in schools">
{{school.name}} <button #click="removeSchool(school)">remove</button>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
counter:0,
name: '',
location:'',
schools: []
}
},
methods:{
addAnotherInputBox(){
// your logic here...
if(this.counter>4){
alert('you should delete one first! only 5 allowed')
}else{
const school = {
name: this.name, //name here
location: this.location //location here
}
this.counter++;
this.schools.push(school)
this.name='';
this.location=''
}
},
removeSchool(school) {
this.schools.splice(this.schools.indexOf(school),1);
this.counter--;
}
}
}
</script>

v-on:model="form.email" expects a function value, got undefined

Vue.js 2 - I am trying to bind form inputs but I always get the erro message ( on all inputs ..)
v-on:model="form.email" expects a function value, got undefined
<form id="registrationForm">
<div class="form-group">
<input type="email" name="email" id="email" #model="form.email" placeholder="enter your email address">
</div>
<button #click="sendRegistration" type="submit" class="btn btn-primary btn-gradient submit">SEND</button>
</form>
and the script
data: function() {
return {
form: {
...
email: '',
...
}
}
},
methods: {
sendRegistration: function() {
console.log('sending form')
return false
}
},
You're getting some things mixed up. Attributes starting with v-on:, often abbreviated as #, are used to register event listeners on elements. #click="sendRegistration" will for example register the sendRegistration method defined on your Vue instance as a handler for that element's click event.
What you're trying to accomplish has nothing to do with event handling. The attribute you need is called v-model and binds an <input>'s value to a value saved on your Vue instance.
<input type="email" name="email" id="email" #model="form.email">
should be
<input type="email" name="email" id="email" v-model="form.email">

vue js get multiple values from inputs

So I have 2 blocks of HTML, each containing 2 input fields and when submitting the form, I want to get all values from the inputs, and then create an object from the values...
As of know I've done it with plain vanilla JS and it works as it should, however if feels like to touching the DOM a bit to much, and also are very much depending on a specific DOM struckture, and therefore I was thinking there must be a better way, the VUE way so to speak, however im a bit stuck on how to do this the VUE way, which is why posting the question here in hope of getting some useful tips :)
HTML:
<form novalidate autocomplete="off">
<div class="input-block-container">
<div class="input-block">
<input type="text" placeholder="Insert name" name="name[]" />
<input-effects></input-effects>
</div>
<div class="input-block">
<input type="email" placeholder="Insert email address" name="email[]" />
<input-effects></input-effects>
</div>
</div>
<div class="input-block-container">
<div class="input-block">
<input type="text" placeholder="Insert name" name="name[]" />
<input-effects></input-effects>
</div>
<div class="input-block">
<input type="email" placeholder="Insert email address" name="email[]" />
<input-effects></input-effects>
</div>
</div>
<button class="button button--primary" #click.prevent="sendInvites"><span>Send</span></button>
</form>
JS:
methods: {
createDataObject() {
let emailValues = document.querySelectorAll('input[type="email"]');
emailValues.forEach((email) => {
let name = email.parentNode.parentNode.querySelector('input[type="text"]').value;
if(email.value !== "" && name !== "") {
this.dataObj.push({
email: email.value,
name
});
}
});
return JSON.stringify(this.dataObj);
},
sendInvites() {
const objectToSend = this.createDataObject();
console.log(objectToSend);
//TODO: Methods to send data to server
}
}
You can provide data properties for each of your inputs if you have static content.
data: function() {
return {
name1: '',
email1: '',
name2: '',
email2: ''
}
}
Then use them in your template:
<input type="text" placeholder="Insert name" v-model="name1" />
Access in method by this.name1
Try this
<div id="app">
<h1> Finds </h1>
<div v-for="find in finds">
<input name="name[]" v-model="find.name">
<input name="email[]" v-model="find.email">
</div>
<button #click="addFind">
New Find
</button>
<pre>{{ $data | json }}</pre>
</div>
Vue Component
new Vue({
el: '#app',
data: {
finds: []
},
methods: {
addFind: function () {
this.finds.push({ name: '', email: '' });
}
enter code here
}
});