vue: changes not triggered #input - vue.js

Below is vue script - the concern method is called notLegalToShip which checks when age < 3.
export default {
template,
props: ['child', 'l'],
created() {
this.name = this.child.name.slice();
this.date_of_birth = this.child.date_of_birth.slice();
},
data() {
return {
edit: false,
today: moment().format('DD/MM/YYYY'),
childUnder3: false
};
},
computed: {
age() {
var today = new Date();
var birthDate = new Date(this.child.date_of_birth);
var age = today.getFullYear() - birthDate.getFullYear();
var m = today.getMonth() - birthDate.getMonth();
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
return age;
}
},
methods: Object.assign(
mapActions(['updateChild']),
{
notLegalToShip() {
if(this.age < 3){
this.childUnder3 = true;
}
this.childUnder3 = false;
},
showForm() {
this.edit = true;
},
hideForm() {
this.edit = false;
},
submitForm() {
this.hideForm();
this.updateChild({
child: this.child,
name: this.name,
dateOfBirth: this.date_of_birth,
childUnder3 : this.childUnder3
});
}
}
)
}
Here's the snippet of my template. The input as below.
I want the notLegalToShip method to be triggered when I click arrow changing the year. A warning will appear when childUnder3 is "true". I've tried #change, #input on my input but my method is not triggered at all:
<div>
{{childUnder3}}
{{age}}
<div class="callout danger" v-if="childUnder3">
<h2>Sorry</h2>
<p>Child is under 3!</p>
</div>
<div v-if="!edit">
<a #click.prevent="showForm()" href="#" class="more-link edit-details edit-child">
<i class="fa fa-pencil" aria-hidden="true"></i>{{ l.child.edit_details }}
</a>
</div>
<form v-show="edit" #submit.prevent="submitForm()">
<div class="input-wrap">
<label for="account__child__date-of-birth__date">{{ l.child.date_of_birth }}</label>
<input id="account__child__date-of-birth__date" type="date" name="date_of_birth" v-on:input="notLegalToShip" v-model="date_of_birth" v-validate="'required'">
<p class="error-message" v-show="errors.has('date_of_birth')">{{ l.child.date_of_birth_invalid }}</p>
</div>
</form>
</div>
Any help checking my code above would be appreciated!

You have a couple of problems...
Initialise the name and date_of_birth properties in the data() initialiser so Vue can react to them. You can even initialise them from your child prop there...
data() {
return {
edit: false,
today: moment().format('DD/MM/YYYY'),
name: this.child.name // no need to use slice, strings are immutable
date_of_birth: this.child.date_of_birth
}
}
Use this.date_of_birth inside your age computed property instead of this.child.date_of_birth. This way, it will react to changes made via your v-model="date_of_birth" input element.
Make childUnder3 a computed property, it will be easier that way
childUnder3() {
return this.age < 3
}
Alternately, ditch this and just use v-if="age < 3"
With the above, you no longer need any #input or #change event listeners.

Related

How to using Array Loop on validation Form using Vue?

I would like to create a simple web app that can validation form using Vue?
I have two input fields, firstname[1] and firstname[2]
data: {
firstname: ['',''],
}
I want to use the following code to validate the form, but finally not suessful.
computed: {
missfirstname(){
for(var i=1;i<this.firstname.length;i++){
if(this.firstname[i] =='' && this.attemptSubmit) {
this.firstname_ErrMsg[i] = 'Not be empty';
return true;
}
return false;
}
}
},
methods: {
validateForm: function (e) {
this.attemptSubmit = true;
if(this.missfirstname){
e.preventDefault();
}else{
return true;
}
}
},
Is it possible to use array Loop on the validation form??
here it my code I am using Vue 2
my full code
script.js
var app = new Vue({
el: '#app',
data: {
firstname: ['',''],
firstname_ErrMsg: ['',''],
attemptSubmit: false
},
mounted () {
var self = this;
},
computed: {
missfirstname(){
for(var i=1;i<this.firstname.length;i++){
if(this.firstname[i] =='' && this.attemptSubmit) {
this.firstname_ErrMsg[i] = 'Not be empty';
return true;
}
return false;
}
}
},
methods: {
validateForm: function (e) {
this.attemptSubmit = true;
if(this.missfirstname){
e.preventDefault();
}else{
return true;
}
}
},
})
index.html
<div id="app">
<form action='process.php' method="post" autocomplete="off" name="submit_form" v-on:submit="validateForm">
firstname1 : <input type='text' id='firstname1' name='firstname1' alt='1' v-model='firstname[1]'>
<div v-if="missfirstname">{{firstname_ErrMsg[1]}}</div>
<br><br>
firstname2 :
<input type='text' id='firstname2' name='firstname2' alt='2' v-model='firstname[2]'>
<div v-if="missfirstname">{{firstname_ErrMsg[2]}}</div>
<br><br>
<input id="submit" class="preview_button" name="submit_form" type="submit">
</form>
</div>
<script src='https://cdn.jsdelivr.net/npm/vue#2.7.8/dist/vue.js'></script>
<script src='js/script.js'></script>
Observations :
missfirstname property should be separate for each field else it will be difficult to assign the error for a specific field.
Instead of iterating over a this.firstname everytime in computed property, you can use #blur and check the value for that particular field which user touched.
v-model properties should be unique else it will update other one on changing current one.
Your user object should be like this and you can use v-for to iterate it in HTML for dynamic fields creation instead of hardcoding.
data: {
user: [{
name: 'firstName1',
missfirstname: false
}, {
name: 'firstName2',
missfirstname: false
}]
}
Now, In validateForm() you can just pass the index of the iteration and check the model value. If value is empty, you can assign missfirstname as true for that particular index object in user array.
Update : As per author comment, assigning object in users array via for loop.
data: {
users: []
},
mounted() {
for(let i = 1; i <= 2; i++) {
this.users.push({
name: 'firstName' + i,
missfirstnam: false
})
}
}
The array of javascript start from index 0.
Which means in your missfirstname(), i should be defined with 0
missfirstname(){
for(var i=0;i<this.firstname.length;i++){
if(this.firstname[i] =='' && this.attemptSubmit) {
this.firstname_ErrMsg[i] = 'Not be empty';
return true;
}
return false;
}
}

v-on does not working why not receive $emit in custom component

I'm create a an component which represents my money field.
My target is on add element in list, set zero on money field to add next element in list...
But, my problem is that not working when send using $emit event to clear input to improve usability.
$emit works as described on image bellow
My money field:
<template>
<div class="input-group" #clear="clearInputField()">
<span>{{ title }}</span>
<input ref="displayMoney" type="text" v-model="displayMoney" #focus="isActive = true" #blur="isActive = false" />
</div>
</template>
<script>
export default {
props: {
title: String,
},
data() {
return {
money: 0,
isActive: false,
};
},
methods: {
clearInputField() {
console.log("Its work event");
this.money = 0;
this.displayMoney = "";
},
},
computed: {
displayMoney: {
get: function () {
if (this.isActive) {
return this.money;
} else {
return this.money.toLocaleString("pt-br", { style: "currency", currency: "BRL" });
}
},
set: function (modifiedMoney) {
let newMoney = parseFloat(modifiedMoney.replace(/[^\d.]/g, "."));
if (isNaN(newMoney) || newMoney.length == 0) {
newMoney = 0;
}
this.$emit("input", newMoney);
return (this.money = parseFloat(newMoney));
},
},
},
};
</script>
My principal component
<template>
<div class="wish-list">
<div class="row">
<div class="input-group">
<span>Digite sua meta: </span>
<input ref="descriptionWish" type="text" v-model="descriptionWish" />
</div>
<MoneyField title="Valor (R$): " v-model="valueWish" #keyup.native.enter="addWish" />
<button id="btnCalculate" #click="addWish()">Adicionar a lista de desejos</button>
</div>
<div class="list-items">
<ul>
<li v-for="wish in wishes" :key="wish">{{ wish }}</li>
</ul>
</div>
</div>
</template>
<script>
import MoneyField from "./Fields/MoneyField";
export default {
components: {
MoneyField,
},
data() {
return {
wishes: [],
valueWish: 0,
descriptionWish: "",
};
},
methods: {
addWish() {
if (!isNaN(this.valueWish) && this.valueWish > 0 && this.descriptionWish.length > 0) {
this.wishes.push(
`${this.descriptionWish} => ${this.valueWish.toLocaleString("pt-BR", { currency: "BRl", style: "currency" })}`
);
this.descriptionWish = "";
console.log("addWish");
this.valueWish = 0;
this.$emit("clear");
this.$refs.descriptionWish.focus();
}
this.valueWish = 0;
},
},
};
</script>
I still don't understand much about vueJS, but I believe it's something related to parent and child elements, but I've done numerous and I can't get my answer.
sorry for my bad english .
The emit sends an event from the child to the parent component not as you've done, to run a method from the child component you could add a ref in the component inside the parent one like :
<MoneyField title="Valor (R$): "
ref="moneyField" v-model="valueWish" #keyup.native.enter="addWish" />
then run this.$refs.moneyField.clearInputField() instead this.$emit("clear")

VueJS toggle password visibilty without mutating the "type" property

I have a basic input component I am working with that has type as a property and up until now has been working very well. However, trying to use it for passwords and implementing obfuscation has been sort of tricky.
How can I toggle hide/show of the password without mutating the prop? I figured making it type = 'password' to type = 'text was the best way, but clearly not.
I've made a Codesandbox to replicate that part of the component, but any advice or direction would be greatly appreciated!
PasswordInput.vue:
<template>
<div>
<input :type="type" />
<button #click="obfuscateToggle" class="ml-auto pl-sm _eye">
<div>
<img :src="`/${eyeSvg}.svg`" alt="" />
</div>
</button>
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
passwordVisible: false,
eyeSvg: "eye-closed",
};
},
props: {
type: { type: String, default: "text" },
},
methods: {
obfuscateToggle() {
if (this.eyeSvg === "eye-closed") {
this.eyeSvg = "eye";
} else this.eyeSvg = "eye-closed";
// this.eyeSvg = "eye-closed" ? "" : (this.eyeSvg = "eye");
if ((this.type = "password")) {
this.type = "text";
} else this.type = "password";
},
},
};
</script>
App.vue
<template>
<div id="app">
<PasswordInput type="password" />
</div>
</template>
The only way to do it is by mutating the type attribute. As that is how the browser decides the render it as either just a textbox or as a password. Therefore you are doing this the right way.
The one issue that you will encounter is that you will have errors thrown in your console because you are attempting to mutate a prop.
This is quick and easy to fix. First, you will create a new data property, and assign it to the default of type
data(){
return{
fieldType:'text'
}
}
Then you will use the on mounted lifecycle hook, and update your data property to match your prop's value`
mounted(){
this.fieldType = this.type;
}
If you know the type prop will change from the parent component you can also use a watcher for changes and assign type
watch:{
type(val){
this.fieldType = val;
}
}
You will then update your obfuscateToggle method to use the fieldtype variable:
obfuscateToggle() {
if (this.eyeSvg === "eye-closed") {
this.eyeSvg = "eye";
} else this.eyeSvg = "eye-closed";
//You can simplify this by using this.fieldType = this.fieldType == "text" ? "password" : "text"
if (this.fieldType == "password") {
this.fieldType = "text";
} else this.fieldType = "password";
}
Finally, in your template, you will want to change type to fieldType
<template>
<div>
<input :type="fieldType" />
<button #click="obfuscateToggle" class="ml-auto pl-sm _eye">
<div>
<img :src="`/${eyeSvg}.svg`" alt="" />
</div>
</button>
</div>
</template>
Putting it all together
<script>
export default {
name: "HelloWorld",
data() {
return {
passwordVisible: false,
eyeSvg: "eye-closed",
fieldType: "text"
};
},
props: {
type: { type: String, default: "text" },
},
methods: {
obfuscateToggle() {
if (this.eyeSvg === "eye-closed") {
this.eyeSvg = "eye";
} else this.eyeSvg = "eye-closed";
//You can simplify this by using this.fieldType = this.fieldType == "text" ? "password" : "text"
if (this.fieldType == "password") {
this.fieldType = "text";
} else this.fieldType = "password";
},
},
watch:{
type(val){
this.fieldType = val;
}
},
mounted(){
this.fieldType = this.type;
},
};
</script>
Here is an example on CodeSandBox
Also, you had a small typo in your obfuscateToggle method.
if(this.type = 'password')
this was assigning type instead of comparing it against a literal :)

Vue.js: #input for <input> not working with v-for

I am creating my own custom <input> Vue component. What I am doing is that the user can never enter the wrong type of input. For that I am using regex.test() at each input.
This is my code for my Vue component for taking an integer element or an integer array:
<template>
<div>
<label>{{ label }}
<template v-if="isArray">
<input
v-model="arr[i - 1]"
#input="filterInput"
:disabled="disableWhen"
v-for="i in arraySize"
:key="i">
</input>
</template>
<template v-else>
<input
v-model="num"
#input="filterInput"
:disabled="disableWhen">
</input>
</template>
</label>
<el-button
type="success"
icon="el-icon-check"
circle
#click="confirm"
:disabled="disableWhen">
</el-button>
</div>
</template>
<script>
export default {
props: {
label: String,
nonNegative: Boolean,
disableWhen: Boolean,
isArray: Boolean,
arraySize: Number
},
data() {
return {
num: '',
arr: []
}
},
methods: {
filterInput() {
if (this.nonNegative) {
if (!/^[0-9]*$/.test(this.num)) {
this.num = '';
}
} else if (!/^(-)?[0-9]*$/.test(this.num)) {
this.num = '';
}
},
confirm() {
if (this.isArray) {
let validArrayInput = true;
for (let i = 0; i < this.arraySize; i++) {
if (!this.validInput(this.arr[i])) {
validArrayInput = false;
}
}
if (validArrayInput) {
this.$emit('confirm', this.arr);
}
} else if (this.validInput(this.num)) {
this.$emit('confirm', this.num);
}
},
validInput(x) {
return (x !== '' && x !== '-' && typeof x !== "undefined");
}
}
}
</script>
The code is working correctly when isArray = false, that is, for integer elements. But the method filterInput is never being called when isArray = true, and there is no restriction for the wrong input. What is the problem?
filterInput is being called fine for both types of input but it only attempts to manipulate num, it doesn't change arr.
Here's my attempt at implementing this:
const MyInput = {
template: `
<div>
<label>{{ label }}
<template v-if="isArray">
<input
v-for="i in arraySize"
v-model="arr[i - 1]"
:disabled="disableWhen"
:key="i"
#input="filterInput"
>
</template>
<template v-else>
<input
v-model="num"
:disabled="disableWhen"
#input="filterInput"
>
</template>
</label>
</div>
`,
props: {
label: String,
nonNegative: Boolean,
disableWhen: Boolean,
isArray: Boolean,
arraySize: Number
},
data() {
return {
arr: []
}
},
computed: {
num: {
get () {
return this.arr[0]
},
set (num) {
this.arr[0] = num
}
}
},
methods: {
filterInput() {
const arr = this.arr
const re = this.nonNegative ? /^\d*$/ : /^-?\d*$/
for (let index = 0; index < arr.length; ++index) {
if (!re.test(arr[index])) {
this.$set(arr, index, '')
}
}
}
}
}
new Vue({
el: '#app',
components: {
MyInput
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<my-input label="Single"></my-input>
<br>
<my-input label="Multiple" is-array :array-size="3"></my-input>
</div>
A few notes:
I've changed num to be a computed property backed by arr[0]. This simplifies the filtering logic as it only has to consider arr for both types of input. It could be simplified further, e.g. the template doesn't really need to handle two cases, it could treat single-valued just the same as multi-valued but with array-size of 1. Only the value that's emitted (not included in my code) really needs to have different behaviour for the single-valued case. With a little refactoring num could probably be removed altogether.
The implementation is painfully stateful. You're going to run into difficulties if you ever want to pass in values from the outside.
Rather than setting the values to '' I would suggest just stripping out the disallowed characters using replace. I have not made this change in my code, I wanted to retain the behaviour from the original example.
Closing </input> tags are invalid and I have removed them.
There was a lot of duplication in your filterInput method that I've tried to remove. It now checks all the entries in the arr array. There didn't seem to be any need to target the specific input that had changed.
this.$set is used as it's updating an array by index, which otherwise would not be detected by the reactivity system (the standard caveat for manipulating arrays).

Bootstrap-vue input doesn't update value inside textbox when v-model is updated

I'm trying to create an input when user enters a value and if it's less than 100 then the value should automatically increased by 125.
I can make this work with simple input and vue's input event but with bootstrap-vue's input(b-form-input) sometimes it doesn't work.
Following is my code
<div id="app">
<div>
<b-form-input
type="number"
placeholder="For bootstrap-vue"
v-model="myObj.prop1"
#input="onProp1Change"/>
</div>
<div>
<input
type="number"
placeholder="Simple"
v-model="myObj.prop2"
#input="onProp2Change"/>
</div>
</div>
new Vue({
el: '#app',
data() {
return {
myObj: {
prop1: null,
prop2: null
}
};
},
methods: {
onProp1Change(value) {
console.log('prop1', value);
if (Number(value) < 1) {
return;
}
if (Number(value) < 100) {
this.myObj.prop1 = Number(value) + 125;
}
},
onProp2Change(event) {
let value = event.target.value;
console.log('prop2', value);
if (Number(value) < 1) {
return;
}
if (Number(value) < 100) {
this.myObj.prop2 = Number(value) + 125;
}
}
}
})
jsFiddle link
In bootstrap-vue input if you start with typing 5 it changes to 130 then if you hit backspace it changes to 138 but then if you hit backspace again it changes to 13, but it should stay at 138. How to make this work?
It works for me with a $nextTick:
if (Number(value) < 100) {
this.$nextTick(() => {
this.myObj.prop1 = Number(value) + 125;
});
}
The component was likely not done updating things before the new value came in. This waits until it has completed a sequence.