I have created a settings page where users can update their email addresses. Everything worked fine but suddenly the validation is not updating anymore. Only the first change of the input field triggers validateState().
Any further changes will not trigger this function so the status of that field stays as it is.
I have compared the code with other components that use the same code and they still work fine.
I am using bootstrap-vue components for the form.
<template>
<div class="row">
<div class="col-md-12">
<b-form #submit="onSubmit">
<b-form-group :label="$t('general.email')"
label-for="settingsEmail"
:invalid-feedback="errors.first('email')">
<b-form-input id="settingsEmail"
type="text"
v-model="form.email"
:disabled="saving"
name="email"
:state="validateState('email')"
v-validate="{required: true, email: true}">
</b-form-input>
</b-form-group>
<b-button type="submit" variant="primary" :disabled="saving || !hasChanged() || errors.any()"><i class="fa fa-refresh fa-spin fa-fw" v-if="saving"></i> {{$t('general.save')}}</b-button>
</b-form>
</div>
</div>
</template>
<script>
import {UPDATE_USER} from '../config/actions'
export default {
name: 'settingsAccount',
data() {
return {
form: {},
saving: false
}
},
computed: {
user: function() {
return this.$store.getters.getUser;
}
},
created() {
this.init();
},
methods: {
init() {
this.form.email = this.user.email;
},
hasChanged() {
if(this.form.email !== this.user.email) {
return true;
}
return false;
},
onSubmit(event) {
event.preventDefault();
this.saving = true;
this.$validator.validateAll().then((result) => {
if (result) {
let data = {};
if(this.form.email !== this.user.email) {
data.email = this.form.email;
}
this.$store.dispatch(UPDATE_USER, data).then(() => {
this.saving = false;
this.$validator.reset();
}).catch(() => {
this.saving = false;
});
} else {
this.saving = false;
}
});
},
validateState(ref) {
if (this.veeFields[ref] && (this.veeFields[ref].dirty || this.veeFields[ref].validated)) {
return !this.errors.has(ref)
}
return null
},
}
}
</script>
The problem you're having is that the form data element is an empty object, so it will only trigger reactivity when the whole object changes. Either you need to change your data to be this:
data() {
return {
form: {email:''},
saving: false
}
},
Or in your init function, explicitly add the email property as reactive:
methods: {
init() {
this.$set(form,'email',this.user.email)
},
//...
If you're not clear on why, you can read the details here: https://v2.vuejs.org/v2/guide/reactivity.html
A working example (minus vuex) here: https://codesandbox.io/s/x4kp93w3o
PS, when writing questions about vue, it's very helpful to boil it down to a simpler example. Get rid of vuex, remove your translation stuff. Sometimes the answer will jump out at you once you have it as simple as possible.
Related
I have two input which both need to remove the space between the string
I used event.clipboardData.setData but it didn't work
After that, I used this.Name_of_my_state But it returns both pasted item and removed space Item.
Let's take a look at my code to make it clear
<template>
<span>
<input class="form-control inputHeight"
#keydown.space.prevent
#paste.space="remove_on_paste"
v-model="floatingData.from_id">
<input class="form-control inputHeight"
#keydown.space.prevent
#paste.space="remove_on_paste"
v-model="floatingData.to_id">
</span>
</template>
First I tried this but didn't work
new Vue({
data() {
return {
floatingData: {
from_id: "",
to_id: ""
}
}
},
methods: {
// Remove space on paste
remove_on_paste(event) {
let main_text = event.clipboardData.getData("text");
event.clipboardData.setData("text", main_text.replace(/\D/g, ""));
}
}
})
Result:
Then I tried this that pasted both copied and replaced value
new Vue({
data() {
return {
floatingData: {
from_id: "",
to_id: ""
}
}
},
methods: {
// Remove space on paste
remove_on_paste(event) {
let main_text = event.clipboardData.getData("text");
this.floatingData.from_id = main_text.replace(/\D/g, "");
}
}
})
Result:
I was able to get the behavior I think you requested, where you can paste in a string with tailing whitespace, and it will be trimmed. The trick is to prevent the browser from doing anything after the paste using event.preventDefault() and using main_text.trim() to remove whitespace. Please let me know if this is what you're looking for.
(Tested on Google Chrome 91, using this codesandbox)
<template>
<span>
<input class="form-control inputHeight"
#keydown.space.prevent
#paste.space="remove_on_paste"
v-model="floatingData.from_id">
<input class="form-control inputHeight"
#keydown.space.prevent
#paste.space="remove_on_paste"
v-model="floatingData.to_id">
</span>
</template>
<script>
export default {
data() {
return {
floatingData: {
from_id: "",
to_id: ""
}
}
},
methods: {
// Remove space on paste
remove_on_paste(event) {
let main_text = event.clipboardData.getData("text");
event.preventDefault();
this.floatingData.from_id = main_text.trim();
}
}
};
</script>
You should use watchers for this:
data()
{
return {
first_input: '',
second_input: '',
}
},
watch:
{
first_input()
{
this.$nextTick(() =>
{
this.first_input = this.first_input.replace(/\s+/g, '');
})
},
second_input()
{
this.$nextTick(() =>
{
this.second_input = this.second_input.replace(/\s+/g, '');
})
},
}
I'm working on a couponcode VueJS app, in which I want to check an array with different discountcodes on matching values. Below I have an array with two discountcodes. If the button is clicked, I want to check the array for any matches. I am not sure what would be the best solution for this..
<template>
<div class="container">
<input placeholder='type discount' v-model="discountInput">
<button #click="checkDiscount">check for discount</button>
<span class="alert" v-if="discountValid">
Code juist
</span>
<span class="alert" v-if="discountInvalid">
Code onjuist
</span>
</div>
</template>
<script>
export default {
props: {
},
data: () => {
return {
discountInput: '',
discountValid: false,
discountInvalid: false,
discountCodes: [
{ code: 'discount-code-1', message: '10% discount' },
{ code: 'discount-code-2', message: '5 dollar discount' }
]
}
},
components: {
},
methods: {
checkDiscount() {
if (this.discountInput === this.discountCode) {
return true;
} else {
return false;
}
}
},
watch: {
}
}
</script>
A find should work.
checkDiscount() {
if (this.discountCodes.find(x => x.code === this.discountInput)) {
return true;
} else {
return false;
}
}
or as comments pointed out could be reduced to:
checkDiscount() {
return !!this.discountCodes.find(x => x.code === this.discountInput);
}
Try to use array some method as follows :
checkDiscount() {
return this.discountCodes.some(dis => dis.code === this.discountInput)
}
I have a functioning Vue Multiselect where I'm using an axios call to fill the options from my database values. This works perfectly and allows me to choose from existing options or enter new options in order to create tags.
As it is, this works perfectly. But I need a way, if possible, to make another Axios call every time the user selects and option or hits the enter key to save a tag option. Is there a way to do this?
This is my first experience with Vue and I'm really not sure how feasible this is, but basically I'm just wondering how to make an axios call every time a tag is selected or entered with the enter key
<div id="tagApp">
<multiselect
label="tag_data"
track-by="campaign_tag_id"
v-model="value"
:options="options"
:multiple="true"
:taggable="true"
#tag="addTag"
#search-change="val => read(val)"
:preselect-first="false"
:close-on-select="false"
:clear-on-select="true"
:preserve-search="true"
tag-placeholder="Add this as new tag"
placeholder="Search or add a tag"
></multiselect>
</div>
new Vue({
components: {
Multiselect: window.VueMultiselect.default
},
el: "#tagApp",
data() {
return{
value: [],
loading: false,
options: []
}
},
methods: {
read: function(val){
//console.log('searched for', val);
if (val) {
this.loading = true;
this.options = [];
const self = this;
console.log(val);
axios.get('campaigns/search',{params: {query: val}})
.then(function (response) {
self.options = response.data;
console.log(response.data);
});
} else {
this.options = [];
}
},
addTag(newTag) {
const tag = {
tag_data: newTag,
};
this.options.push(tag);
this.value.push(tag);
}
}
})
Monitor #select event and trigger a function where your Axios call will happen.
<div id="tagApp">
<multiselect
...
#select= "callAxios"
...
></multiselect>
</div>
...
methods: {
callAxios: function(val){
//your Axios call here
},
...
}
...
below is HTML code for form
<div class="form-group">
<label for="email">Email</label>
<input type="email" class="form-control"
(blur)="suggestEmail(signupForm.controls['userData'].controls.email.value)"
id="email" formControlName="email">
<span class="help-block" *ngIf="!signupForm.get('userData.email').valid && signupForm.get('userData.email').touched">
please enter a valid email id
</span>
</div>
Below is ts code
constructor(private fb: FormBuilder) {
this.signupForm = this.fb.group({
userData: this.fb.group({
email: [null, [Validators.required, Validators.email]]
})
});
}
ngOnInit() {
}
suggestEmail(email) {
Mailcheck.run({
email: email,
domains: ['gmail.com', 'aol.com', 'hotmail.com', 'yahoo.com', 'rediffmail.com', 'edu', 'msn.com',],
secondLevelDomains: ['domain', 'hotmail'],
topLevelDomains: ["com", "net", "org", "info"],
suggested: function (suggestion) {
console.log(suggestion);
if (suggestion) {
alert(suggestion.full);
console.log(suggestion.full + "dkdjdekjekde")
}
},
empty: function () {
}
});
}
Right now, value of suggestions.full comes in alert if its being called. But I am trying to show suggestions.full in html side, like as a error warning.
Below is link to my stackblitz
stackblitz
To avoid potential problems with access to this within the Mailcheck.run suggested callback, you could save the results of Mailcheck.run, check them and, if appropriate, set an error on your form field.
let check = Mailcheck.run({
email: email,
... other stuff ...
suggested: (suggestion) => {
return suggestion;
},
empty: () => {
return false; // or however you want to handle it...
}
if (check && check.full) {
this.suggestedEmail = check.full;
this.signupForm.get('userData.email').setErrors({ 'has_suggestion': true })
}
// then in your template (using a getter)
<span class="help-block"
*ngIf="f.invalid && f.touched && f.errors?.has_suggestion">
Suggestion: {{suggestedEmail}}
</span>
Please find this stackblitz -- hope it helps!
Instead of using a regular function which will be lost this scope whereas arrow function keeps track of this. Read more about the difference here https://stackoverflow.com/a/34361380/5836034
do something like this
....
suggestion: any;
....
suggestEmail(email) {
Mailcheck.run({
email: email,
domains: ['gmail.com', 'aol.com', 'hotmail.com', 'yahoo.com', 'rediffmail.com', 'edu', 'msn.com',],
secondLevelDomains: ['domain', 'hotmail'],
topLevelDomains: ["com", "net", "org", "info"],
suggested: (suggestion) => {
console.log(suggestion);
if (suggestion) {
alert(suggestion.full);
this.suggestion = suggestion;
console.log(suggestion.full + "dkdjdekjekde")
}
},
empty: function () {
}
});
}
Observe the use of arrow function, to keep track of this scope and also, assigning the value of suggestion to your class variable via
this.suggestion = suggestion
in your template, you can now have access to suggestion like so
<div *ngIf="suggestion">{{suggestion.full}} </div>
Source: https://stackblitz.com/edit/angular-email-checker-bjcrcc
all how i can watch changes in my component in data?
I need watch when user choose car brand to take from server models for that brand
this is my code
Templete
<template>
<div class="category-info">
<div v-for="input in inputs.text">
<label >{{ input.placeholder}}</label>
<input type="text" id="location" :name="input.name" v-model="input.value" #click="console">
</div>
<div class="select" v-for="select in inputs.select">
<label >{{ select.placeholder }}</label>
<my-select :data="select" v-model="select.value"></my-select>
</div>
<button #click="console">click</button>
</div>
Script
<script>
export default {
name: "profile-add-inputs",
props: ['category'],
data() {
return {
inputs: {
text : {},
select: {}
},
}
},
methods: {
getCategories(){
axios.get('/profile/inputs', {
params: {
category: JSON.stringify(this.category.href)
}
})
.then((response) => {
this.inputs.text = response.data.text;
this.inputs.select = response.data.select;
for(let key in this.inputs.text){
this.inputs.text[key].value = '';
}
for(let key in this.inputs.select){
this.inputs.select[key].value = '';
if(this.category.href.sub == 'car' && this.inputs.select[key].name == 'brand'){
console.log('CAR BREND');
this.$watch.inputs.select[key].value = function () {
console.log(this.inputs.select[key].value);
}
}
}
},this)
.catch(function (error) {
console.log(error);
});
},
console(){
console.log(this.inputs.select);
}
},
watch: {
category : function () {
this.getCategories();
console.log('categoty');
},
inputs : {
handler() {
console.log('watch inputs');
}
}
}
}
So, i tried to use watch and $watch but its not working, plz give me a reason why that not work, or maybe some another way to resolve this problem
this.$watch can i create dynamiclly watchers with this stement?
The correct syntax is
watch : {
inputs : function(val, oldVal){
//val : New value
//oldVal : Previous value.
}
}