show/hide elements inside of for loop - vue.js

I have the following:
<div v-for="num in [1,2,3,4,5]" :key="num ">
<customelement0 :num ="num" />
<span #click="show = !show" > Details: </span>
<customelement1 v-if="show" :num ="num" />
<hr />
</div>
and:
export default {
data() {
return {
show: false
};
},
};
However, in this implementation Whenever show changes it affects all of the customelement1s and will show/hide all of them.
How would one solve this problem, so that whenever a user clicks on the span it only shows/hides one element in the loop?
P.S.: in reality, the length of the loop is much longer, than what's indicated above

The problem here is essentially that your show is a variable for the entire component, and isn't linked to one of your array elements.
Generally, we don't really tend to hard-code an array into the html, but rather in the data, as shown in the other answers.
The other answers show it with num being coded into the object but you could also do something like this. Note that other field is optional and not required. The main advantage of this method is that you don't need to code every number.
<div v-for="(item, i) in items" :key="i">
<!-- Note I do i+1 so that it matches the 1,2,3 in your example -->
<!-- if you're okay with having 0,1,2 you can omit the +1 -->
<customelement0 :num="i+1" />
<span #click="item.show = !item.show" >Details: </span>
<customelement1 v-if="item.show" :num="i+1" />
<hr />
</div>
export default {
data() {
return {
items: [
{show: false, other: "foo"},
{show: false, other: "bar"},
//...
],
};
},
};

You can change number to object and toggle property:
<div id="app">
<h2>Todos:</h2>
<ol>
<li v-for="todo in todos">
<label>
<input type="checkbox"
v-on:change="toggle(todo)"
v-bind:checked="todo.done">
<del v-if="todo.done">
{{ todo.text }}
</del>
<span v-else>
{{ todo.text }}
</span>
</label>
</li>
</ol>
</div>
new Vue({
el: "#app",
data: {
todos: [
{ text: "1", done: false },
{ text: "2", done: false },
{ text: "3", done: true },
{ text: "4", done: true },
{ text: "5", done: true },
]
},
methods: {
toggle: function(todo){
todo.done = !todo.done
}
}
})

You can to save the value isShow for each element in the array. So you need to use an array of objects in you data and do something like this:
export default {
data() {
return {
numList: [
{
num: 1,
isShow: true
},
{
num: 2,
isShow: true
}
]
};
},
};
In your template:
<div v-for="item in numList" :key="item.num">
<customelement0 :num="item.num" />
<span #click="item.isShow = !item.isShow" > Details: </span>
<customelement1 v-if="item.isShow" :num="item.num" />
<hr />
</div>

Related

Including text when an image/icon is shown

At the moment I am trying to include different text when an image and/or icon shows on the page. Here is the code for the vue file:
<template>
<div class="profile">
<div
:class="{
'flags--relevant': hasFlagType('medication'),
'flags--active': flag == 'medication'
}"
class="flags"
#click="setFlag('medication')"
>
<medication-icon
:class="[
hasFlagType('medication')
? 'medication-icon--focus'
: 'medication-icon--blur',
]"
/>
</div>
<div
:class="{
'flags--relevant': hasFlagType('condition'),
'flags--active': flag == 'condition'
}"
class="flags"
#click="setFlag('condition')"
>
<treatment-icon
:class="[
hasFlagType('condition')
? 'treatment-icon--focus'
: 'treatment-icon--blur',
]"
/>
</div>
<div
:class="{
'flags--relevant': hasFlagType('translator'),
'flags--active': flag == 'translator',
}"
class="flags"
#click="setFlag('translator')"
>
<foreign-dialect-icon
:class="[
hasFlagType('translator')
? 'foreign-dialect-icon--focus'
: 'foreign-dialect-icon--blur',
]"
/>
</div>
</div>
</template>
<script>
export default {
components: {
ForeignDialectIcon,
MedicationIcon,
TreatmentIcon,
},
props: {
userFlags: {
type: Array,
default() {
return {};
},
},
},
data() {
return {
flags: this.userFlags,
flag: null,
title: "Requires daily medication",
title2: "Specialist health condition",
title3: "Requires a translator",
};
},
methods: {
hasFlagType(flagType) {
return this.flags[flagType] !== undefined;
},
setFlag(flagType) {
if (this.hasFlagType(flagType)) {
this.flag = flagType;
}
},
resetFlag() {
this.flag = null;
},
},
};
</script>
I have tried outputting the titles in the data section for each icon and they still show even if the icon doesn't show. I need it to output the title when the image is shown and the many attempts I've tried haven't worked so was wondering how I am able to solve this?
Assuming the title should only appear when the corresponding icon is focused, you could use the same condition (hasFlagType(...)) with v-if to render the title:
<div>
<medication-icon .../>
<span v-if="hasFlagType('medication')">{{ title }}</span>
</div>
<div>
<treatment-icon .../>
<span v-if="hasFlagType('condition')">{{ title2 }}</span>
</div>
<div>
<foreign-dialect-icon .../>
<span v-if="hasFlagType('translator')">{{ title3 }}</span>
</div>

How to toggle between DIVs with bound data in Vue?

So the problem I'm facing is that I have a template that fetches data from a Realtime Firebase Database and at the same time the user can import more data through an input element. I'm using Vue.js and I need the data to be bound to each other.
Here is my template:
<template>
<ul>
<li>
<input type="text" v-model="email" v-on:keyup.enter="addData()"/>
<img #click="addData()" src="#/assets/Plus.png" />
</li>
</ul>
<ul>
<li v-for="(item, key) in emails" :key="key">
<div>
<p>{{ item.data }}</p>
<img #click="deleteDataByKey(key)" src="#/assets/Delete.png" />
</div>
<div class="first">
<input type="text" v-model="comment[key]" v-on:keyup.enter="addComment(key, comment[key])"/>
<img #click="addComment(key, comment[key])" src="#/assets/Plus.png" />
</div>
<div class="second">
<p>{{ comment[key] }}</p>
<img #click="deleteCommentByKey(key)" src="#/assets/Delete.png" />
</div>
</li>
</ul>
</template>
Now what is happening is that I want to show <div class="first"> when there is no comment and when there is one, <div class="second"> should be shown while hiding the first one.
I tried using v-if="comment[key]" but it will toggle the divs straight away.
I also tried to v-model.lazy which seems to be working but then the method to update the db is not called.
I tried using pure JS in the method to change the HTML but it doesn't seem to be working as well.
These are my methods and data:
data() {
return {
emailList: [],
email: "",
comment: []
};
},
addData() {
db.ref("emailItems").push({
data: data
});
this.email = "";
this.fetchData();
},
deleteDataByKey(key) {
db.ref("emailItems"+key).remove();
this.fetchData();
},
addComment(key, comment) {
db.ref(`emailItems/${key}/comment`).set(comment);
},
deleteCommentByKey(key){
db.ref("comment/"+key).remove();
this.fetchData();
},
fetchData() {
db.ref("emailItems")
.once("value")
.then(snapshot => {
this.emailList = snapshot.val().emailItems;
});
}
And the db structure looks like this
Any help would be highly appreciated...
I think you should build more on the (arguably) biggest features of Vue, namely: reactivity & components.
Break down the logic a bit more, until you arrive at elements that do only one thing - those elements can be your components. Then build up the business logic from those atomic components.
Vue.component('NewEmail', {
data() {
return {
email: null,
}
},
methods: {
handleKeyup() {
this.$emit("add-email", {
email: this.email,
comment: null
})
this.email = null
}
},
template: `
<div>
<label>
NEW EMAIL: <input
type="email"
placeholder="Type in an email"
v-model="email"
#keyup.enter="handleKeyup"
/>
</label>
</div>
`
})
Vue.component('SingleEmailRow', {
props: {
email: {
type: Object,
default: null,
}
},
methods: {
handleDeleteClick() {
this.$emit('remove-email', this.email)
},
},
template: `
<li
class="d-flex"
>
<div>
{{ email.email }}
</div>
<div>
<button
#click="handleDeleteClick"
>
X
</button>
</div>
<component
:is="email.comment ? 'HasEmailComment' : 'NoEmailComment'"
:email="email"
v-on="$listeners"
></component>
</li>
`
})
Vue.component('HasEmailComment', {
props: {
email: {
type: Object,
required: true
}
},
methods: {
handleUpdateComment() {
this.$emit('update:comment', { ...this.email,
comment: null
})
}
},
template: `
<div
class="d-flex"
>
<div>
{{ email.comment }}
</div>
<button
title="Remove comment"
#click="handleUpdateComment"
>
-
</button>
</div>
`
})
Vue.component('NoEmailComment', {
props: {
email: {
type: Object,
required: true
}
},
data() {
return {
comment: null,
}
},
methods: {
handleUpdateComment() {
this.$emit('update:comment', { ...this.email,
comment: this.comment
})
}
},
template: `
<div
class="d-flex"
>
<div>
<input
v-model="comment"
type="text"
placeholder="Write a comment"
/>
</div>
<button
title="Add comment"
#click="handleUpdateComment"
>
+
</button>
</div>
`
})
new Vue({
el: "#app",
data() {
return {
emailList: [],
}
},
methods: {
handleAddEmail(newEmail) {
if (!this.emailList.find(({
email
}) => email === newEmail.email)) {
this.emailList.push(newEmail)
}
},
handleRemoveEmail(toRemove) {
this.emailList = this.emailList.filter(({
email
}) => {
return email !== toRemove.email
})
},
handleUpdateComment(newEmail) {
this.emailList = this.emailList.map(email => {
if (email.email === newEmail.email) {
return newEmail
} else {
return email
}
})
}
}
})
.d-flex {
display: flex;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<new-email #add-email="handleAddEmail"></new-email>
<ul v-for="(email, i) in emailList" :key="i">
<single-email-row :email="email" #remove-email="handleRemoveEmail" #update:comment="handleUpdateComment"></single-email-row>
</ul>
</div>
OK, the comment handling in SingleEmailRow could be put to its separate component (based on my thoughts above).
The main points in the snippet above are:
there's only one original data source (emailList in the root component) that is passed down as props where needed, and all the other components & functions just manipulate THAT list via events (reactivity is great!)
because all the components are based on one central data source, they just have to work with the item they get as props. (Ok, they have some internal state, but hey - this is only a snippet! :) )
the two components have only one responsibility:
NewEmail: to add an item to the central emailList
SingleEmailRow: to manage the data in ONE email item
I hope this helps you a bit to reach a solution for your problem.
EDIT: UPDATING SNIPPET
I had to update the snippet, because I wasn't satisfied with it.
Modifications:
adding two new components: HasEmailComment, NoEmailComment
updated SingleEmailRow with the two new components
Now, all the components have only one task to do.

V-model same value in loop

I have created loops for some v-select. When I enter v-model it makes my v-select the same value. How i can fix this?
<template>
<div class="create-group">
<div class="form-group">
<button class="btn btn-defualt addGroup" v-on:click="addmember(member.value)">
<h3>Crate Group</h3>
</button>
<div class="member">
<div class="row">
<div class="col-lg-2" style="padding-right: 0;">
<h3>member in grroup :</h3>
{{member}}
</div>
<div class="col-lg-10 list" v-bind:class="{'list-member' : listActive}">
<div class="input-member" v-for="list in lists" v-bind:key="list.id">
<h3>{{list.count}}.</h3>
<div class="select">
<v-select :options="options" v-model="member"></v-select>
</div>
</div>
<div class="add-member" v-on:click="add()">
<h4>+ add member</h4>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
My data:
data() {
return {
member: [],
newitem: { name: "", type: [] },
options: [],
lists: [{ count: "1" }, { count: "2" }, { count: "3" }],
};
},
You are assigning selected value to member that is the same between all selectors. You need to use different variables for different lists, like this:
<v-select :options="options" v-model="member[list.id]"></v-select>
data() {
return {
member: {}, // <- This is object now
newitem: { name: "", type: [] },
options: [],
lists: [{ count: "1" }, { count: "2" }, { count: "3" }],
};
},

How do i get the v-model values of a v-for components if i iterate with only numbers?

I have v-for form-group components i iterated over a select's value(integer). I want to get the values of the iterated v-models, but i just can't seem to get it right
TEMPLATE
<b-form-group
id="input-group-1"
label="Jumlah Lowongan:"
label-for="selectJumlahLow"
description="Silahkan pilih satu."
v-if="show"
>
<b-form-select id="selectJumlahLow" v-model="form.jumlahlow" :options="selow" required></b-form-select>
</b-form-group>
<b-form-group label="Nama Lowongan:" v-for="n in parseInt(form.jumlahlow)" :key="n">
<b-form-input required placeholder="Masukkan nama lowongan" v-model="low"></b-form-input>
</b-form-group>
SCRIPT DATA
data() {
return {
form: {
jumlahlow: 1,
checked: [],
low: []
}
}
I've tried changing the model to low[n] or declaring low in data as object {} but either of these seems to be undefined according to TypeErrors i've encoutered.
How am i suppose to get the low[n]'s values?
EDIT:
Here is the full code:
<template>
<div>
<b-form #submit="onSubmit" #reset="onReset">
<b-form-group
id="input-group-1"
label="Jumlah Lowongan:"
label-for="selectJumlahLow"
description="Silahkan pilih satu."
v-if="show"
>
<b-form-select id="selectJumlahLow" v-model="form.jumlahlow" :options="selow" required></b-form-select>
</b-form-group>
<b-form-group label="Nama Lowongan:" v-for="n in parseInt(form.jumlahlow)" :key="n">
<b-form-input required placeholder="Masukkan nama lowongan" v-model="low"></b-form-input>
</b-form-group>
<b-button type="submit" variant="primary">
{{ buttonText }}
<i class="material-icons">arrow_forward_ios</i>
</b-button>
<b-button type="reset" variant="danger">Reset</b-button>
</b-form>
<b-card class="mt-3" header="Form Data Result">
<pre class="m-0">{{ form }}</pre>
</b-card>
</div>
</template>
<script>
export default {
name: "lowonganForm",
data() {
return {
form: {
jumlahlow: 1,
checked: [],
low: []
},
selow: [
{ text: "Pilih Satu", value: null, disabled: true },
1,
2,
3,
4,
5,
6
],
show: true,
target: false,
buttonText: "Next"
};
},
methods: {
onSubmit(evt) {
evt.preventDefault();
alert(JSON.stringify(this.form));
// if (this.jumlahlow !== null || !this.jumlahlow < 1) {
// this.show = false;
// }
},
onReset(evt) {
evt.preventDefault();
// Reset our form values
this.form.jumlahlow = null;
this.form.checked = [];
// Trick to reset/clear native browser form validation state
this.show = false;
this.$nextTick(() => {
this.show = true;
});
}
},
computed: {}
};
</script>
You should try to model your data for how you want the view to be rendered. If you want to have a list of input boxes, then the data for those inputs should be defined in an array that is prepopulated with those items, or when you need to adjust the number of items you should add those data items to the array. You'll avoid reactivity problems this way too.
Here's an example of what I mean:
new Vue({
el: '#app',
data: {
maxCount: 5,
count: 3,
items: [],
data: '',
},
computed: {
visibleItems() {
return this.items.slice(0, this.count)
}
},
created() {
// Define the data upfront so it will be reactive
for (let i = 0; i < this.maxCount; i++) {
this.items.push({
firstName: '',
lastName: '',
})
}
},
methods: {
submit() {
// Transform the data into some JSON that is
// compatible with your API
const data = this.visibleItems.map(item => ({
first_name: item.firstName,
last_name: item.lastName,
role: 'user',
}))
this.data = JSON.stringify(data, null, ' ')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
Number of people:
<select v-model="count">
<option v-for="i of maxCount" :value="i">{{ i }}</option>
</select>
</div>
<div v-for="item of visibleItems">
<input placeholder="First name" v-model="item.firstName">
<input placeholder="Last name" v-model="item.lastName">
</div>
<button #click="submit">Submit</button>
<pre>{{ data }}</pre>
</div>
Try this example.
<div id="app">
<div>
<select v-model="jumlahlow">
<option v-for="i in selects" :key="i">{{ i }}</option>
</select>
</div>
<div v-for="num, index in parseInt(jumlahlow)">
<input v-model="lows[index].value" />
</div>
</div>
And JS
new Vue({
el: '#app',
data: {
lows: [
{
value: ''
}
],
jumlahlow: 1,
selects: [
1,
2,
3,
4,
5,
6
]
},
watch: {
jumlahlow: function (val) {
this.lowsTmp = this.lows;
this.lows = [];
for (let i = 0; i < val; i++) {
const currentVal = typeof this.lowsTmp[i] !== 'undefined' ? this.lowsTmp[i].value : '';
this.addLow(currentVal);
}
}
},
methods: {
addLow: function(val) {
this.lows.push({ value: val });
}
}
})
Directly check here: https://jsfiddle.net/abinhho/m3c8r4tj/2/
you are iterating v-for="n in parseInt(form.jumlahlow)" but that's an Object and v-for works on array not on objects.
Here you can use array of objects to iterate, for example:-
form: [{
jumlahlow: 1,
checked: [],
low: []
}]
and after that you will have to write v-for="n in form" then try accessing low by form.low

How to get checked value of checkbox if use array as a model in vue

I wonder if I use array as model for multiple-checkbox list, how can I check which items is checked and which are unchecked efficiently rather than compare one by one with that array?
<ul>
<li v-for="task in tasks">
<input type="checkbox" :id="task.title" :value="task" v-model="selectedTasks" #change="handleTasks(task)">
<label :for="task.title">{{task.title}}</label>
</li>
</ul>
new Vue({
data: {
tasks: [
{ title: 'Go to the store' },
{ title: 'Clean the house' },
{ title: 'Learn Vue.js' }
],
selectedTasks: []
},
})
You could add a property to each task (e.g., checked) and bind that to each input's v-model, making it trivial in code to check whether a task is checked/selected:
new Vue({
el: '#app',
data() {
return {
tasks: [
{ title: 'Go to the store', checked: false },
{ title: 'Clean the house', checked: false },
{ title: 'Learn Vue.js', checked: false }
]
};
},
methods: {
clearCheckedTasks() {
this.tasks = this.tasks.filter(x => !x.checked);
}
}
})
<script src="https://unpkg.com/vue#2.5.16"></script>
<div id="app">
<ul>
<li v-for="task in tasks">
<input type="checkbox" :id="task.title" v-model="task.checked">
<label :for="task.title">{{task.title}}</label>
</li>
</ul>
<button #click="clearCheckedTasks">Clear checked tasks</button>
<h3>tasks (live)</h3>
<pre>{{tasks}}</pre>
</div>
based on your comment that "I also want to know if one item is checked or unchecked when I click on it", I would use the DOM Event object to detect if it's checked.
demo: https://jsfiddle.net/jacobgoh101/m480bupd/6/
add #click="clickHandler" to the input
<input type="checkbox" :id="task.title" :value="task" v-model="selectedTasks" #change="handleTasks(task)" #click="clickHandler">
use e.target.checked to get the checked
methods: {
clickHandler(e) {
console.log(e.target.checked);
},
// ...
}
Use the loop index:
<li v-for="(task, index) in tasks">
<input type="checkbox" :id="task.title" :value="task" v-model="selectedTasks[index]" #change="handleTasks(task)">
<label :for="task.title">{{task.title}}</label>
</li>