Computed property or other way to change style depending on value inside v-for in Vue - vue.js

I have component like this:
Vue.component('mcp-item', {
template: '#mcp-item-template',
data() {
return {
name: "MCP v2",
version: "2.0",
imei: 'XXXXXX XX XXXXXX X',
relays: [
{ name : "REL1", state : 0 },
{ name : "REL2", state : 0 }
],
inputs: [
{ name: "BP1", state: 0, color: "#CC0000" },
{ name: "BP2", state: 0, color: "#CC0000" },
{ name: "BP3", state: 1, color: "#00CC00" },
{ name: "BP4", state: 0, color: "#CC0000" },
{ name: "BP5", state: 0, color: "#CC0000" },
{ name: "BP6", state: 0, color: "#CC0000" }
],
}
},
methods: {
reboot: function (event) { alert( this.imei) }
}
})
And somewhere in compoment template:
<table>
<thead>
<tr>
<th>Input</th>
<th>State</th>
</tr>
</thead>
<tbody v-for="input in inputs" :key="input.name">
<tr>
<td :style="{ 'color': input.color}">{{input.name}}</td>
<td>{{input.state}}</td>
</tr>
</tbody>
</table>
As you can see, now I have dedicated color field in my object (which is element of inputs array in data):
JS:
{ name: "BP1", state: 0, color: "#CC0000" }
HTML:
<td :style="{ 'color': input.color}">{{input.name}}</td>
I want to get rid of this extra property, but I can't figure out how can I use computed property inside of v-for loop to make red color for state==0 and green for state==1.

Rather than creating a computed property or adding the logic into the template, I would create a method getColor(state) which looks like this:
getColor(state) {
let color = '';
if (state === 0) {
color = 'red';
} else if (state === 1) {
color = 'green';
}
return { color };
}
Or if the only values are 0 and 1 you could shorten this to something like:
getColor(state) {
const color = state === 0 ? 'red' : 'green';
return { color };
}
then call it like this:
<td :style="getColor(input.state)">{{input.name}}</td>

maybe computed property is an overkill - what about simple condition:
<td :style="{ 'red': input.state === 0, 'green': input.state === 1}">...// </td>

Related

Property 'campaign' was accessed during render but is not defined on instance

These are the issues i'm getting
Here below is the code that produces the problems, this part in particular:
When ever i;m trying to filter campaigns using company_id and product_id with v-if the problem occurs. Almost the same exact code works a few lines above filtering products. I have no idea what to do next. I tried refs and putting the mocked that into reactive variable and computeing it with a function but it didn;t work out.
<script setup>
import CompanyItem from "./CompanyItem.vue";
import ProductItem from "./ProductItem.vue";
import CampaignItem from "./CampaignItem.vue";
import { useCurrentCompanyStore } from "../stores/currentCompanyStore.js"
import { useCurrentProductStore } from "../stores/currentProductStore.js"
const companyStore = useCurrentCompanyStore();
const productStore = useCurrentProductStore();
const companies =
[
{
company_id: 1,
name: 'Domain of Man',
fund_balance: 100000,
products_list: [
{
product_id: 1,
name: 'gate'
},
{
product_id: 2,
name: 'exploration ship'
},
{
product_id: 3,
name: 'artifacts'
}
]
},
{
company_id: 2,
name: 'Hegemony',
fund_balance: 200000,
products_list: [
{
product_id: 1,
name: 'toothbrash'
},
{
product_id: 2,
name: 'ore'
},
{
product_id: 3,
name: 'food'
}
]
},
];
const campaigns = [
{
campaign_id: 1,
company_id: 1,
product_id: 1,
campaign_name: "Gates for everyone",
keywords: [
"one for each",
"limited offer"
],
bid_amount: 25000,
status: true,
town: "Tarnow",
radius: "10"
},
{
campaign_id: 2,
company_id: 1,
product_id: 3,
campaign_name: "Get them while they last",
keywords: [
"rare",
"one for each",
"limited offer"
],
bid_amount: 25000,
status: false,
town: "Tarnow",
radius: "10"
},
{
campaign_id: 3,
company_id: 3,
product_id: 1,
campaign_name: "Let the shine power your ship",
keywords: [
"electricity",
"green technology",
],
bid_amount: 25000,
status: true,
town: "Tarnow",
radius: "10"
}
];
</script>
<template>
<div class="container">
<div class="companies" >
<CompanyItem v-for="company in companies" v-bind:key="company.company_id" :company-id="company.company_id">
<template #name>
{{ company.name }}
</template>
<template #budget>
{{ company.fund_balance }}
</template>
</CompanyItem>
</div>
<div class="products">
<template v-for="company in companies">
<ProductItem
v-for="product in company.products_list"
v-bind:key="product.product_id"
:id="company.company_id"
v-if="companyStore.companyId === company.company_id"
:product-id="product.product_id">
<template #name>
{{ product.name }}
</template>
</ProductItem>
</template>
</div>
<div class="campaigns">
<CampaignItem
v-for="campaign in campaigns"
v-if="companyStore.companyId === campaign.company_id"
v-bind:key="campaign.campaign_id"
:id="campaign.campaign_id"
>
<template #name>
{{campaign.campaign_name}}
</template>
</CampaignItem>
</div>
</div>
</template>
<style scoped>
.container {
width: 100%;
height: 100%;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: auto;
grid-template-areas:
"companies products campaigns";
}
.companies {
grid-area: companies;
display: flex;
flex-direction: column;
overflow: hidden;
}
.products {
grid-area: products;
}
.campaigns {
grid-area: campaigns;
}
</style>
Here are stores:
import { defineStore } from 'pinia'
export const useCurrentCompanyStore = defineStore({
id: 'currentComapny',
state: () => ({
companyId: -1
}),
getters: {
getCompanyId: (state) => state.companyId
},
actions: {
change(newCompanyId) {
this.companyId = newCompanyId;
}
}
})
import { defineStore } from 'pinia'
export const useCurrentProductStore = defineStore({
id: 'currentProduct',
state: () => ({
productId: -1
}),
getters: {
getCompanyId: (state) => state.productId
},
actions: {
change(newProductId) {
this.productId = newProductId;
}
}
})
Btw. if anybody wants to run it themself here is the git repo, its feature/frontend branch:
https://github.com/kuborek2/campaign_planer
You must not use v-if and v-for on the same element because v-if will always be evaluated first due to implicit precedence.
And exactly because of that, you are facing this error of undefined company_id as v-for is not executed yet and v-if is trying to access it.
Make the changes as suggested below and it should fix your error.
<CampaignItem
v-for="campaign in campaigns"
:key="campaign.campaign_id"
:id="campaign.campaign_id"
>
<template v-if="companyStore.companyId === campaign.company_id" #name>
{{campaign.campaign_name}}
</template>
</CampaignItem>
Click here for the reference

Standard "check-all" functionality in table

Here's a part of my grid (CRUD) component:
<template>
<table class="MyComponent table">
<thead>
<tr>
<th width="30px">
<b-form-checkbox v-model="allChecked" />
</th>
</tr>
</thead>
<tbody>
<tr v-for="(record, index) in records" :key="index">
<td width="30px">
<b-form-checkbox :value="record['id']" v-model="checkedRows" />
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: "MyComponent",
components: {
},
props: ['config'],
data() {
return {
records: [{
id: 1
}, {
id: 2
}, {
id: 3
}, {
id: 4
}, {
id: 5
}, {
id: 6
}],
checkedRows: []
}
},
computed: {
allChecked: {
get() {
return this.records.length == this.checkedRows.length
},
set(v) {
if(v) {
this.checkedRows = [];
for(var i in this.records) {
this.checkedRows.push(this.records[i]['id'])
}
}
else {
this.checkedRows = [];
}
}
}
}
};
</script>
As you can see, I would like to achive a standard, widely used functionality: The user can check multiple rows and do some operation with the selected rows. The problem is with the "check all" checkbox on the top of the table. When I check all, then I remove the tick from only one checkbox below, it unchecks all the checkboxes on page.
I understand why its happening: When I remove a tick from on of the checkboxes below, the "this.records.length == this.checkedRows.length" condition will no longer be true, so the "allChecked" computed variable will be set to false, therefore the top checkbox will set to unchecked. The problem is: when the top checkbox will be unchecked, then all of the checkboxes will be unchecked as well, because of the "set" part of the computed variable.
Is there a clean way to solve this problem in Vue?
I'm not sure what you want to do with the checked rows, but maybe this will be better:
<b-form-checkbox :value="record['id']" v-model="record.checked" />
Then add to your objects in records a checked property.
records: [
{
id: 1,
checked: false
},
...
]
and if you need a list of checked records you might do a computed property:
computed: {
checkedRecords() {
return this.records.filter(record => record.checked);
}
}
and for checking-unchecking all you just iterate over all records:
<b-form-checkbox #change="clickedAll" />
methods: {
clickedAll(value) {
this.records = this.records.map(record => {
record.checked = value
return record
}
}
}
OK, meanwhile I solved the problem. Here's my solution. Thanks #Eggon for your help, you gave the idea to use the #change method.
<template>
<table class="MyComponent table">
<thead>
<tr>
<th width="30px">
<b-form-checkbox v-model="allChecked" #change="checkAll" />
</th>
</tr>
</thead>
<tbody>
<tr v-for="(record, index) in records" :key="index">
<td width="30px">
<b-form-checkbox :value="record['id']" v-model="checkedRows" />
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: "MyComponent",
components: {
},
props: ['config'],
data() {
return {
records: [{
id: 1
}, {
id: 2
}, {
id: 3
}, {
id: 4
}, {
id: 5
}, {
id: 6
}],
checkedRows: []
}
},
methods: {
checkAll(value) {
if(!value) {
this.checkedRows = [];
return ;
}
var newCheckedRows = [];
for(var i in this.records) {
newCheckedRows.push(this.records[i].id)
}
this.checkedRows = newCheckedRows;
}
},
computed: {
allChecked: {
get() {
return this.records.length == this.checkedRows.length
},
set() {
}
}
}
};
</script>

Value of <Input in Vue component not getting value of method

I have a list of users. Click on a specific user the user edit form is populated but the only way I can get a value in the input is by putting the return in the
<input v-model="this.cl.data.USER_RLTNP_SCTY_ACCS_GRP.User_Name" ref=User_Name>
if I do
<input v-model="User_Name "v-bind:value="this.cl.data.USER_RLTNP_SCTY_ACCS_GRP.User_Name">
Nothing appears in the input.
If I use the v-model="this.cl.data....." the value of the user_name is in the input I'm not sure how the value is passed to my updateUser() function because normally I would use username = this.User_Name
Using apollo and Graphgl for the querying
<template>
<div>
<v-text-field label="User Name" ref="User_Name" v-model="User_Name" v-bind:value="this.cl.data.USER_RLTNP_SCTY_ACCS_GRP[0].User_Name" id="" placeholder="User Name"></v-text-field>
<v-btn v-on:click="editUser()">Edit</v-btn>
</div>
</template>
<script>
import gql from graphql-tag import { SCTY_ACCS_GRP, SCTY_ACCS_GRP_USER }
from './gqlqueries'
export default {
data: () => ({
users: [],
cl: '',
user_form: false,
user_name: ''
}),
methods: {
editForm: async function(userid){
this.cl = await this.$apollo.query({query : SCTY_ACCS_GRP_USER, variables: {id : userid}})
this.user_form = true
console.log(this.cl)
alert(this.cl.data.USER_RLTNP_SCTY_ACCS_GRP[0].User_Name)
},
editUser(){
this.user_name = this.User_Name
alert(this.user_name)
}
},
mounted: async function() {
this.users = await this.$apollo.query({ query: SCTY_ACCS_GRP })
// console.log(this.users.data.USER_RLTNP_SCTY_ACCS_GRP)
} }
</script>
I would assume the <input v-bind:value="this.cl.data...."> Would populate once The editForm() function is triggered. So how do I get the value of the User_Name when the editUser button is clicked
You could try list rendering.
A little example :
https://jsfiddle.net/L4xu9r5g/7/
var mapp = new Vue({
el: "#app",
data: {
currentuserinfo : undefined,
users: [
{ name: "John Doe", id : 12},
{ name: "Black Jack", id : 932},
{ name: "White Jack", id: 342}
],
userinfo:
[
{ userid: 12, favcolor: 'green', age : 23},
{ userid: 932, favcolor: 'red', age : 11},
{ userid: 342, favcolor: 'blue', age : 12}
]
},
methods:
{
selectuser : function(id)
{
//your query here
this.currentuserinfo = this.userinfo.filter(u => u.userid ==id)[0];
}
}
})
td, th
{
padding: 5px;
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table>
<tbody>
<tr>
<th>Name</th>
<th>Select User</th>
</tr>
<tr v-for="user in users" :key="user.id">
<td>{{user.name}}</td>
<td><button #click="selectuser(user.id)">Select User</button></td>
</tr>
</tbody>
</table>
<div v-if="currentuserinfo === undefined">
Please select a user
</div>
<div v-if="currentuserinfo != undefined">
<span>Favourite color : {{currentuserinfo.favcolor}}</span><br/>
<span>Age : {{currentuserinfo.age}}</span><br/>
</div>
</div>

How can I make a reusable "CheckAll" checkbox solution in Vue2

I am trying to create a reusable "Check All" solution for displaying a list of objects retrieved from an API.
I really like the get/set methods of computed properties that I use in this example here, https://codepen.io/anon/pen/aLeLOZ but I find that rewriting the same function over and over again and maintaining a seperate checkbox state list is tedious.
index.html
<div id="app">
<input type="checkbox" v-model="selectAll1"> Check All
<div v-for="person in list1">
<input type="checkbox" v-model="checkbox" :value="person.id">
<span>{{ person.name }}</span>
</div>
<hr/>
<input type="checkbox" v-model="selectAll2"> Check All
<div v-for="person in list2">
<input type="checkbox" v-model="checkbox2" :value="person.id">
<span>{{ person.name }}</span>
</div>
</div>
main.js
new Vue({
el: '#app',
data () {
return {
list1: [
{ id: 1, name: 'Jenna1'},
{ id: 2, name: 'Jenna2'},
{ id: 3, name: 'Jenna3'},
{ id: 4, name: 'Jenna4'}
],
list2: [
{ id: 1, name: 'Mary1'},
{ id: 2, name: 'Mary2'},
{ id: 3, name: 'Mary3'},
{ id: 4, name: 'Mary4'}
],
checkbox: [],
checkbox2: []
}
},
computed: {
selectAll1: {
get: function () {
return this.list1 ? this.checkbox.length === this.list1.length : false
},
set: function (value) {
let selected = []
if (value) {
this.list1.forEach(function (bf) {
selected.push(bf.id)
})
}
this.checkbox = selected
}
},
selectAll2: {
get: function () {
return this.list2 ? this.checkbox2.length === this.list2.length : false
},
set: function (value) {
let selected = []
if (value) {
this.list2.forEach(function (bf) {
selected.push(bf.id)
})
}
this.checkbox2 = selected
}
},
}
});
How can I make a resuable selectAll() function that will work in this example that can be included as often as needed?
Is it possible to make a class that can maintain the check box state for each list and still function as a computed property to make use of the v-model directive?
It's not the same at all, but a method based solution would be
methods: {
selectUs: function(){
if (this.checkbox.length <= this.list1.length) {
this.checkbox = Array.from(Array(this.list1.length + 1).keys())
} else {
this.checkbox = []
}
}
}
with #click="selectUs" instead of v-model="selectAll1"
(you could keep the get part of your computed properties to keep track of whether all are selected, and then use if (selectAll1) { etc } in the method)

Table Pagination with VueJS

I want to make pagination for my table but, when I run, my data does not appear anything. I am confused my code which is wrong perhaps here anyone can help me.
var newVue = new Vue({
el: '#app',
data: {
items: [
{ name: "item #1" }, { name: "item #2" }, { name: "item #3" }, { name: "item #4" },
{ name: "item #5" }, { name: "item #6" }, { name: "item #7" }, { name: "item #8" },
{ name: "item #9" }, { name: "item #10" }, { name: "item #11" }, { name: "item #12" }
],
start: 0,
limit: 2,
pagination: null,
total: 12
},
computed: {
filtered: function(){
return this.items.slice(0, this.limit)
}
},
mounted: function() {
this.limit = parseInt(this.pagination);
},
watch: {
pagination: function() {
this.limit = parseInt(this.pagination);
if(this.limit != this.start && this.start > 0)
this.start = parseInt(this.pagination);
this.limit = this.start + parseInt(this.pagination);
}
},
methods: {
paginate: function(direction) {
if(direction === 'next') {
this.start += parseInt(this.pagination);
this.limit += parseInt(this.pagination);
}
else if(direction === 'back') {
this.limit -= parseInt(this.pagination);
this.start -= parseInt(this.pagination);
}
},
},
filters: {
paginate: function(array, start, limit) {
return array.slice(start, limit);
}
}
});
.pager button {
background: #fff;
border: 1px solid #ddd;
border-radius: 15px;
color: #337AB7;
padding: 7px 13px;
text-align: center;
}
.pager button:hover {
background: #eee;
cursor: pointer;
outline: none;
}
.pager button:disabled {
background: #eee;
color: #bbb;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/css/bootstrap.min.css" rel="stylesheet"/>
<div id="app">
<table class="table table-striped table-advance table-table-hover table-bordered">
<thead>
<th>Item</th>
</thead>
<tbody>
<tr v-for="item in filtered | paginate">
<td>{{ item.name }}</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-md-12 center">
<ul class="pager">
<li>
<button #click="paginate('previous')" :disabled="start <= 0">
Previous
</button>
</li>
<li>
<button #click="paginate('next')" :disabled="limit >= total">
Next
</button>
</li>
</ul>
</div>
</div>
</div>
<script src="https://unpkg.com/vue#2.1.10/dist/vue.js"></script>
There are few problems with your code, after removing those your code works as can be seen here in fiiddle.
You dont need | paginate with v-for as filtered is computed property which will give you paginated result
<tbody>
<tr v-for="item in filtered | paginate">
<td>{{ item.name }}</td>
</tr>
</tbody>
in filtered you needed to pass correct params in slice method.
computed: {
filtered: function(){
return this.items.slice(this.start, this.limit)
}
},`