I am asking this again because I don't understand what I am doing wrong. This is a tutorial I did and I did exactly as the tutorial went. I am trying to toggle between tasks being marked done and not done. When I run the code it does not toggle and there are no errors. I read the documentation but I don't fully understand. I am new to programming.
let bus = new Vue();
let Task = {
props: ['task'],
template: `
<div class="task" :class="{ 'task--done' : task.done , 'task-notdone' : task.done === false }">
{{ task.body }}
Mark me as {{ task.done ? 'not done' : 'done' }}
</div>
`,
methods: {
toggleDone(taskId) {
bus.$emit('task:toggleDone', taskId);
}
}
};
let Tasks = {
components:{
'task': Task
},
data() {
return {
tasks: [
{id: 1, body: 'Task One', done: false },
{id: 2, body: 'Task Two', done: true },
{id: 3, body: 'Task Three', done: true }
],
}
},
template: `
<div>
<template v-if="tasks.length">
<task v-for="task in tasks" :key="task.id" :task="task"></task>
</template>
<span v-else>No tasks</span>
<form action="">
form
</form>
</div>
`,
methods: {
toggleDone(taskId){
let task = this.tasks.find(function (task) {
return task.id === taskId;
});
console.log(task);
}
},
mounted () {
bus.$on('task:toggleDone', (taskId) => {
this.toggleDone(taskId);
})
},
};
let app = new Vue({
el:'#app',
components: {
'tasks': Tasks,
},
});
I'm not sure why the tutorial was leading you to using a bus; it's just not needed here. There is a list of tasks that are javascript objects and each task object is being passed to the task component. Since it is a javascript object, and not a primitive value, you can update the done property in the task component.
console.clear()
let Task = {
props: ['task'],
template: `
<div class="task" >
<span :class="{ 'task--done' : task.done , 'task-notdone' : !task.done}">{{ task.body }}</span>
Mark me as {{ task.done ? 'not done' : 'done' }}
</div>
`
};
let Tasks = {
components:{
'task': Task
},
data() {
return {
tasks: [
{id: 1, body: 'Task One', done: false },
{id: 2, body: 'Task Two', done: true },
{id: 3, body: 'Task Three', done: true }
],
}
},
template: `
<div>
<template v-if="tasks.length">
<task v-for="task in tasks" :key="task.id" :task="task"></task>
</template>
<span v-else>No tasks</span>
</div>
`,
};
let app = new Vue({
el:'#app',
components: {
'tasks': Tasks,
},
});
.task--done{
text-decoration: line-through
}
<script src="https://unpkg.com/vue#2.5.2/dist/vue.js"></script>
<div id="app">
<tasks></tasks>
</div>
Additionally, if you don't want to mutate the object in the component, you can instead emit an event that lets the parent mutate it.
console.clear()
let Task = {
props: ['task'],
template: `
<div class="task" >
<span :class="{ 'task--done' : task.done , 'task-notdone' : !task.done}">{{ task.body }}</span>
Mark me as {{ task.done ? 'not done' : 'done' }}
</div>
`
};
let Tasks = {
components:{
'task': Task
},
data() {
return {
tasks: [
{id: 1, body: 'Task One', done: false },
{id: 2, body: 'Task Two', done: true },
{id: 3, body: 'Task Three', done: true }
],
}
},
template: `
<div>
<template v-if="tasks.length">
<task v-for="task in tasks" :key="task.id" :task="task" #toggle-task="toggleTask"></task>
</template>
<span v-else>No tasks</span>
</div>
`,
methods:{
toggleTask(task){
task.done = !task.done
}
}
};
let app = new Vue({
el:'#app',
components: {
'tasks': Tasks,
},
});
.task--done{
text-decoration: line-through
}
<script src="https://unpkg.com/vue#2.5.2/dist/vue.js"></script>
<div id="app">
<tasks></tasks>
</div>
Related
i have two tasks:
Displaying the items of the item list 3 per row
Add a input field used to edit the title field in the currently selected element (selection should be made by clicking).
So, I made the first task based on this solving V-if inside v-for - display list of items in two columns and now i have a problem with my second task selecting method. It should be working for every item but on click selects an items from every list and can to edit only from first list. I think that problem can be in onItemClick(index) method but don't know why.
Any ideas about that?
Vue.component('item-list', {
template: '#item-list-template',
data() {
return {
items: [{
title: 'item 1'
},
{
title: 'item 2'
},
{
title: 'item 3'
},
{
title: 'item 4'
},
{
title: 'item 5'
},
{
title: 'item 6'
}
],
activeIndex: -1,
rowArray: []
}
},
mounted(){
this.fakeFetchData();
},
methods: {
// new method from example solving
fakeFetchData(){
var cloned = this.items.slice();
while (cloned.length > 0) {
let chunk = cloned.splice(0,3);
this.rowArray.push(chunk);
}
},
onItemClick(index) {
this.activeIndex = this.activeIndex === index ? -1 : index;
},
setActiveItemValue(event) {
const foundItem = this.items[this.activeIndex];
if (!foundItem) return;
return this.items[this.activeIndex].title = event.currentTarget.value;
}
},
computed: {
activeItemValue() {
return this.items[this.activeIndex]?.title ?? '';
}
}
});
Vue.component('item', {
template: '#item-template',
props: {
isActive: Boolean,
title: String
}
});
new Vue({
el: '#app'
});
li.active {
background-color: yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<item-list></item-list>
</div>
<script type="text/x-template" id="item-list-template">
<div>
<input type="text" placeholder="Edit selected items" :value="activeItemValue" #input="setActiveItemValue" />
<div class="items-col">
<ul class="items-list" v-for="(row, rowIndex) in rowArray" :key="rowIndex">
<item v-for="(item, i) in row" :key="i" :title="item.title" :isActive="activeIndex === i" #click.native="onItemClick(i)" />
</ul>
</div>
</div>
</script>
<script type="text/x-template" id="item-template">
<li class="item" :class="{ active: isActive }">{{ title }}</li>
</script>
<style>
.items-list {
display: flex;
}
</style>
I have modified and moved the fakeFetchData() from mounted to inside computed and modified the inner v-for inside the template. Check it out
Vue.component('item-list', {
template: '#item-list-template',
data() {
return {
items: [{
title: 'item 1'
},
{
title: 'item 2'
},
{
title: 'item 3'
},
{
title: 'item 4'
},
{
title: 'item 5'
},
{
title: 'item 6'
}
],
activeIndex: -1,
rowArray: []
}
},
methods: {
// new method from example solving
onItemClick(index) {
this.activeIndex = this.activeIndex === index ? -1 : index;
},
setActiveItemValue(event) {
const foundItem = this.items[this.activeIndex];
if (!foundItem) return;
return this.items[this.activeIndex].title = event.currentTarget.value;
}
},
computed: {
activeItemValue() {
return this.items[this.activeIndex]?.title ?? '';
},
fakeFetchData(){
// ********* Changes done below ************
var cloned = this.items.map((item, index) => {
return {title: item.title, id: index}
});
this.rowArray = [];
while (cloned.length > 0) {
let chunk = cloned.splice(0,3);
this.rowArray.push(chunk);
}
return this.rowArray;
// ******* End of changes ********
},
}
});
Vue.component('item', {
template: '#item-template',
props: {
isActive: Boolean,
title: String
}
});
new Vue({
el: '#app'
});
li.active {
background-color: yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<item-list></item-list>
</div>
<script type="text/x-template" id="item-list-template">
<div>
<input type="text" placeholder="Edit selected items" :value="activeItemValue" #input="setActiveItemValue" />
<div class="items-col">
<ul class="items-list" v-for="(row, rowIndex) in fakeFetchData" :key="rowIndex">
<!-- Changes done --><item v-for="item in row" :key="item.id" :title="item.title" :isActive="activeIndex === item.id" #click.native="onItemClick(item.id)" />
</ul>
</div>
</div>
</script>
<script type="text/x-template" id="item-template">
<li class="item" :class="{ active: isActive }">{{ title }}</li>
</script>
<style>
.items-list {
display: flex;
}
</style>
This is a simple shopping Cart
Actually, when we clic on items, it updated the total number in the Cart.
But i want now to make a better Cart: showing each items in the list. Like when we chose 2 croissiants, it adds 2 croissiants in the Cart.
My problem is, in this course, i didn't really learn how to make a Mutation dependings of the ID of the item. I'm looking for the syntax witch is sending the ID of the item where we clicked, to the store.
Here my my 3 files : (Store's "index", "Home" and it's children "MenuItem")
Store :
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
restaurantName: 'Cafe with A Vue',
shoppingCart: 0,
croissiantNumber: 0,
baguetteNumber: 0,
eclairNumber: 0,
simpleMenu: [
{
id: 1,
name: 'Crossiant',
image: {
source: '/images/crossiant.jp',
alt: 'A crossiant'
},
inStock: true,
quantity: 1,
price: 2.99
},
{
id: 2,
name: 'French Baguette',
image: {
source: '/images/french-baguette.jpe',
alt: 'Four French Baguettes'
},
inStock: true,
quantity: 1,
price: 3.99
},
{
id: 3,
name: 'Éclair',
image: {
source: '/images/eclair.jp',
alt: 'Chocolate Éclair'
},
inStock: false,
quantity: 1,
price: 4.99
}
]
},
getters: {
copyright: state => {
const currentYear = new Date().getFullYear()
return `Copyright ${state.restaurantName} ${currentYear}`
}
},
mutations: {
ADD_ITEMS_TO_SHOPPING_CART(state, amount) {
state.shoppingCart += amount
}
},
actions: {
updateShoppingCart({ commit }, amount) {
commit('ADD_ITEMS_TO_SHOPPING_CART', amount),
commit('ADD_ITEM_TO_SHOPPING_CART', amount)
}
},
modules: {}
})
Home :
<template>
<div>
<h1>{{ restaurantName }}</h1>
<p class="description">
Welcome to {{ restaurantName }}! We are known for our freshly baked bread
and french pastries! Give you morning a warm start or treat yourself in
the middle of the day. Our butter is imported from local farmers in
France. Once you take your first bite, you will see why everyone can't get
enough!
</p>
<section class="menu">
<h2>Menu</h2>
<MenuItem
v-for="item in simpleMenu"
:name="item.name"
:image="item.image"
:price="item.price"
:quantity="item.quantity"
:inStock="item.inStock"
:key="item.name"
:id="item.id"
/>
</section>
<div class="shopping-cart">
<h2>Shopping Cart: {{ shoppingCart }} items</h2>
<h2>Croissiant: {{ croissiantNumber }} items</h2>
<h2 v-if="baguetteNumber">French Baguette: {{ baguetteNumber }} items</h2>
<h2 v-if="eclairNumber">Eclair: {{ eclairNumber }}items</h2>
</div>
<footer class="footer">
<p>{{ copyright }}</p>
</footer>
</div>
</template>
<script>
import MenuItem from '../components/MenuItem'
import { mapGetters, mapState } from 'vuex'
export default {
name: 'Home',
components: {
MenuItem
},
computed: {
...mapGetters({
copyright: 'copyright'
}),
...mapState({
restaurantName: 'restaurantName',
shoppingCart: 'shoppingCart',
croissiantNumber: 'croissiantNumber',
baguetteNumber: 'baguetteNumber',
eclairNumber: 'eclairNumber',
simpleMenu: 'simpleMenu'
})
}
}
</script>
MenuItem :
<script>
import { mapActions } from 'vuex'
import BaseButton from './BaseButton.vue'
export default {
name: 'MenuItem',
components: {
BaseButton
},
props: {
image: {
type: Object,
required: true
},
inStock: {
type: Boolean,
required: true
},
name: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
quantity: {
type: Number,
defaut: 1
}
},
data() {
return {
onSale: false
}
},
computed: {
generatedPrice() {
if (this.onSale) {
return (this.price * 0.9).toFixed(2)
} else {
return this.price
}
}
},
methods: {
...mapActions(['updateShoppingCart'])
},
beforeMount() {
const today = new Date().getDate()
if (today % 2 === 0) {
this.onSale = true
}
}
}
</script>
<template>
<div class="menu-item">
<img class="menu-item__image" :src="image.source" :alt="image.alt" />
<div>
<h3>{{ name }}</h3>
<p>Price: {{ generatedPrice }} <span v-if="onSale">(10% off!)</span></p>
<p v-if="inStock">In Stock</p>
<p v-else>Out of Stock</p>
<div>
<label for="add-item-quantity">Quantity: {{ quantity }}</label>
<input v-model.number="quantity" id="add-item-quantity" type="number" />
<BaseButton #click="updateShoppingCart(quantity, id)" class="test">
Add to shopping cart
</BaseButton>
</div>
</div>
</div>
</template>
So i want to update this value for example :
Croissiant: 0 items
Website preview
Thanks.
You have to pass either object or array to your action to handle multiple parameters.
#click="updateShoppingCart({quantity, id})"
and then in store:
...
actions: {
updateShoppingCart({ commit }, {quantity, id}) {
commit('ADD_ITEMS_TO_SHOPPING_CART', amount),
//here you have id that you can map to item type
}
},
I would suggest having item type that is constant in simpleMenu and not depending on IDs.
I am trying to show the dynamically elapsed time from a start time
<template>
<div class="dashboard-editor-container">
<div class="wrapper__body">
<el-row :gutter="30">
<el-col v-for="item in options" :key="item.value" align="middle" :xs="24" :sm="24" :md="24" :lg="4" :xl="24" style="margin-bottom:10px">
<el-button type="primary" style="width: 180px;height:120px ;" >{{item.label}} - {{getTimer(item.FechaHora)}}</el-button>
</el-col>
</el-row>
</div>
</div>
</template>
js
<script>
export default {
data() {
return {
options: [{
value: '01',
label: 'Room 1',
FechaHoraInicio:'2020-02-18T18:17:53.56',
FechaHoraSalida:'2020-02-18T18:17:53.56',
}, {
value: '02',
label: 'Room 2',
FechaHoraStartTime:'2020-02-18T18:17:53.56',
FechaHoraSalida:'2020-02-18T18:17:53.56',
}, {
value: '03',
label: 'Room 2',
FechaHoraStartTime:'2020-02-18T18:17:53.56',
FechaHoraSalida:'2020-02-18T18:17:53.56',
},
}
}
},
computed: {
getTimer : function(FechaHoraInicio) {
setInterval(function(){
return Date.now()-new Date(FechaHoraInicio)
}, 3000);
},
},
}
</script>
the buttons are created dynamically and will have a start time, and after that I want to calculate dynamically
I dynamically create each button with its respective start time, and I need that dynamically as a clock the time that elapses is shown, subtracting the current time with the start time
the time that has elapsed, since the room was rented
i hope this works for you:
new Vue({
el: "#app",
data() {
return {
options: [
{
value: "01",
label: "Room 1",
FechaHoraStartTime: "2020-02-18T18:17:53.56",
FechaHoraSalida: "2020-02-18T18:17:53.56"
},
{
value: "02",
label: "Room 2",
FechaHoraStartTime: "2020-02-18T18:17:53.56",
FechaHoraSalida: "2020-02-18T18:17:53.56"
},
{
value: "03",
label: "Room 2",
FechaHoraStartTime: "2020-02-18T18:17:53.56",
FechaHoraSalida: "2020-02-18T18:17:53.56"
}
],
intervalEvents: []
};
},
created() {
this.setTimers();
},
beforeDestroy() {
this.intervalEvents.map(intervalEvent => {
clearInterval(intervalEvent)
})
},
methods: {
setTimers() {
this.options = this.options.map(option => ({
...option,
elapsed: "",
startTimeAsDate: new Date(option.FechaHoraStartTime)
}));
this.options.map(option => {
const event = setInterval(() => {
option.elapsed = new Date(new Date().getTime() - option.startTimeAsDate).toLocaleTimeString();
}, 1000);
this.intervalEvents.push(event)
});
}
}
});
<link
rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<div id="app">
<div class="dashboard-editor-container">
<div class="wrapper__body">
<el-row :gutter="30">
<el-col
v-for="(option, index) in options"
:key="index"
align="middle"
:xs="24" :sm="24" :md="24" :lg="4" :xl="24"
style="margin-bottom:10px"
>
<el-button type="primary" style="width:180px;height:120px ;">
{{option.label}} {{ option.elapsed }}
</el-button>
</el-col>
</el-row>
</div>
</div>
</div>
I am trying to trigger a change event, where some statement is set from true to false and the other way around, whenever I check the "checkbox", but it doesn't work.
It's actually from this tutorial where I tried to experiment by adding this change event.
<div v-for="task in tasks" :key="task.id" v-if="task.completed">
<ul>
<li v-text="task.description"></li>
<input type="checkbox" #change="changeComplete">
</ul>
</div>
var app = new Vue ({
el: '#root',
data: {
tasks: [
{description: 'clean room', completed: true, id: 1},
{description: 'do homework', completed: true, id: 2},
{description: 'go to sleep', completed: false, id: 3}
]
},
methods: {
changeComplete() {
this.task.completed = !this.task.completed;
}
}
})
I would now expect whenever I check the box for a certain task that the value completed is changing, but the change event doesn't trigger and the completed value remains unchanged.
There is no task property in data object here so you need to change in tasks array, And to do this you need to pass index to identify which task need to change.
var app = new Vue ({
el: '#root',
data: {
tasks: [
{description: 'clean room', completed: true, id: 1},
{description: 'do homework', completed: true, id: 2},
{description: 'go to sleep', completed: false, id: 3}
]
},
methods: {
changeComplete(index) {
this.tasks[index].completed = !this.tasks[index].completed;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<div id="root">
<div>
<div v-for="task,index in tasks" :key="task.id" v-if="task.completed">
<ul>
<li v-text="task.description"></li>
<input type="checkbox" #change="changeComplete(index)">
</ul>
</div>
</div>
</div>
Or better you can change just class to revert or toggle task status
var app = new Vue ({
el: '#root',
data: {
tasks: [
{description: 'clean room', completed: true, id: 1},
{description: 'do homework', completed: true, id: 2},
{description: 'go to sleep', completed: false, id: 3}
]
},
methods: {
changeComplete(index) {
this.tasks[index].completed = !this.tasks[index].completed;
}
}
})
.completed{
color: green;
text-decoration: line-through;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<div id="root">
<div>
<div :class='{completed:task.completed}' v-for="task,index in tasks" :key="task.id">
<ul>
<li v-text="task.description"></li>
<input type="checkbox" #change="changeComplete(index)">
</ul>
</div>
</div>
</div>
You need to refer to the task you want to complete inside the callback:
#change="changeComplete(task)"
and then:
changeComplete(task) {
task.completed = !task.completed;
}
And your v-if shows the completed tasks, I suppose you want to show incomplete ones:
v-if="task.completed == false"
Here's a working copy:
Vue.config.productionTip = false;
Vue.config.devtools = false;
var app = new Vue({
el: '#root',
template: '<div> \
<div v-for="task in tasks" :key="task.id" v-if="task.completed == false"> \
<ul> \
<li> \
<span v-text="task.description"></span> \
<input type="checkbox" #change="changeComplete(task)"> \
</li> \
</ul> \
</div></div>',
data: {
tasks: [{
description: 'clean room',
completed: false,
id: 1
},
{
description: 'do homework',
completed: false,
id: 2
},
{
description: 'go to sleep',
completed: false,
id: 3
}
]
},
methods: {
changeComplete(task) {
task.completed = !task.completed;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="root"></div>
I am trying to use an inline-template in Vue.js but I can't get it to display data, here is my code:
Vue.component('food-item', {
props: ['food'],
})
var app7 = new Vue({
el: '#app-7',
data: {
foodList: [
{ id: 0, text: 'Vegetables' },
{ id: 1, text: 'Cheese' },
{ id: 2, text: 'Whatever else humans are supposed to eat' }
]
}
})
HTML Code:
<div id="app-7">
<ol>
<food-item v-for="item in foodList" v-bind:food="item" v-bind:key="item.address">
</food-item>
</ol>
<!-- template -->
<food-item inline-template>
<li>{{ food.text }}</li>
</food-item>
</div>
I get the following error:
TypeError: Cannot read property 'text' of undefined
You're treating it as a way of specifying the template separate from the component. That's not how inline-template works. The inline-template attribute and the template specification go in the tags where you're instantiating the component.
Vue.component('food-item', {
props: ['food'],
})
var app7 = new Vue({
el: '#app-7',
data: {
foodList: [{
id: 0,
text: 'Vegetables'
},
{
id: 1,
text: 'Cheese'
},
{
id: 2,
text: 'Whatever else humans are supposed to eat'
}
]
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app-7">
<ol>
<food-item v-for="item in foodList" v-bind:food="item" v-bind:key="item.address" inline-template>
<li>{{ food.text }}</li>
</food-item>
</ol>
</div>