Dynamic Rendering with V-If - vue.js

I am creating a form and I want to show certain fields only when a certain button is pressed, but take those fields away and show other fields when another button is pressed.
I am new to vue (and coding), but I think I'm wanting to use v-if, but I can't seem to return the values back to the v-if field.
If the type MAGAZINE is selected, then a method sets the typeIsMagazine to TRUE and the other typeselectors to FALSE. I would expect that once typeIsMagazine is set to true, then the v-if would be triggered and the form fields will be shown.
The method is being triggered, and I am testing it with console.log so I know the if functions are working. I just don't think it's being returned to v-if.
<template>
<form #submit.prevent="handleSubmit">
<label class="main">Type:</label>
<div class="type-row">
<div class="sub-column">
<div
class="sub"
#click="updateType('auto_stories')"
:class="{ selected: type === 'auto_stories' }"
>
Book
</div>
</div>
<div class="sub-column">
<div
class="sub"
#click="updateType('article')"
:class="{ selected: type === 'article' }"
>
Article
</div>
</div>
<div class="sub-column">
<div
class="sub"
#click="updateType('website')"
:class="{ selected: type === 'website' }"
>
Website
</div>
</div>
</div>
<template v-if="typeIsWebsite">
<label class="main">Website:</label>
<input type="text" class="text" v-model="url" required />
</template>
<template v-if="typeIsArticle">
<label class="main">Magazine:</label>
<input type="text" class="text" v-model="magazine" required />
</template>
<button class="form">Add Entry</button>
</form>
</template>
<script>
export default {
data() {
return {
typeIsWebsite: false,
typeIsArticle: false,
typeIsBook: false,
};
},
methods: {
updateType(typeSelect) {
this.type = typeSelect;
let typeIsWebsite = false;
let typeIsBook = false;
let typeIsArticle = false;
if (typeSelect === "website") {
typeIsWebsite = true;
typeIsArticle = false;
typeIsBook = false;
} else if (typeSelect === "article") {
typeIsWebsite = false;
typeIsArticle = true;
typeIsBook = false;
} else if (typeSelect === "auto_stories") {
typeIsWebsite = false;
typeIsArticle = false;
typeIsBook = true;
}
return typeIsWebsite, typeIsArticle, typeIsBook;
},

In updateType, your variables typeIsWebsite, typeIsBook, etc are declared as local variables using let. Thus, when you do the if, you are updating local variables, not your component's instance variables.
To fix, remove the typeIsX variable declarations in updateType, and use this.typeIsX to refer to each variable.
Like so:
updateType(typeSelect) {
this.type = typeSelect;
if (typeSelect === "website") {
this.typeIsWebsite = true;
this.typeIsArticle = false;
this.typeIsBook = false;
} else if (typeSelect === "article") {
this.typeIsWebsite = false;
this.typeIsArticle = true;
this.typeIsBook = false;
} else if (typeSelect === "auto_stories") {
this.typeIsWebsite = false;
this.typeIsArticle = false;
this.typeIsBook = true;
}
}
Finally, the function doesn't need to return anything.
As an extra advice, note that this is really verbose code and at least in your use case you don't need all the flags. Just keeping the current type as a string and then comparing against that would be enough. For example:
this.typeIsWebsite is equivalent to this.type === 'website'.
Remember: less code means less errors!

Related

vuejs event modifier search list process

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>

Vue Vuelidate to validate unique value based on data from server

I am trying to create a form with vuelidate. In one field I would like to check if the name is taken or not. I have some async methods to get names and ids from the server and assigning them to arrays, and I have a custom validator that checks if the name exists, either by checking the includes in the array, or by checking a computed value that already checks the array.
Neither of the methods seems to work however. If I check the array, its seems to be empty since it always returns false (even tho the array has values according to the vue tools in the browser). If I check the the computed value, I get an error with undefined.
So my question is, what is the simplest why to validate whether a value exists, and why isn't my current code wokring?
<template>
<div>
<form class="needs-validation" #submit.prevent="submitForm" method="post">
<div class="form-group row">
<label class="col-sm-2 col-form-label">Name:</label>
<div class="col-sm-10">
<input
type="text"
class="form-control"
:class="{ 'is-invalid': $v.form.name.$error }"
id="name"
placeholder="enter name"
v-model="form.name">
<span
class="text-danger"
v-if="!$v.form.name.required && $v.form.name.$dirty">name is required</span>
<span
class="text-danger"
v-if="!$v.form.name.isUnique && $v.form.name.$dirty">name not unique</span>
</div>
</div>
</form>
</div>
</template>
<script>
import { required } from 'vuelidate/lib/validators'
const isUnique = (value, vm) => {
if (value === '') return true
if (vm.names) return !vm.names.includes(value)
return true
}
export default {
data() {
return {
form: {
name: ""
}
ids: [],
names: []
}
}
validations: {
form: {
name: {
required,
isUnique
}
}
}
async created() {
try {
const response = await this.$http.get('/get_data/?fields=id,name')
var array_id = []
var array_name = []
for (var data of response.data) {
array_id.push(data['id'])
array_name.push(data['name'])
}
this.ids = array_id
this.names = array_name
}
}
}
<script>
Seem like you miss the right way to write down methods
form: {
name: {
required,
isUnique : this.isUnique
}
}
},
methods: {
isUnique = (value, vm) => {
if (value === '') return true
if (vm.names) return !vm.names.includes(value)
return true
}
}

Error mesage quickly displaying then hiding as expected in VUE

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>

Updating v-model data in form inputs with a series of methods in a Vuetify project?

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.

V-model updating all Instances

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"
/>