VueJS - Checkbox "checked" attribute is inconsistent with its state - javascript

This is a simple todo-list example, where I'd like the checkboxes to be checked or not, depending on the task being completed or not.
After unchecking a checkbox under the "Complete tasks" section, the next checkbox will appear unchecked, even though it's still checked in the "All tasks" section.
To reproduce easily, uncheck "Go to the store" in the "Complete tasks" section. "Clean room" will appear unchecked, even though it's still checked in "All tasks", and still completed in the data.
How can I fix this ?
<html>
<head>
<title>VueJS</title>
</head>
<body>
<div id="root">
<h1>All tasks</h1>
<ul>
<li v-for="task in tasks">
{{ task.description }}
<input type="checkbox" v-model="task.completed" :id="task.id">
</li>
</ul>
<h1>Complete tasks</h1>
<ul>
<li v-for="completeTask in completeTasks">
{{ completeTask.description }}
<input type="checkbox" v-model="completeTask.completed" :id="completeTask.id">
</li>
</ul>
<h1>Incomplete tasks</h1>
<ul>
<li v-for="incompleteTask in incompleteTasks">
{{ incompleteTask.description }}
<input type="checkbox" v-model="incompleteTask.completed" :id="incompleteTask.id">
</li>
</ul>
</div>
<script src="https://unpkg.com/vue#2.6.12/dist/vue.js"></script>
<script>
new Vue({
el: '#root',
data: {
tasks: [
{id: 1, description: 'Go to the store', completed: true},
{id: 2, description: 'Finish X', completed: false},
{id: 3, description: 'Do Y', completed: false},
{id: 4, description: 'Clear inbox', completed: false},
{id: 5, description: 'Make Z', completed: false},
{id: 6, description: 'Clean room', completed: true},
],
},
computed: {
completeTasks() {
return this.tasks.filter(task => task.completed);
},
incompleteTasks() {
return this.tasks.filter(task => !task.completed);
},
},
});
</script>
</body>
</html>

The issue is that Vue is trying to be smart and "reuse" the DOM. But in this case it will work against you.
To fix this you need add a key to each element with a v-for, this way Vue can track which <li> is which based on the key.
It's explained more in-depth on the docs:
https://v2.vuejs.org/v2/guide/list.html#Maintaining-State
<html>
<head>
<title>VueJS</title>
</head>
<body>
<div id="root">
<h1>All tasks</h1>
<ul>
<li v-for="task in tasks" :key="task.id">
{{ task.description }}
<input type="checkbox" v-model="task.completed" :id="task.id">
</li>
</ul>
<h1>Complete tasks</h1>
<ul>
<li v-for="completeTask in completeTasks" :key="completeTask.id">
{{ completeTask.description }}
<input type="checkbox" v-model="completeTask.completed" :id="completeTask.id">
</li>
</ul>
<h1>Incomplete tasks</h1>
<ul>
<li v-for="incompleteTask in incompleteTasks" :key="incompleteTask.id">
{{ incompleteTask.description }}
<input type="checkbox" v-model="incompleteTask.completed" :id="incompleteTask.id">
</li>
</ul>
</div>
<script src="https://unpkg.com/vue#2.6.12/dist/vue.js"></script>
<script>
new Vue({
el: '#root',
data: {
tasks: [
{id: 1, description: 'Go to the store', completed: true},
{id: 2, description: 'Finish X', completed: false},
{id: 3, description: 'Do Y', completed: false},
{id: 4, description: 'Clear inbox', completed: false},
{id: 5, description: 'Make Z', completed: false},
{id: 6, description: 'Clean room', completed: true},
],
},
computed: {
completeTasks() {
return this.tasks.filter(task => task.completed);
},
incompleteTasks() {
return this.tasks.filter(task => !task.completed);
},
},
});
</script>
</body>
</html>

Related

How to specify bootstrap accordion for data from API in vue js

I'm trying to create an order feed using accordion elements from bootstrap-vue and I am struggling with making only one element opened when I press it.
I've tried changing ids which are from api, but have no result.
HTML:
<div v-for="el in APIData" :key="el.id" >
<div class="accordion" role="tablist" :id="el.id">
<b-card no-body class="mb-1" :id="el.id">
<b-card-header header-tag="header" class="p-1" role="tab" :id="el.id">
<b-button block v-b-toggle.accordion-1 variant="dark">{{ el.name }}. Deadline: {{ el.deadline }} Price: <strong>{{ el.price }}</strong></b-button>
</b-card-header>
<b-collapse id="accordion-1" accordion="my-accordion" role="tabpanel" >
<b-card-body>
<b-card-text>
<div> <p> <strong>Posted:</strong> {{ el.date_posted }}. <br />{{ el.description }}</p> </div>
<a class= "button btn btn-dark">More</a>
</b-card-text>
</b-card-body>
</b-collapse>
</b-card>
</div>
</div>
Script:
<script>
import axios from 'axios'
export default {
name: 'Orders',
data () {
return {
APIData: [],
}
},
created () {
axios
.get('/api/v1/orders/')
.then(response => {
this.APIData = response.data
console.log(response.data)
})
.catch(err => {
console.log(err)
})
},
}
</script>
Example of data:
[
{
price: "179",
id: "1",
date_posted: "04_04_2022",
description: "some desc bla bla bla",
name: "some name",
deadline: "04_06_2022"
},
{
price: "189",
id: "2",
date_posted: "05_04_2022",
description: "another desc bla bla bla",
name: "some name",
deadline: "05_06_2022"
},
{
price: "199",
id: "3",
date_posted: "06_04_2022",
description: "another desc bla bla bla",
name: "some name",
deadline: "06_06_2022"
},
]
Again, I need to get opened only one accordion but get three instead e.g. because it seems to bootstrap that it is all one element.
You can simply achieve that by concatenating the el.id in the v-b-toggle attribute and the accordion id as well.
Working Demo :
new Vue({
el: '#app',
data: {
APIData: [{
id: 1,
name: 'Accordion 1'
}, {
id: 2,
name: 'Accordion 2'
}]
}
})
#app {
padding: 20px;
height: 350px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.22.0/dist/bootstrap-vue.js"></script>
<link rel="stylesheet" href="https://unpkg.com/bootstrap-vue#2.22.0/dist/bootstrap-vue.css"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#4.4.1/dist/css/bootstrap.min.css"/>
<div id="app">
<div v-for="el in APIData" :key="el.id" >
<p>
<b-btn v-b-toggle="`collapse-${el.id}`" variant="primary">Toggle {{ el.name }}</b-btn>
</p>
<b-collapse :id="`collapse-${el.id}`">
<b-card>
Collapse {{ el.name }} contents Here
</b-card>
</b-collapse>
</div>
</div>

Creating an accordion menu: The submenus are not displayed

I am creating an accordion menu. The headings are displayed but I don't know how to display the submenus?
Something missing on this part? Because, I don't see where is the problem? If you have a solution, I'm really interested.
<ul class="submenu" #submenu>
<li *ngFor="let submenu of menu.submenu">
<a routerLink="{{ submenu.url }}"> {{ submenu.name }} </a>
</li>
</ul>
HTML file
<div class="sidebar">
<ul id="accordion" class="accordion">
<li *ngFor="let menu of menus; let i = index" [class.active]="menu.active">
<ng-container>
<div class="menu" (click)="selectMenu(menu)">
<i [class]="menu.iconClass"></i> {{ menu.name }}
<i class="fa fa-chevron-down"></i>
</div>
<ul class="submenu" #submenu>
<li *ngFor="let submenu of menu.submenu">
<a routerLink="{{ submenu.url }}"> {{ submenu.name }} </a>
</li>
</ul>
</ng-container>
</li>
</ul>
</div>
Then
export class AppComponent {
menus: Menu[] = [
{
name: 'Markets',
iconClass: 'bx bx-lock-alt',
active: false,
submenu: [
{ name: 'Indice', url: 'https://www.google.be'},
{ name: 'Value', url: '#'}
]
},
{
name: 'Stock',
iconClass: 'bx bx-lock-alt',
active: false,
submenu: [
{ name: 'Currency', url: 'https://www.google.be'},
{ name: 'Devise', url: '#'}
]
},
{
name: 'Traiding',
iconClass: 'bx bx-lock-alt',
active: false,
submenu: [
{ name: 'Day traiding', url: 'https://www.google.be'},
{ name: 'Devise', url: '#'}
]
}
]
selectMenu(parentMenu: { name: string }) : void {
this.menus.forEach(menu => {
if(menu.name !== parentMenu.name){
menu.active = false;
} else {
menu.active = true;
}
})
}
}
There is a reproduction here.
Thank you in advance for your help.

view image in an HTML page using Vue

<main id="meals">
<h3 style="text-align: center;">Week 1 - Day 1</h3>
<h6>{{mealType}} ( {{selectedItems}}/{{maxSelection}} selected )</h6>
<div class="grid">
<div v-for="item in d1w1" :key="item.id" class="option">
<div class="optionImg">
<img src={{item.pic}} width="100%" height="100%" />
</div>
<div class="optionDetails">
<h4>{{item.name}}</h4>
<p>{{item.desc}}</p>
</div>
</div>
</div>
</main>
hi everyone, I'm trying to render an images you can see in the code above as well as other components. everything is working fine except for the image. I am not sure why.
here is the code in my main.js file
const app = Vue.createApp({
data() {
return {
d1w1: [
{id: 001, name: 'arabic breakfast', desc: 'arabic food with keto bread', pic: './assets/logo_green.png'},
{id: 002, name: 'keto crackers', desc: 'cheese and keto crackers', pic: './assets/logo_green.png'},
{id: 003, name: 'meat shawarma', desc: 'shawrma with tahini sauce', pic: './assets/logo_green.png'},
{id: 004, name: 'ejjah', desc: 'light omelet arab version', pic: './assets/logo_green.png' },
],
mealType: 'light meal',
selectedItems: 0,
maxSelection: 2,
}
},
})
Use
<img :src="item.pic" width="100%" height="100%" />
Working Fiddle

Optimize the number of ng-repeat |angularJS

I want to separate everything in my code according to predefined levels given , from level 1 to level 6 , now my JSON reads
$scope.myJson=[{
id: 1,
level: 1,
name: "any name"
},
{
id: 2,
level: 2,
name: "any name"
},
{
id: 3,
level: 2,
name: "any name"
}....]
now in my HTML I have written something like
<div>level 1
<div ng-repeat="prod in myJson" ng-if="prod.level==1">{{prod.name}}
</div>
</div>
<div>level 2
<div ng-repeat="prod in myJson" ng-if="prod.level==2">{{prod.name}}
</div>
</div>
and so on , I want to use only one ng-repeat to filter all the results in HTML on basis of the levels , as the results number in thousands the app is taking too much of time to respond
You could try a switch case in your ng repeat, for example :
<div ng-repeat="prod in myJson">
<div ng-switch-on = "prod.level">
<div ng-switch-when="1">
Level 1 {{prod.name}}
</div>
<div ng-switch-when="2">
Level 2 {{prod.name}}
</div>
<div ng-switch-default>
Another level {{prod.name}}
</div>
</div>
</div>
So you will only do one run over your data.
To make it more generic, groupBy will solve this for you.
Demo:
angular.module('app', ['angular.filter'])
.controller('myCtrl', function($scope) {
$scope.myJson = [{
id: 1,
level: 1,
name: "any name level1"
}, {
id: 2,
level: 2,
name: "any name level2"
}, {
id: 3,
level: 2,
name: "any name level2"
}, {
id: 4,
level: 2,
name: "any name level2"
}, {
id: 5,
level: 1,
name: "any name level1"
}, {
id: 6,
level: 3,
name: "any name level3"
}]
});
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-filter/0.4.7/angular-filter.js"></script>
</head>
<body>
<div ng-controller="myCtrl">
<ul ng-repeat="(key, value) in myJson | groupBy: 'level'">
Level- {{ key }}
<li ng-repeat="item in value">
{{ item.name }}
</li>
</ul>
</div>
</body>
</html>

Knockout.js {{each}} not listing items in javascript template

I've created a simple app that should iist each item from a model in a list, created using a javascrit template.
Fiddle
Html:
<div id="tagsList" class="box">
<div class="box-head">
<h2 class="left">Tags</h2>
</div>
<div class="box-content">
<input type="text" placeholder="Add New Tag" />
<button>+ Add</button>
<div data-bind="template: 'tagsTempl'"></div>
</div>
</div>
<script id="tagsTempl" type="text/html">
<ul>
{{each tags}}
<li class="tagItem">
<span>${Name}</span>
<div>
Edit
Delete
</div>
</li>
{{/each}}
</ul>
</script>
Javascript:
$(function () {
//$("#tagDialog").hide();
var data = [
{ Id: 1, Name: "Ball Handling" },
{ Id: 2, Name: "Passing" },
{ Id: 3, Name: "Shooting" },
{ Id: 4, Name: "Rebounding" },
{ Id: 5, Name: "Transition" },
{ Id: 6, Name: "Defense" },
{ Id: 7, Name: "Team Offense" },
{ Id: 8, Name: "Team Defense" }
];
var viewModel = {
tags: ko.observableArray(data),
tagToAdd: ko.observable(""),
addTag: function() {
this.tags.push({ Name: this.tagToAdd() });
}
}
ko.applyBindings(viewModel)
});
Output of list:
{{each tags}}
${Name}
Edit Delete
{{/each}}
The scripts file is accessible through viewing source. I'm not sure where my error is. Any help?
I updated your fiddle. Now it is working like you want it to: The list of tags is being rendered using the knockout standard method as described in the docs.
HTML
<ul data-bind="template: {name: 'tagsTempl', foreach: tags}"></ul>
Template
<script id="tagsTempl" type="text/html">
<li class="tagItem">
<span data-bind="text: Name"></span>
<div>
Edit
Delete
</div>
</li>
</script>
Also I connected the viewmodel to the view.
For example:
<button data-bind="click: addTag">+ Add</button>
You simply forgot most of it. I suggest you follow the interactive tutorials on how to do this.

Categories