I'm a vue js newbie, I perform a get operation with the value entered in the search input and if there is a result, I show it in "listShow", if there is no result, I return "listShow" false. no problem so far. only if the user chooses any of the incoming data, I send the "name" searchtext of the incoming data to the input. but if there is no result "listShow false" and click somewhere outside the input
I want to make "newDiv" true. so "inputOutClick" does the job, but when I click on any of the "search" data, "inputOutClick" does not allow this "selecteds()" function to fire.
And also, is my coding style correct, I'm getting too repetitive.
Is it ok to use search #keyup?
Does it make sense to use v-on:focusout?
const app = new Vue({
el: '#app',
data: {
searchText: '',
listShow: true,
newDiv:false,
searcList:[],
},
methods: {
inputOutClick() {
this.listShow = false
},
selecteds(list) {
this.listShow = false;
this.searchText = list.name;
},
async search() {
if (this.searchText !== '') {
const res = await this.callApi('get', 'search' + '?filter=' + this.searchText)
if (res.status === 200) {
this.searcList = this.getList;
if (res.data.length > 0) {
this.listShow = true;
} else {
this.listShow = false;
}
}
} else {
this.listShow = false;
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<div>
<input
type="text"
v-model="searchText"
#keyup="search"
v-on:focusout="inputOutClick"
/>
<div v-if="listShow" style="background:red">
<ul>
<li v-for="(list, index) in searcList">
<a #click="selecteds(list)">{{ list.name }}</a>
</li>
</ul>
</div>
<div v-if="newDiv">
<p>hello</p>
</div>
</div>
</div>
You can use #mousedown.prevent on the searchList entries (where the click handler is attached). This prevents the v-on:focusout event being fired, if a searchList entry is clicked.
<input
type="text"
v-model="searchText"
#keyup="search"
v-on:focusout="inputOutClick"
/>
<a
#click="selectEntry(entry)"
#mousedown.prevent
>
xxx
</a>
Use #mousedown instead of #click.
=> #click runs after #focusout.
=> #mousedown runs before #focusout.
If you do not want to run the focusout function on the input field when the list is clicked at all then you can use #mousedown.prevent="selecteds(list)".
See example below (click on "Full page" so the console.log doesn't block the list):
const app = new Vue({
el: '#app',
data: {
searchText: '',
listShow: true,
newDiv:false,
searcList:[],
list: {}
},
methods: {
inputOutClick() {
console.log("inputOutClick");
if (this.listShow == false) {
console.log("mousedown was fired first");
}
this.listShow = false
},
selecteds(list) {
console.log("selecteds");
this.listShow = false;
this.searchText = list.name;
},
async search() {
console.log("search");
this.listShow = true;
this.searcList = ['aeaeg', 'tdthtdht', 'srgsr'];
this.list.name = "TEST"
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<div>
<input
type="text"
v-model="searchText"
#keyup="search"
v-on:focusout="inputOutClick"
/>
<div v-if="listShow" style="background:red">
<ul>
<li v-for="(list, index) in searcList">
<a #mousedown="selecteds(list)">LIST TEXT</a>
</li>
</ul>
</div>
<div v-if="newDiv">
<p>hello</p>
</div>
</div>
</div>
I have a page which I validate the email add input #blur. This works perfectly and displays the error message if it fails validation rules set but the issue I have is that due to the #blur, when I click my reset button the error quickly displays then hides and this is poor UI and I want to stop it but can't figure out how to.
HTML
<div class="card" v-on:click="select($event)">
<div class="card-body">
<div class="form-group row">
<label class="col-sm-3 col-form-label pr-0" for="emailAddField">Email <span class="text-danger">*</span></label>
<div class="col-sm-9">
<div class="input-group">
<input id="emailAddField" ref="pdEmailAdd" class="form-control" type="search" :value="pdEmailAdd" #input="pdEmailAddInput" #blur="emailInputValChecker($event)" placeholder="Enter an email address">
<div class="input-group-append" :class="emailButtonValidation">
<a class="btn input-group-text primaryBtn" :class="emailButtonValidation" type="button" :href="'mailto:' + pdEmailAdd">
<i class="far fa-envelope"></i>
</a>
</div>
</div>
<div v-if="emailFormatErrorMsg" class="text-danger">Incorrect email address format</div>
</div>
<div class="card-footer">
<button id="resetButton" ref="resetButton" class="btn btn-warning col-4" #click="pdInitPageStates($event)" :disabled="resetDetails">
Reset details
</button>
</div>
</div>
I have 'hacked' at the card trying to use #click on the card to get the id but this didn't work so I set the id in my `data but not happy about it and sure there is a lot better way but I just can't figure it out
Code
data() {
return {
pdEmailAdd: '',
reg: /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,24}))$/,
detailsChanged: false,
emailIncorrectFormat: false,
targetId: 'resetButton', // HACK
targetId2: '', // HACK
}
},
computed: {
emailButtonValidation() {
if (!this.pdEmailAdd || !this.reg.test(this.pdEmailAdd)) {
if (this.pdEmailAdd === '') {
this.emailIncorrectFormat = false;
} else {
this.emailIncorrectFormat = true;
}
return 'disabled'
} else {
this.emailIncorrectFormat = false;
return ''
}
},
resetDetails() {
this.detailsChanged = false;
if (this.pdName != this.$store.state.account.firstname + ' ' + this.$store.state.account.lastname) {
this.detailsChanged = true;
}
if (this.telNoType === 'ddi' && this.pdTelNo != this.$store.state.account.ddi) {
this.detailsChanged = true;
} else if (this.telNoType === 'mobile' && this.pdTelNo != this.$store.state.account.mobile) {
this.detailsChanged = true;
} else if (this.telNoType === 'na' && this.pdTelNo != '') {
this.detailsChanged = true;
}
if (this.pdExtNo != this.$store.state.account.extension) {
this.detailsChanged = true;
}
if (this.pdEmailAdd != this.$store.state.user.adminemail) {
this.detailsChanged = true;
}
return !this.detailsChanged;
}
}
// Another hack to try set it soon as page loads
mounted() {
this.$refs.resetButton.click();
},
methods: {
emailInputValChecker(event) {
this.emailFormatErrorMsg = false;
if (!this.pdEmailAdd || !this.reg.test(this.pdEmailAdd)) {
if (this.pdEmailAdd === '') {
this.emailFormatErrorMsg = false;
} else {
this.select(event)
// Uses the 'dirty hacks'
if (this.targetId !== '' && this.targetId !== 'resetButton' && this.targetId2 !== 'resetButton') {
this.emailFormatErrorMsg = true;
};
}
}
},
select(event) {
this.targetId = event.target.id;
if (this.targetId === 'resetButton') {
this.targetId2 === 'resetButton';
} else if (this.targetId === '') {
this.targetId === 'resetButton';
}
}
}
Basically all I want is the input to check it passes validation when input is left unless the reset button is clicked then ignore it but think I've gone code blind and can't think of a way to do this.
The mousedown event on your reset button is what causes blur on the input to fire. Adding #mousedown.prevent to the reset button will stop that from happening specifically when the reset button is clicked.
This snippet ought to illustrate the solution. Remove #mousedown.prevent from the reset button and you'll see similar behavior to your issue, with the error briefly flashing.
new Vue({
el: '#app',
data: {
email: '',
error: null
},
methods: {
onBlur () {
if (this.email === 'bad') {
this.error = 'bad email!'
} else {
this.error = null
}
},
onReset () {
this.error = null
this.email = ''
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="onReset" #mousedown.prevent>Reset</button>
<p>
Type in "bad" and leave input to trigger validation error<br>
<input type="text" v-model="email" #blur="onBlur"/>
</p>
<p>{{ email }}</p>
<p v-if="error">error!</p>
</div>
I am attempting to update a series of v-text-fields (type='number') so that after the user has entered in a numeric value, the number shown in the input will be updated with commas (so a value of 5032 would become 5,032 for example). I found this article and was able to accomplish what I'm after with a single input using the example provided...
Markup:
<div id="app">
<div v-if="visible === true">
Enter Amount: <br>
<input type="number"
v-model="amount"
placeholder="Enter Amount"
#blur="onBlurNumber"/>
</div>
<div v-if="visible === false">
Enter Amount: <br>
<input type="text"
v-model="amount"
placeholder="Enter Amount"
#focus="onFocusText"/>
</div>
Script:
data: {
amount: null,
temp: null,
visible: true
},
methods: {
onBlurNumber() {
this.visible = false;
this.temp = this.amount;
this.amount = this.thousandSeprator(this.amount);
},
onFocusText() {
this.visible = true;
this.amount = this.temp;
},
thousandSeprator(amount) {
if (amount !== '' || amount !== undefined || amount !== 0 || amount !== '0' || amount !== null) {
return amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
} else {
return amount;
}
}
}
...but I want to make the methods generic enough to work with any numeric v-text-fields I am using. I have been able to update a parameter value within my methods, but have been unable to actually update the v-model data of the v-text-field.
Markup:
<div id="app">
<div v-if="visible === true">
<v-text-field
class="mb-3 d-inline-block"
type="number"
prepend-inner-icon="attach_money"
v-model="amount"
label="Amount"
mask="##########"
outline
:rules="[v => !!v || 'Amount is required']"
#blur="onBlurNumber(amount)"
required>
</v-text-field>
</div>
<div v-if="visible === false">
<v-text-field
class="mb-3 d-inline-block"
prepend-inner-icon="attach_money"
v-model="amount"
label="Amount"
outline
#focus="onFocusText(amount)"
>
</v-text-field>
</div>
Script:
onBlurNumber(data) {
this.visible = false;
this.temp = data;
data = this.thousandSeprator(data);
},
onFocusText(data) {
this.visible = true;
data = this.temp;
},
I can log the value of data in these methods and confirm that the commas are being applied correctly, but now I don't know how to send the data value back to update the v-text-field's v-model. I experimented with selecting the v-text-field using a ref value but the ref turns up as an undefined value when the method is triggered.
Does anyone know how I can update the v-model of the v-text-field using arguments in this sort of fashion so the methods are reusable?
I assume that you have multiple data items for each of the text fields:
data: function() {
return {
// Field 1
temp1: null,
amount1: null,
visible1: true,
// Field 2
temp2: null,
amount2: null,
visible2: true
}
}
In your markup, when calling the method you could then pass the name of the property, or maybe its suffix.
<v-text-field #blur="onBlurNumber('2')"
And in your script, you could update the data items by using dynamic properties:
methods: {
onBlurNumber(suffix) {
this["visible" + suffix] = false;
this["temp" + suffix] = this["amount" + suffix];
this["amount" + suffix] = this.thousandSeprator(this["amount" + suffix]);
},
Here's a working example of two independent text inputs that are calling the same methods to achieve this. We could refactor this to reduce the number of data items using arrays if needed.
My code accurately updates the gameInput array, however when you edit the numbers, tempval visually fills in every empty cell- is there anyway to avoid that? It, however, only updates the cell that is needed, not all cells. It is only visually as you input it.
<template>
<div>
<div class = "wrapper">
<div class = "list" v-for="(number,index) in gameNums" :key="index">
<div class = "cell=empty" v-if="number == 0"> <input type="text" v-on:change="changeVal(index)" v-model="tempval" v-bind:id="'empty'+ index""> </div>
<div class = "cell" v-else> {{number}} </div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'sudoku',
data: function(){
return{
gameAnswer: [4,3,5,2,6,9,7,8,1,6,8,2,5,7,1,4,9,3, 1,9,7,8,3,4,5,6,2, 8,2,6,1,9,5,3,4,7, 3,7,4,6,8,2,9,1,5,9,5,1,7,4,3,6,2,8,5,1,9,3,2,6,8,7,4, 2,4,8,9,5,7,1,3,6,7,6,3,4,1,8,2,5,9],
gameBoard: [4,3,5,2,6,9,7,8,1,6,8,2,5,7,1,4,9,3, 1,9,7,8,3,4,5,6,2, 8,2,6,1,9,5,3,4,7, 3,7,4,6,8,2,9,1,5,9,5,1,7,4,3,6,2,8,5,1,9,3,2,6,8,7,4, 2,4,8,9,5,7,1,3,6,7,6,3,4,1,8,2,5,9],
gameInput: [4,3,5,2,6,9,7,8,1,6,8,2,5,7,1,4,9,3, 1,9,7,8,3,4,5,6,2, 8,2,6,1,9,5,3,4,7, 3,7,4,6,8,2,9,1,5,9,5,1,7,4,3,6,2,8,5,1,9,3,2,6,8,7,4, 2,4,8,9,5,7,1,3,6,7,6,3,4,1,8,2,5,9],
gameWon: 0,
tempval : 0
}
},
computed: {
gameNums: function() {
var i = 0;
while(i < 35){
var val = Math.floor(Math.random()*82);
console.log(i);
this.gameBoard[val] = 0;
this.gameInput[val] = 0;
i = i+1;
}
return this.gameBoard;
}
},
methods:{
randomNumber: function(index){
var val = Math.floor(Math.random()*3);
if(val == 0){
this.gameBoard[index] = 0;
return true;
} else{
return false;
}
},
changeVal(index, number){
this.gameInput[index]= this.tempval;
console.log(number + "number");
}
},
};
</script>
The issue here is that you are using the same variable tempval as v-model for all inputs, so once tempval changes it gets updated on all inputs.
One way of fixing this is, if you want to have all inputs active at the same time, is to remove the v-model and pass the event to the changeVale method:
<input
type="text"
v-on:change="changeVal(index, $event);"
v-bind:id="'empty' + index"
/>
And then in the changeVal method:
changeVal(index, event) {
this.gameInput[index] = event.target.value;
}
I tried this approach with v-on:change and I didn't like it too much since it doesn't trigger instantly. It gets triggered when you change the focus or press enter.
I prefer to use keyup:
<input
type="text"
v-on:keyup="changeVal(index, $event);"
v-bind:id="'empty' + index"
/>