How to add to array from method in vue.js? - vue.js

I'm having a weird proplem that I can't understand
I have a registration form, and on any input I'm executing the method on blur;
<input class='form-control' placeholder='Username' #blur="watchVal" v-model="username">
Method
watchVal : function(){
if(this.username == ""){
this.errors['username'].push('Username is empty');
}
}
Data:
data: function(){
return {
username: "",
errors: {
'username' : []
}
}
}
When I blur without writing any value, nothing is added to this.errors['username'], unless I type a letter in any field.
I've also tried to make validation on submit, but found same problem that no error is added to the array unless I type in any input,
Can anyone explain to me what I am doing wrong??

I faced similar issue. How I solved this.
your data:
data: function(){
return {
username: "",
errors: {}
}
}
your Method
watchVal (key) {
let errors = this.errors
if (this[key] === '') {
errors[key].push('Emai empty')
}
this.errors = errors
}
your HTML
<input class='form-control' placeholder='Username' #blur="watchVal('username')" v-model="username">
<p>{{errors['username']}}</p>
You must display error variable in your HTML template then it will be solved.

Your source is working. See in full mode if you cannot see error messages.
new Vue({
el: "#app",
data: function() {
return {
username: '',
errors: {
username: []
}
}
},
methods: {
watchVal : function(){
if(this.username == ""){
this.errors['username'].push('Username is empty');
}
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input class='form-control' placeholder='Username' #blur="watchVal" v-model="username">
<p class="text-sm text-danger" v-for="(error, index) in errors.username" :key="index">
{{ error }}
</p>
</div>

Related

How to make a list containing checkboxes attached to object in a v-for directive switch state safely (W/O switching other checkboxes in the list)?

I'm trying to make a client-side-only todolist that uses VueJS (2.x). Here is my HTML:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>To-do List</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
</head>
<body>
<div id="app">
<h1>To-do List</h1>
<h2>Completed Tasks!</h2>
<ul>
<li v-for="item in complete">{{ item.description }}<input type="checkbox" :checked="item.done" #change="item.done = false"></li>
</ul>
<h2>Uncompleted Tasks!</h2>
<ul>
<li v-for="item in uncomplete">{{ item.description }}<input type="checkbox" :checked="item.done" #change="item.done = true"></li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.7.13/dist/vue.js"></script>
<script type="module" src="scripts/app.js"></script>
</body>
</html>
Then in scripts/app.js I did this:
'use strict'
let app = new Vue({
el : "#app",
data : {
tasks: [
{ description: 'Say Hello', done: true },
{ description: 'Review Code', done: false },
{ description: 'Read Emails', done: false },
{ description: 'Reply to Emails', done: false },
{ description: 'Wash The Dishes', done: true },
{ description: 'Stop Working', done: true },
]
},
computed : {
complete : function() {
return this.tasks.filter(task => task.done === true);
},
uncomplete : function() {
return this.tasks.filter(task => task.done === false);
}
}
});
My issue is simple: when I change the state of a checkbox (checking it or unchecking it) in a given list, the checkbox that directly follows it switches state as well.
I can't figure out why this happens and how to fix it. If one can tell me why this happens (so that I don't have to come back here whenever I have a misbehaving v-for/switch-state) as well as how to fix it, that would be great!
first of all. You'd better use Function in data instead of Object, or it may cause an update error.
Since Function is accepted only when used in a component definition.
// wrong
data: {
tasks: []
}
// correct
data() {
return {
task: []
}
}
You may not change the computed attribute directly which only has a Computed Getter in default. If you want to handle the computed attribute, give a Computed Setter to it.
// wrong
computed: {
complete(){
return this.tasks.filter(task => task.done === true);
},
}
// right
computed: {
complete: {
get() {
return this.tasks.filter(task => task.done === true);
},
set(value) {
this.task.forEach((task, index) => {
let matched = value.find(({ description }) => description === task.description);
if(matched) {
this.task[index] = matched;
}
});
}
}
}
you can't bind the value via v-for, because vue2 can't recognize the changes in the array.
<!-- wrong -->
<li
v-for="item in complete">
{{ item.description }}
<input
type="checkbox"
:checked="item.done"
#change="item.done = false">
</li>
<!-- correct -->
<li
v-for="(item, index) in complete">
{{ item.description }}
<input
type="checkbox"
:checked="item.done"
#change="complete[index].done = false">
</li>
new Vue({
el: "#app",
data() {
return {
tasks: [
{ description: 'Say Hello', done: true },
{ description: 'Review Code', done: false },
{ description: 'Read Emails', done: false },
{ description: 'Reply to Emails', done: false },
{ description: 'Wash The Dishes', done: true },
{ description: 'Stop Working', done: true },
]
};
},
computed : {
complete: {
get() {
return this.tasks.filter(task => task.done === true);
},
set(value) {
this.task.forEach((task, index) => {
let matched = value.find(({ description }) => description === task.description);
if(matched) {
this.task[index] = matched;
}
});
}
},
uncomplete: {
get() {
return this.tasks.filter(task => task.done === false);
},
set(value) {
this.task.forEach((task, index) => {
let matched = value.find(({ description }) => description === task.description);
if(matched) {
this.task[index] = matched;
}
});
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h1>To-do List</h1>
<h2>Completed Tasks!</h2>
<ul>
<li
v-for="(item, index) in complete">
{{ item.description }}
<input
type="checkbox"
v-model="complete[index].done">
</li>
</ul>
<h2>Uncompleted Tasks!</h2>
<ul>
<li
v-for="(item, index) in uncomplete">
{{ item.description }}
<input
type="checkbox"
v-model="uncomplete[index].done">
</li>
</ul>
</div>

Conditional rendering after function - VueJS [duplicate]

This question already has answers here:
Use arrow function in vue computed does not work
(6 answers)
Closed 1 year ago.
I am currently writing a template for sending an email and after the function I want to conditionally render a success or error block. For some reason it is not working. The function itself is working, however neither success or error block is rendered. Please find my code below.
Template:
<form v-if="success==null" #submit.prevent="sendEmail" >
... //form code
<input name="submit" type="submit" class="btn" value="send" />
</form>
<b-alert variant="success" v-if="success">success</b-alert>
<b-alert variant="error" v-if="!success">error</b-alert>
Function:
data() {
return {
success: null
}
},
methods: {
sendEmail: (e) => {
... // request code
.then((result) => {
this.success=true
console.log('success')
}, (error) => {
this.success=false
console.log('error')
})
}
}
I think you just need to use strict comparing for error block
like:
success === false
There can be problems with !success because it's true for both null and false.
Also you can use string instead of boolean|null
var app = new Vue({
el: '#app',
data: {
type: 'form',
formData: {
email: '',
}
},
methods: {
sendEmail() {
if (this.formData.email) {
this.type = 'success';
} else {
this.type = 'error';
}
setTimeout(() => this.type = 'form', 10000);
}
}
})
.success {
color: green;
}
.error {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<form v-if="type === 'form'" #submit.prevent="sendEmail">
<input type="email" v-model="formData.email" />
<button type="submit">Submit</button>
</form>
<div v-if="type === 'success'" class="success">Success!</div>
<div v-if="type === 'error'" class="error">Error!</div>
</div>
Are you sure your success/error handler are called ?/
In your method sendEmail you should define define it without an arrow function
sendEmail (e) {
... // request code
.then((result) => {
this.success=true
console.log('success')
}, (error) => {
this.success=false
console.log('error')
})
}
Try this
<b-alert :variant="success ? 'success' : 'error'" v-if="success!=null" :key="success">
<span v-if="success">success</span>
<span v-else>error</span>
</b-alert>

What is the opposite of $set in Vue.js?

In a blog app, I'd like to show/hide comments of each post inside a loop of posts. I know how to show the div containing the comments by setting a showComments on the fly:
this.$set(post, 'showComments', true) ;
But I don't know how to hide the post's comments when the div is already open. What I tried is this:
if (this.$get(post, 'showComments')==true) {
this.$set(post, 'showComments', false) ;
return
}
The code above thoes not work and I get this error:
Uncaught TypeError: this.$get is not a function
So I'm wondering how can I acheive this functionaliry.
You should be able to simply read the dynamic property and reapply the value.
new Vue({
el: '#app',
data() {
return {
posts: [
{ content: 'Post #1' },
{ content: 'Post #2' },
{ content: 'Post #3' }
]
}
},
methods: {
toggleComment(post) {
if ('showComment' in post) {
post.showComment = !post.showComment;
}
else {
this.$set(post, 'showComment', true);
}
}
}
})
.post {
background-color: lightgreen;
margin: 10px 0;
padding: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="post" v-for="(post, index) in posts" :key="index">
{{post.content}}
<p v-if="post.showComment">
Hidden comments.
</p>
<button #click="toggleComment(post)">Toggle comment</button>
</div>
</div>
Use property name to get property value
if ( typeof this.post.showComments !== 'undefined' && this.post.showComments ) {
Vue.set(post, 'showComments', false);
return;
}
Also note that you should try to avoid using this.$set because it was deprecated due to conflicts with other libraries. Consider using Vue.set instead.
https://v2.vuejs.org/v2/api/#Vue-set

Can I execute a method when an input box gets to a certain length?

I have an input box that takes a string. Can I execute a method (in vue.js) when the length of the string gets to a certain number?
something like
<input v-if="inputBox.length == 6 THEN runme()"...>
You can use watch option, you'll be able to react to data changes :
new Vue({
el: '#root',
data: {
message: '',
inputLength : undefined
},
methods : {
doSomething(){
console.log('I did it !')
}
},
watch :{
message : function(val) {
if(val.length>=5){
this.inputLength = val.length
this.doSomething();
}
}
}
})
.container {
padding-top: 2em;
}
.intro {
font-size: 1.5em;
margin-bottom: 1.5em;
}
.input-value {
margin-top: 1em;
font-size: 1.25em;
}
.highlight {
color: #00d1b2;
font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div class="container">
<h1 class="intro">Binding with Vue</h1>
<div id='root' class="box">
<label class="label">Enter text here</label>
<input class="input is-medium" type='text' id='input' v-model='message'>
<p class="input-value">The value of the input is: <span class="highlight">{{ inputLength }}</span></p>
</div>
</div>
In this example, if input length is >= 5 then it will change the inputLenght value in data and execute a method.
For more informations about this, go see :
https://v2.vuejs.org/v2/guide/computed.html#Watchers
You can use a watcher to trigger a method when the string exceeds the length:
new Vue({
data () {
return {
model: ''
}
},
watch: {
model: {
handler: function (value) {
if (value.length >= 6) {
this.trigger()
}
}
}
},
el: '#app',
methods: {
trigger () {
alert('hi there')
}
},
template: `<input v-model="model">`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

Vuejs reactive v-if template component

I'm struggling to understand how to make my component reactive. At the moment the button is rendered correctly but once the create/delete event happens, the template does not change. Any tips on how to update the component after the event has taken place?
new Vue({
el: '#app'
});
Vue.component('favourite-button', {
props: ['id', 'favourites'],
template: '<input class="hidden" type="input" name="_method" value="{{ id }}" v-model="form.listings_id"></input><button v-if="isFavourite == true" class="favourited" #click="delete(favourite)" :disabled="form.busy"><i class="fa fa-heart" aria-hidden="true"></i><button class="not-favourited" v-else #click="create(favourite)" :disabled="form.busy"><i class="fa fa-heart" aria-hidden="true"></i></button><pre>{{ isFavourite == true }}</pre>',
data: function() {
return {
form: new SparkForm({
listings_id: ''
}),
};
},
created() {
this.getFavourites();
},
computed: {
isFavourite: function() {
for (var i = 0; this.favourites.length; i++)
{
if (this.favourites[i].listings_id == this.id) {
return true;
}
}
},
},
methods: {
getFavourites() {
this.$http.get('/api/favourites')
.then(response => {
this.favourites = response.data;
});
},
create() {
Spark.post('/api/favourite', this.form)
.then(favourite => {
this.favourite.push(favourite);
this.form.id = '';
});
},
delete(favourite) {
this.$http.delete('/api/favourite/' + this.id);
this.form.id = '';
}
}
});
Vue.component('listings', {
template: '#listing-template',
data: function() {
return {
listings: [], favourites: [],
};
},
created() {
this.getListings();
},
methods: {
getListings() {
this.$http.get('/api/listings')
.then(response => {
this.listings = response.data;
});
}
}
});
Vue expects HTML template markups to be perfect. Otherwise you will run into multiple issues.
I just inspected your template and found an issue - the first <button> element does not close.
Here is the updated version of your code:
Vue.component('favourite-button', {
props: ['id', 'favourites'],
template: `
<input class="hidden" type="input" name="_method" value="{{ id }}" v-model="form.listings_id"></input>
<button v-if="isFavourite == true" class="favourited" #click="delete(favourite)" :disabled="form.busy">
<i class="fa fa-heart" aria-hidden="true"></i>
</button> <!-- This is missing in your version -->
<button class="not-favourited" v-else #click="create(favourite)" :disabled="form.busy">
<i class="fa fa-heart" aria-hidden="true"></i>
</button>
<pre>{{ isFavourite == true }}</pre>
`,
...
Note the comment on 7th line above, the closing </button> tag is not present in your template.
As a side note, if you do not want to type back-slash at the end of every line to make multi-line template strings, you can use back-ticks as shown in my code example above. This will help you avoid markup errors leading to Vue component issues and many hours of debugging.
Another reference: Check out "Multi-line Strings" in this page: https://developers.google.com/web/updates/2015/01/ES6-Template-Strings
Relevant lines (copied from above page):
Any whitespace inside of the backtick syntax will also be considered part of the string.
console.log(`string text line 1
string text line 2`);
EDIT: Found a possible bug in code
Here is another issue in your create method of favourite-button component:
methods: {
// ...
create() {
Spark.post('/api/favourite', this.form)
.then(favourite => {
this.favourite.push(favourite); // Note: This is the problem area
this.form.id = '';
});
},
//...
}
Your success handler refers to this.favourite.push(...). You do not have this.favourite in data or props of your component. Shouldn't it be this.favourites?