Why is this :class="{}" not working? vueJs - javascript

I made a really simple file which have a reactive Array of objects. ⇒ all objects have a property called checked, which is a boolean and toggles based on a checkbox.
I'm iterating with a v-for="" the array of employees and rendering them on a <ul/ li.
I'm trying to make a :class just for the ones who got checked, but it's throwing me a syntax error, and I'm not sure where I'm wrong and which would be the best approach. Every comment, advice will be appreciated, here's the code:
<template>
<div class="contaier">
<h1>Employees view</h1>
<ul class="list">
<li
class="li-item"
v-for="employee in employees" :key="employee.id"
:class="{employee.checked: isChecked}"
>
<input class="checkbox" v-model="employee.checked" type="checkbox" #click="checkEmployee">
{{ employee.name }}
</li>
</ul>
</div>
</template>
<script>
import { reactive, ref } from 'vue'
export default {
setup() {
const checked = ref(false)
const employees = reactive([
{id: 1,
name:'Terry Lawrence',
username:'TerryLaw',
email: 'TerryLaw#gmail.com',
address: 'whateverStreet 258',
checked: checked.value
},
{id: 2,
name:'MartyClFly',
username:'MartyMac',
email: 'MartyMac#gmail.com',
address: 'George Junior 300',
checked: checked.value
},
{id: 3,
name:'Nancy Pelosi',
username:'Drunk ho',
email: 'drunkHo#gmail.com',
address: 'Velbedere 400',
checked: checked.value
},
{id: 4,
name:'Jonh Doe',
username:'Jonny',
email: 'JonhDoe#gmail.com',
address: 'NobodyKnows 129',
checked: checked.value
},
{id: 5,
name:'Candace Owens',
username:'the greate black hope',
email: 'Candace#gmail.com',
address: 'Washington Str 777',
checked: checked.value
},
{id: 6,
name:'Charles Dickens',
username:'Charlie',
email: 'CharlieDick#gmail.com',
address: 'chickenNutso 678',
checked: checked.value
}
])
const checkEmployee = (event) => {
try {
for (const employee of employees) {
if (event.target.id !== employee.id) {
checked.value = true
}}
} catch (error) {
console.error(error)
}
}
return {
employees,
checkEmployee,
}
}}
</script>
<style scoped>
.list {
width: 60%;
margin-inline: auto;
padding: 1em;
list-style: none;
}
.li-item {
padding: .5em;
border: 1px solid black;
}
.checkbox {
float: left;
}
.isChecked {
background-color: rgb(191, 236, 122);
}
</style>
the error is here exactly ⇒ <li / element:

Replace
<li class="li-item"
v-for="employee in employees" :key="employee.id"
:class="{employee.checked: isChecked}">
with
<li class="li-item"
v-for="employee in employees" :key="employee.id"
:class="{isChecked: employee.checked}">

Related

Vue js drag and drop how to remove duplicate id in array

I want to create a drag and drop application using the Vue JS framework. Here is an example of my complete code.
The problem is with the id properties inside the children arrays.
For example, when I drag an object named 'AAA' to another place, everything works fine for me, but when I drag it back, I get an error like - Duplicate keys detected: '0'. This may cause an update error.
I'm pretty sure the problem is inside the oneDrop function
onDrop(e, categoryId) {
const itemId = parseInt(e.dataTransfer.getData('itemId'))
this.categories.map(item => {
item.children = item.children.filter(child => {
if (child.id == itemId) {
child.categoryId = categoryId;
this.categories[categoryId].children.push(child);
}
return child
})
})
}
Of course, I understand that when dragging using the push method, the old object remains and is not deleted, so I get this error, but how to deal with this problem? (Full code at the beginning of the question)
You need to filter list from and add item to list to:
new Vue({
el: "#demo",
data() {
return {
categories: [
{id: 0, title: "This is parent block", children: [{ id: 0, title: "AAA", categoryId: 0 }, { id: 1, title: "BBB", categoryId: 0 },],},
{id: 1, title: "This is parent block", children: [{ id: 2, title: "CCC", categoryId: 1 }, { id: 3, title: "DDD", categoryId: 1 },],},
],
};
},
methods: {
onDrop(e, categoryId) {
const itemId = parseInt(e.dataTransfer.getData("itemId"));
const id = categoryId === 0 ? 1 : 0
const child = this.categories[id].children.find(c => c.id === itemId)
child.categoryId = categoryId;
this.removeFromList(id, itemId)
this.addToList(categoryId, child)
},
addToList(categoryId, child) {
this.categories[categoryId].children = [...this.categories[categoryId].children, child];
},
removeFromList(id, itemId) {
this.categories[id].children = this.categories[id].children.filter(c => c.id !== itemId);
},
onDragStart(e, item) {
e.dataTransfer.dropEffect = "move";
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("itemId", item.id.toString());
},
},
})
.droppable {
padding: 15px;
border-radius: 5px;
background: #2c3e50;
margin-bottom: 10px;
}
.droppable h4 {
color: white;
}
.draggable {
background: white;
padding: 5px;
border-radius: 5px;
margin-bottom: 5px;
}
.draggable h5 {
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div
v-for="category in categories"
:key="category.id"
#drop="onDrop($event, category.id)"
class="droppable"
#dragover.prevent
#dragenter.prevent
>
<h4>{{ category.title }}</h4>
<div class="child">
<div
v-for="item in category.children.filter(
(x) => x.categoryId === category.id
)"
:key="item.id"
#dragstart="onDragStart($event, item)"
class="draggable"
draggable="true"
>
<h5>{{ item.title }}</h5>
</div>
</div>
</div>
{{categories}}
</div>

Filter nested v-for list

<div>
<q-card
v-for="box in boxes"
:key="box.id">
<q-item>
<q-item-section>
<span> {{ box.name }} </span>
</q-item-section>
</q-item>
<q-list>
<q-item
v-for="tool in box.tools"
:key="tool.id"
clickable
<q-item-section>
<span> {{ tool.name }} </span>
</q-item-section>
</q-item>
</q-list>
</q-card>
</div>
Form input filter value
inputFilterValue = "box A"
Filter boxes
Edited with return.
computed: {
boxes(){
return boxes.filter(box => {
return box.name.toLowerCase().match(inputFilterValue.toLowerCase())
});
}
}
This works
How to filter too nested v-for box-tools list?
EDITED:
CODEPEN: https://codepen.io/ijose/pen/NWyoRMX
You can use JavaScript filter() along with some() method. some() method checks if any of the elements in an array pass the function.
Demo :
new Vue({
el: '#app',
data: {
value: null,
boxes: [],
dataObj: [{
id: 1,
name: 'Box A',
tools: [{
id: 1,
name: 'Tool A'
}, {
id: 2,
name: 'Tool D2'
}]
}, {
id: 2,
name: 'Box B',
tools: [{
id: 1,
name: 'Tool B'
}]
}, {
id: 3,
name: 'Box C',
tools: [{
id: 1,
name: 'Tool C'
}]
}]
},
mounted() {
this.boxes = structuredClone(this.dataObj);
},
methods: {
inputFilterValue(filterField) {
if (filterField === 'box') {
this.boxes = this.dataObj.filter(box => box.name.toLowerCase().match(this.value.toLowerCase()))
} else {
const filteredToolsArr = this.dataObj.map(box => {
const filteredTool = box.tools.filter((
{ name }) => name.toLowerCase().match(this.value.toLowerCase()));
return { ...box, tools: filteredTool }
})
this.boxes = filteredToolsArr.filter(obj => obj.tools.length);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
Filter : <input type="text" v-model="value" #keyup="inputFilterValue('tool')"/>
<ul v-for="box in boxes" :key="box.id">
<li>{{ box.name }}</li>
<ul v-for="tool in box.tools" :key="tool.id">
<li>{{ tool.name }}</li>
</ul>
</ul>
</div>
In the filter callback of boxes add a condition if the tool name matches the inputFilterValue
computed: {
boxes(){
return boxes.filter(box => {
return box.name.toLowerCase().match(inputFilterValue.toLowerCase()) ||
box.tools.filter(tool=>tool.name.toLowerCase().match(inputFilterValue.toLowerCase())
});
}
}

Nested Vue components with counts of direct children and nested children

I am trying to implement nested comments in vue.js and nuxt.js.
Each comment can have one or more children comments.
Each child comment, can again, have one or more children comments.
Unlimited levels of nested comments is possible.
As you can see in the diagram I have attached, I would like each comment to "know" (for the sake of simplicity, to display) the following information:
The depth of the comment (I have this working already). Example, all of the "top-level" comments are at depth=0, all their children are at depth=1, and so on.
The number of direct children
the number of children (including nested children, unlimited levels deep)
I came across this question on StackOverflow but it doesn't quite do the trick. Or maybe I am doing something wrong.
In case you want to take a look at my (very messy) code, here it is. However, I'm willing to start over, so appreciate any pointers on how to pass the data up / down the chain of nested comments (vue components). Some sample code would be great.
components/PostComment.vue:
<template>
<div>
<div class="tw-flex tw-flex-wrap tw-justify-end">
<div :class="indent" class="tw-w-full tw-flex">
<div class="tw-font-bold tw-p-4 tw-border-gray-400 tw-border tw-rounded tw-text-right">
<div class="kb-card-section">
<div class="kb-card-section-content tw-flex tw-flex-wrap tw-items-center tw-text-left">
<div class="tw-flex tw-w-full">
<div class="tw-hidden md:tw-block md:tw-w-2/12 tw-text-right tw-my-auto">
<div class="tw-flex">
<p class="tw-w-full tw-text-xs tw-text-gray-600 tw-text-right">children: {{ numNestedChildComments }}, depth: {{depth}}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tw-w-full" v-if="commentData.nested_comments" v-for="nestedComment in commentData.nested_comments">
<post-comment
:commentData="nestedComment"
:depth="depth + 1"
:numChildCommentsOfParent=numNestedChildComments
/>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'post-comment', // necessary for recursive components / nested comments to work
props: {
depth: {
type: Number,
required: true
},
postAuthorData: {
type: Object,
required: true
},
commentAuthorData: {
type: Object,
required: true
},
commentData: {
type: Object,
required: true
},
numChildCommentsOfParent: {
type: Number,
required: true
},
},
data() {
return {
numNestedChildComments: this.numChildCommentsOfParent,
}
},
mounted() {
this.incrementNumParentComments();
},
methods: {
incrementNumParentComments() {
this.numNestedChildComments++;
this.$emit('incrementNumParentComments');
},
},
computed: {
indent() {
switch (this.depth) {
case 0:
return "tw-ml-0 tw-mt-1";
case 1:
return "tw-ml-4 tw-mt-1";
case 2:
return "tw-ml-8 tw-mt-1";
case 3:
default:
return "tw-ml-12 tw-mt-1";
}
},
},
}
</script>
Figured it out with some help from Rodrigo Pedra from the Laracasts community.
Here as a parent component calling the tree roots:
<template>
<div>
<MyTree v-for="item in records" :key="item.id" :item="item" />
</div>
</template>
<script>
import MyTree from './MyTree';
const FIXTURE = [
{
id: 1,
children: [
{
id: 2,
children: [{id: 3}, {id: 4}, {id: 5}],
},
{
id: 6,
children: [
{id: 7},
{id: 8, children: [{id: 9}, {id: 10}]},
],
},
],
},
{
id: 11,
children: [
{id: 12, children: [{id: 13}, {id: 14}, {id: 15}]},
{id: 16, children: [{id: 17}]},
{id: 18},
],
},
];
export default {
components: {MyTree},
data() {
return {
records: FIXTURE,
};
},
};
</script>
And here is the tree component:
<template>
<div>
<div style="border: 1px solid black; padding: 5px;" :style="offset">
id: {{ item.id }}
// depth: {{ depth }}
// direct: {{ direct }}
// children: {{ childrenCount }}
</div>
<template v-if="item.children">
<MyTree
v-for="record in item.children"
:key="record.id"
:item="record"
:depth="depth + 1"
#born="handleBorn()" />
</template>
</div>
</template>
<script>
const COLORS = [
'white',
'lightgray',
'lightblue',
'lightcyan',
'lightskyblue',
'lightpink',
];
export default {
// MUST give a name in recursive components
// https://vuejs.org/v2/guide/components-edge-cases.html#Recursive-Components
name: 'MyTree',
props: {
item: {type: Object, required: true},
depth: {type: Number, default: 0},
},
data() {
return {
childrenCount: 0,
};
},
computed: {
direct() {
if (Array.isArray(this.item.children)) {
return this.item.children.length;
}
return 0;
},
offset() {
return {
'margin-left': (this.depth * 20) + 'px',
'background-color': COLORS[this.depth % COLORS.length],
};
},
},
mounted() {
this.$emit('born');
},
methods: {
handleBorn() {
this.childrenCount++;
this.$emit('born');
},
},
};
</script>
Screenshot:

How to loop through an array and add a property to all objects in vue.js

In Vue.js, in order to add a property/array item to something already in the virtual DOM, you have to use the $set function.
Here are the wrong ways:
Object: this.myObject.newProperty = "value";
Array: this.myArray[3] = object;
Here's the right way:
Object: this.$set(this.myObject, "newProperty", "value");
Array: this.$set(this.myArray, 3, object);
My question is how do you $set a property of all objects in an array?
Here's the wrong way:
for (var i = 0; i < this.myArray.length; i++) {
this.myArray[i].newProperty = "value";
}
So, what's the method for me to use $set to do this?
A bit tweaked code of yours works:
new Vue({
el: "#app",
data: {
todos: [{
text: "Learn JavaScript",
done: false
},
{
text: "Learn Vue",
done: false
},
{
text: "Play around in JSFiddle",
done: true
},
{
text: "Build something awesome",
done: true
}
]
},
methods: {
toggle: function(todo) {
todo.done = !todo.done
},
changeProperty1() {
const val = 'A'
// this is your code a bit modified
// defining length (and using it in the comparison) is a
// bit of optimization, not required
for (var i = 0, length = this.todos.length; i < length; i++) {
this.$set(this.todos[i], 'property1', val);
}
},
changeProperty1Again() {
for (todo of this.todos) {
if (todo.property1) {
todo.property1 = 'B'
}
}
}
},
created() {
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<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>
<span>
{{ todo.property1 }}
</span>
</label>
</li>
</ol>
<button #click="changeProperty1">Click this first</button>
<button #click="changeProperty1Again">Click this second</button>
</div>
Sorry for the lengthy snippet, I just copied it over from JSFiddle :)
You keep doing this.$set(this.myArray, 3, object); in a loop using the index.
Something like this after modifying your object.
var newObject = Object.assign({}, this.myArray[i], {newProperty: 'value'} ); // Immutable object created
this.$set(this.myArray, i, newObject);
This will be inefficient as it will call $set for each iteration.
so you can do a map on your array and return a new Object from inside.
const newArray = myArray.map(object => {
return Object.assign({}, object, {newProperty: 'value'} );
//or by ES6 spread operator
return {...object, newProperty: 'value'};
});
Then set your array for Vuejs to re-render.
Hope, this will give the idea. Although context(this) may vary depending on how you're implementing!
You simply just wanna add a new property to objects in an array. Not set a new value to the array by their index. You can do the following:
new Vue({
el: '#demo',
data: {
myArray: [
{id: 1},
{id: 2},
{id: 3},
{id: 4},
{id: 5}
]
},
methods: {
addProperties() {
for (var i = 0; i < this.myArray.length; i++) {
this.$set(this.myArray[i], 'newProperty', 5 - i)
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div v-for="item in myArray" :key="item.id">
<span>{{item.id}}: </span>
<span v-if="item.newProperty">{{item.newProperty}}</span>
</div>
<button #click="addProperties">Add Properties</button>
</div>
Not really a vue thing - just plain JS:
arr.map(obj => { return {...obj, newProperty: "sameValueAsOthers"}});

Filtering by multiple items in React

I am trying to filter the list of items in state.items by adding items from state.filterItems to my state.filter array
if I use this.state.items.filter(items => items.cat === 'veg' ) of course this works but I need to be able to filter dynamically using the list of items added to my state.filter array and I'm not sure how to do this,
I would also like to be able select multiple options and then hit a button to apply the filters rather than selecting them one by one
https://www.webpackbin.com/bins/-KoCT_DiT2CNLz8ddr4O
Hello.js
import React, { Component } from 'react';
import logo from './logo.svg'
import './App.css'
import update from 'immutability-helper'
import TodoList from './TodoList'
import styled from 'styled-components'
import FilterList from './FilterList'
const Wrapper = styled.div`
max-width:1280px;
background: papayawhip;
margin: 0 auto;
padding:20px;
`
const Grid = styled.div`
display:flex;
flex-wrap:wrap;
`
const Cell = styled.div`
flex: 0 0 25%;
padding: 20px;
`
export default class hello extends Component {
constructor(props) {
super(props)
this.state = {
items: [
{id: 1, cat: 'fruit', text: 'apples'},
{id: 2, cat: 'fruit', text: 'oranges'},
{id: 3, cat: 'fruit', text: 'peaches'},
{id: 4, cat: 'veg', text: 'carrots'},
{id: 5, cat: 'veg', text: 'aubergine'},
{id: 6, cat: 'veg', text: 'peaches'},
{id: 7, cat: 'bread', text: 'olive bread'},
{id: 8, cat: 'bread', text: 'bread roll'},
{id: 9, cat: 'bread', text: 'bagel'},
],
filterItems: [
{id: 1, text: 'bread'},
{id: 2, text: 'fruit'},
{id: 3, text: 'vegetables'},
],
filter: [
{text: 'bread'}
],
}
}
handleFilterChange = (filter) => {
this.setState({filter: filter})
}
render() {
return (
<Wrapper>
<div>
<FilterList
value={this.state.filter}
onChange={this.handleFilterChange}
filterItems={this.state.filterItems}
/>
</div>
<Grid>
{
this.state.items.filter(items => items.cat === 'veg', 'fruit' )
.map(item =>
<Cell>
{console.log(this.state.filter.text)}
<div>{item.cat}</div>
<div>{item.text}</div>
</Cell>
)
}
</Grid>
</Wrapper>
)
}
}
// <pre>{JSON.stringify(this.state, null, 4)} </pre>
FilterList.js
import React, { Component } from 'react';
import TodoItem from './TodoItem'
import update from 'immutability-helper'
import styled from 'styled-components'
const FilterListBg = styled.div`
background: lightblue;
width: 100%;
height: 60px;
`
const FilterListItem = styled.div`
float: left;
height: 40px;
width: 100px;
padding:10px;
border-right: 1px solid #ff00ff;
`
const FilterBg = styled.div`
width: 100%;
height:40px;
background: #fff;
margin-top:20px;
`
const FilterItem = styled.div`
float: left;
height: 40px;
width: 100px;
padding:10px;
border-right: 1px solid #ff00ff;
`
export default class FilterList extends Component {
constructor() {
super()
this.state = {
search: ''
}
}
handleAdd = (item) => {
const value = update(this.props.value, {
$push: [
{
text: item,
id: Math.random(),
}
]
})
this.props.onChange(value)
}
handleRemove = (index) => {
const value = update(this.props.value, {
$splice: [
[index, 1]
]
})
this.props.onChange(value)
}
handleFilterUpdate = event => {
this.setState({ search: event.target.value })
}
render() {
return (
<div>
<input
type="text"
value={this.state.search}
onChange={this.handleFilterUpdate}
placeholder="Hledat podle nazvu"
/>
{this.state.search}
<FilterListBg>
{
this.props.filterItems.filter(items => items.text.toLowerCase().indexOf(this.state.search.toLowerCase()) >= 0)
.map((item,cat,index) =>
<FilterListItem key={item.id} onClick={()=>this.handleAdd(item.text)}>
{item.text}
</FilterListItem>
)
}
</FilterListBg>
Aktivní filtry
<FilterBg>
{
this.props.value.map((item, index) =>
<FilterItem key={item.id} onClick={this.handleRemove}>
{item.text}
</FilterItem>
)
}
</FilterBg>
</div>
)
}
}
Assuming you want to show the items matching your filterList, shouldn't something simple like this work?
const filterTexts = this.state.filter.map(item => item.text);
const itemsToShow = this.state.items.filter(
item => filterTexts.indexOf(item.cat) !== -1);
And then you can map over itemsToShow to create your Cells.
If you want a one-liner to simply copy-paste:
this.state.items.filter(items => this.state.filterItems.map(item => item.text)
.indexOf(items.cat) !== -1 )
.map(item =>
<Cell>
{console.log(this.state.filter.text)}
<div>{item.cat}</div>
<div>{item.text}</div>
</Cell>
)

Categories

Resources