vue #click not changing class - vue.js

sorry I'm still new to reactive vue models and updating I'm trying to draw a line through an input element if a box is checked.
so far I have this setup:
data: {
selected: null,
checked: null,
list: [
{
id: 0,
category: 'Bakery',
food: ['bread','muffins','pie']
},
{
id: 1,
category: 'Fruits',
food: ['apple','bananna','orange']
},
{
id: 2,
category: 'Diary',
food: ['cheese','milk','yogurt']
}
],
isHidden: true,
form: {},
},
my html is as follows (this is a single page app)
<li v-for="food in item.food" class="list-group-item">
<input :class="{marked:food == checked}" #click="checked = food" type="checkbox"> {{ food }}</input>
</li>
this is the css i'm trying to implement
.marked{
text-decoration: line-through;
}
I'm not sure what I need to do to my #click to make it work but so far nothing happens and the class is not applied. Can someone give me tips?

An <input> element can't have content.
So you've got this structure:
<input>{{ food }}</input>
But that's misleading as the <input> tag will be closed automatically. What you'll end up with will actually be some text next to a checkbox, not inside it.
You probably want something similar to this:
<label :class="{marked: food === checked}" #click="checked = food">
<input type="checkbox"> {{ food }}
</label>
There are some other problems involving deselection and multiple selections. I've tried to fix those in the example below:
new Vue({
el: '#app',
data: {
checked: [],
list: [
{
id: 0,
category: 'Bakery',
food: ['bread','muffins','pie']
},
{
id: 1,
category: 'Fruits',
food: ['apple','bananna','orange']
},
{
id: 2,
category: 'Diary',
food: ['cheese','milk','yogurt']
}
]
}
})
.marked{
text-decoration: line-through;
}
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<ul v-for="item in list">
<li v-for="food in item.food" class="list-group-item">
<label :class="{marked: checked.includes(food)}">
<input type="checkbox" v-model="checked" :value="food">{{ food }}
</label>
</li>
</ul>
</div>

Related

"prop" is undefined though it's being passed correctly

I've been following VueJS official documentation on passing data to child components with props; though I'm not working with a string template. I'm aware about what happens when your prop is camel case; you should write it as kebab case.
Nevertheless, this is not the case since it's all lowercase and won't work.
I'm using nuxt and I've separated my work into files, which are:
<template>
<div class="row">
<input type="text" name="" id="" placeholder="Write your question" v-model="text">
<select v-model="selectedField">
<option v-for="option in options" :key="option.id" :value="option.value">
{{ option.text }}
</option>
</select>
<button class="btn btn-sm btn-primary" #click="$emit('add-field')"
v-bind:class="{ disabled: ($parent.count <= 1 && $parent.count == identifier) }">
>{{identifier}}</button>
<button class="btn btn-sm btn-danger" #click="$emit('delete-field')">-</button>
</div>
Now for its JS file:
export default {
data () {
return {
options: [
{
id: 1,
value: 1,
text: "Radio"
},
{
id: 2,
value: 2,
text: "Rate"
},
{
id: 3,
value: 3,
text: "Text"
}
],
props: ['identifier'],
selectedField: 1,
text: "",
}
},
}
Now, for my parent component:
<template>
<div class="offset-md-3" id="qz">
<form-maker
v-for="item in questions" :key="item._id"
v-on:add-field="addField()"
v-on:delete-field="deleteField(item._id)"
v-bind:identifier="item._id" <<--What I want to set
ref="child"
></form-maker>
<button #click="saveForm" class="btn btn-large btn-success">SAVE</button>
</div>
</template>
Finally:
var vm = null;
export default {
layout: 'admin',
components: {
formMaker
},
data() {
return {
count: 1,
questions: [{
_id: 1//static
}]
}
},
}
What I'm trying to do is, to use the prop for some validations, nevertheless it throws the next error:
Property or method "identifier" is not defined on the instance but
referenced during render. Make sure that this property is reactive,
either in the data option, or for class-based components, by
initializing the property.
Thank you.
Here is where you go wrong. Props should not be in data(). See the code snippet below
<script>
export default {
props: ['identifier'],
data() {
return {
options: [
{
id: 1,
value: 1,
text: "Radio"
},
{
id: 2,
value: 2,
text: "Rate"
},
{
id: 3,
value: 3,
text: "Text"
}
],
selectedField: 1,
text: "",
}
}
}
</script>

Strange behaviour of collection-validation in vuelidate

I have made the following minimal example, which can also be tested in this fiddle:
HTML:
<div id="app">
<div v-for="(gameV, index) in $v.games.$each.$iter">
<input type="text" :class="{error: gameV.$error, dirty: gameV.$dirty}" v-model="gameV.name.$model" />
<input type="button" value="-" #click="games.splice(index, 1)" style="cursor: pointer;"/>
</div>
<input type="button" value="+" #click="games.push({name: ''})" style="cursor: pointer; margin-top: 5px;"/>
<div v-if="$v.$invalid" style="color: red; margin-top: 1em;">Form invalid</div>
<pre>{{ $v }}</pre>
</div>
JS:
Vue.use(vuelidate.default)
new Vue({
el: "#app",
data: {
games: [{name: "Fallout"}, {name: "WoW"}, {name: ""}]
},
validations: {
games: {
$each: {
name: {
required: validators.required
}
}
}
}
})
Steps to reproduce the error:
Type something in the second line.
Remove second line.
Result:
The former third line (now second) is marked as containing an error, even though it was not touched.
Note
I've also filed an issue on the vuelidate github-repo, but as there are many unanswered issues, I have decided to also ask the question here.
https://jsfiddle.net/jacobgoh101/cqye5van/
You can use $trackBy to solve this.
If you use $trackBy: 'id', the validation will be differentiated based on the id in each game. Each game object in the array would need to have a unique id for this to work.
e.g. games: [{name: "Fallout", id: 1}, {name: "WoW", id: 2}, {name: "", id: 3}]

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>

VueJS Custom dropdown buttons not working independently of each other

So I have a button dropdown which is working as expected but I have a bug where I can't reuse the same component as they both dont work independently of each other but instead when one is clicked the other changes too.
Please find a JSFiddle below and my code.
Thanks
<div id="app">
<div v-on:click="menuVisible = !menuVisible" class="dropdownBtn">
<div v-bind:class="{ active : menuVisible }"class="dropdownBtn__title">
<span v-if="!btnTitle">exploring</span>
<span v-else>{{ btnTitle }}</span>
</div>
<div v-show="menuVisible" class="dropdownBtn__content">
<ul v-for="reason in reasons">
<li v-on:click="updateTitle(reason)">{{ reason.title }}</li>
</ul>
</div>
</div>
<div v-on:click="menuVisible = !menuVisible" class="dropdownBtn">
<div v-bind:class="{ active : menuVisible }"class="dropdownBtn__title">
<span v-if="!btnTitle">exploring</span>
<span v-else>{{ btnTitle }}</span>
</div>
<div v-show="menuVisible" class="dropdownBtn__content">
<ul v-for="reason in reasons">
<li v-on:click="updateTitle(reason)">{{ reason.title }}</li>
</ul>
</div>
</div>
</div>
new Vue({
el: '#app',
data() {
return {
menuVisible: false,
btnTitle: false,
reasons: [{
title: 'family fun',
value: 1
},
{
title: 'relaxing',
value: 2
},
{
title: 'dining',
value: 3
},
{
title: 'meetings & events',
value: 4
},
{
title: 'what\'s on',
value: 5
},
{
title: 'gold',
value: 6
}]
}
},
methods: {
updateTitle($event) {
this.btnTitle = $event.title
}
}
})
So, for one, you haven't actually made a reusable component for the dropdown. You need to define that using Vue.component. Then you can use the tag for the custom component in the root template.
Secondly, if you are binding to the same data, then that is what will be reflected in both templates. You'll still need to pass separate data to the two separate dropdown components.
Vue.component('dropdown', {
template: `
<div v-on:click="menuVisible = !menuVisible" class="dropdownBtn">
<div v-bind:class="{ active : menuVisible }"class="dropdownBtn__title">
<span v-if="!btnTitle">exploring</span>
<span v-else>{{ btnTitle }}</span>
</div>
<div v-show="menuVisible" class="dropdownBtn__content">
<ul v-for="reason in reasons">
<li v-on:click="updateTitle(reason)">{{ reason.title }}</li>
</ul>
</div>
</div>
`,
props: ['menuVisible', 'btnTitle', 'reasons'],
methods: {
updateTitle($event) {
this.btnTitle = $event.title
}
}
})
new Vue({
el: '#app',
data() {
return {
fooReasons: [
{ title: 'family fun', value: 1 },
{ title: 'relaxing', value: 2 },
{ title: 'dining', value: 3 },
{ title: 'meetings & events', value: 4 },
{ title: 'what\'s on', value: 5 },
{ title: 'gold', value: 6 }
],
barReasons: [
{ title: 'family bar', value: 1 },
{ title: 'bar relaxing', value: 2 },
{ title: 'bar dining', value: 3 },
{ title: 'meetings & bars', value: 4 },
{ title: 'bar\'s on', value: 5 },
{ title: 'gold bar', value: 6 }
]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<div id="app">
<dropdown :menu-visible="false" :btn-title="false" :reasons="fooReasons"></dropdown>
<dropdown :menu-visible="false" :btn-title="false" :reasons="barReasons"></dropdown>
</div>

Accessing siblings elements in a v-for

New to Vuejs 2, need a bit of help to understand how this would be done.
So, I have a template that looks like the following:
<template>
<div class="wrapper">
<div class="item" v-for="item in items">
<div class="item">
<div class="title">{{ item.title }}</div>
<div class="action">
<a href="#" #click.prevent="editSettings(item, $event)">
Click Me
</a>
</div>
</div>
<div v-if="showSettings" class="settings">
<!-- Settings component for this item. -->
<settings-panel item="item"></settings-panel>
</div>
</div>
</div>
</template>
The scripts looks like this:
export default {
data() {
return {
items: [
// This data is from an API usually and only
// shown here for example purposes
{ id: 1, title: 'Title 1' },
{ id: 2, title: 'Title 2' },
{ id: 3, title: 'Title 3' },
{ id: 4, title: 'Title 4' }
],
showSettings: false
};
},
methods: {
editSettings(item, event) {
// Only show the clicked items settings, not all
this.showSettings = !showSettings;
}
}
}
What is the easiest and cleanest approach to only showing the settings panel for the clicked item? Right now, if I click the Click Me settings, all settings open up. The data listed in the code is only there for example, but is coming from an API so I don't have much to do with the manipulation of that data.
Can anyone help lead me in the right direction?
Instead of using showSettings add a selectedItem.
export default {
data() {
return {
items: [
// This data is from an API usually and only
// shown here for example purposes
{ id: 1, title: 'Title 1' },
{ id: 2, title: 'Title 2' },
{ id: 3, title: 'Title 3' },
{ id: 4, title: 'Title 4' }
],
selectedItem: null
};
},
methods:{
selectItem(item){
if (this.selectedItem === item) this.selectedItem = null
else this.selectedItem = item
}
}
}
And modify your template to set the selectedItem when the item is clicked and show the settings only if the selectedItem is the current item.
<template>
<div class="wrapper">
<div class="item" v-for="item in items">
<div class="item">
<div class="title">{{ item.title }}</div>
<div class="action">
<a href="#" #click.prevent="selectItem(item)">
Click Me
</a>
</div>
</div>
<div v-if="selectedItem === item" class="settings">
<!-- Settings component for this item. -->
<settings-panel item="item"></settings-panel>
</div>
</div>
</div>
</template>
The above will show the settings for the clicked item if the currently selected item is not the item that was clicked. If the currently selected item is the one that was clicked, it will hide the settings.