Passing b-icon to <td> element in VueJS - vue.js

I want to pass a piece of HTML to a table-data-element using VueJS. The following demonstrates my scenario:
<template>
<div>
<div v-if="someObject.properties" style="margin-top: 20px;" class="table-responsive-md">
<table class="table table-striped">
<thead>
<tr>
<th style="text-align: left" scope="col">Some icons</th>
</tr>
</thead>
<tbody v-for="(property, index) in someObject.properties" :key="index">
<tr>
<td style="text-align: center" v-html="getIconWhenSomeRequirementIsMet(property)"/>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script lang="ts">
...
getIconWhenSomeRequirementIsMet (property: any): string {
if (property.type === 'animal') return '<b-icon-check class="h3 mb-0" style="color:green;"/>'
if (property.type === 'human') return '<b-icon-check class="h3 mb-0" style="color:yellow;"/>'
return '<b-icon-x class="h3 mb-0" style="color:red;"/>'
}
</script>
The code above is a minimal example of my Vue single file component. However, this way, I get empty fields in my table instead of the actual icons. Isn't there a simple and clean approach to achieve this?

The reason it doesn't work is because you can't use v-html to render custom components.
Instead, here's two different ways you can do this.
The first is to pre-define your b-icon-* and use v-if, v-else-if and v-else to match which icon to show.
The second is to dynamically bind properties using v-bind, this way you can use a method to do it, like you are now, but instead return the properties based on the type.
new Vue({
el: "#app",
data() {
return {
items: [
{ type: "animal" },
{ type: "human" },
{ type: "alien" },
],
fields: ['Type', 'Icon 1', 'Icon 2']
}
},
methods: {
getIconWhenSomeRequirementIsMet (type) {
/* Default properties */
const properties = {
icon: 'x',
style: 'color: red',
class: 'h3 mb-0'
};
if (type === 'animal') {
properties.icon = 'check';
properties.style = 'color: green;';
}
else if (type === 'human') {
properties.icon = 'check';
properties.style = 'color: yellow;';
}
return properties;
}
}
})
<link href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="//unpkg.com/bootstrap-vue#2.7.0/dist/bootstrap-vue.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<div id="app">
<div class="table-responsive-md">
<table class="table table-striped">
<thead>
<tr>
<th v-for="field in fields" >{{ field }}</th>
</tr>
</thead>
<tbody>
<tr v-for="{ type } in items">
<td>
{{ type }}
</td>
<td>
<b-icon-check v-if="type === 'animal'" variant="success" class="h3 mb-0">
</b-icon-check>
<b-icon-check v-else-if="type === 'human'" variant="warning" class="h3 mb-0">
</b-icon-check>
<b-icon-x v-else variant="danger" class="h3 mb-0">
</b-icon-x>
</td>
<td>
<b-icon v-bind="getIconWhenSomeRequirementIsMet(type)"></b-icon>
</td>
</tr>
</tbody>
</table>
</div>
</div>

Related

Unable to calculate product qty

I'm trying to calculate the quantity of items in my vue. The problem I'm having is that my computed property isn't picking up my object, because my thinking was as you can see with the commented out section is that I was going to loop through it and calculate the quantity, but since I'm not able to grab productItems I'm not able to loop through it.
Here is my code
<template>
<div class="content-header">
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-lg-12">
<table class="table">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Qty</th>
</tr>
</thead>
<tbody>
<tr v-for="item in products">
<td>
{{ item['name'] }}
</td>
<td>
<input style="width: 100px; margin-left: 0; display: inline"
type="number" class="form-control"
v-model="productItems[item['name']]['unit']"
>
</td>
</tr>
<tr>
<td></td>
<td>
Consumption total: {{ consumptionTotal }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default{
props: [],
data(){
return {
productItems: {}
},
computed:{
consumptionTotal(){
console.log(this.productItems);
// return Object.keys(this.productItems).reduce((carry, item) => {
// carry += Number(this.productItems[item]['unit'])
// return carry;
// }, Number(0));
},
},
watch: {
},
methods: {
},
mounted() {
}
}
</script>
Try below steps. Hopefully it will helps you.
Step 1: productItems should be an array
Step 2: Calculate functions be like
computed: {
consumptionTotal () {
return this.productItems.reduce((total, item) => {
total += item.unit
return total
}, Number(0))
}
}
Step 3: HTML template will be like
<div class="content-header">
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-lg-12">
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Qty</th>
</tr>
</thead>
<tbody>
<tr v-for="item in productItems" :key="item.id">
<td>{{item.id}}</td>
<td>
{{item.name}}
</td>
<td>
<input style="width: 100px; margin-left: 0; display: inline" type="number" class="form-control" :value="item.unit">
</td>
</tr>
<tr>
<td></td>
<td>
Consumption total: {{ consumptionTotal }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
DEMO

Dynamically set v-model

I'm trying to make my v-model naming dynamic, but when I run npm run watch I get this error
You are binding v-model directly to a v-for iteration alias. This will not be able to modify the v-for source array because writing to the alias is like modifying a function local variable. Consider using an array of objects and use v-model on an object property instead
and it points to my v-model.
Here is my code
<template>
<div class="content-header">
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-lg-12">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr v-for="product in products">
<td>{{ product }}</td>
<td>
<input type="text" class="form-control" v-model="product" :name="product"> month
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default{
props: [],
data(){
return {
products: []
}
},
computed:{
},
methods: {
getProducts(){
axios.get(`/api/product/all`).then(response => {
this.products = response.data.products;
});
}
},
mounted() {
}
}
</script>

Getting a NaN when typing a value

I'm trying to calculate a value from 2 input boxes and then get the total of those input boxes. I'm then trying to get the all my amounts and total them and add them to the subtotal but the issue I'm having is that when I type in a number in the first box my output is NaN instead of 0 and I would like for it to show me a 0 instead.
Here is my code
<template>
<div class="content-header">
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-lg-12">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Unit</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr v-for="product in products">
<td>{{ product['name'] }}</td>
<td>
<input type="text" class="form-control" v-model="unit[product['unit']]" #change="calculateCost(product['name'])">
</td>
<td>
<input type="text" class="form-control" v-model="price[product['price']]" #change="calculateCost(product['name'])">
</td>
<td>
{{ cost[product['name']] }}
</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>
Subtotal: {{ subTotal }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default{
props: [],
data(){
return {
products: [],
unit: {},
price: {},
cost: []
}
},
computed:{
subTotal(){
if(this.cost!== null)
{
if(Object.keys(this.cost).length !== 0){
return Object.keys(this.cost).reduce((carry, item) => {
carry+= Number(this.cost[item])
return carry;
}, Number(0))
}else{
return 0;
}
}
}
},
methods: {
getProducts(){
axios.get(`/api/product/all`).then(response => {
this.products = response.data.products;
});
},
calculateCost(item){
this.cost[item] = Number(this.unit[item]) * Number(this.price[item]);
},
},
mounted() {
this.getProducts();
}
}
</script>
Almost all type of inputs return a string. You can use
<input type="number" class="form-control" v-model="unit[product['unit']]" #change="calculateCost(product['name'])">
or
<input type="text" class="form-control" v-model.number="unit[product['unit']]" #change="calculateCost(product['name'])">
The problem is the v-model for unit and price are set to the different keys than the one given to calculateCost(), which causes the lookups to fail and results in NaN:
<input v-model="unit[product['unit']]" #change="calculateCost(product['name'])"> ❌
^^^^^^ ^^^^^^
<input v-model="price[product['price']]" #change="calculateCost(product['name'])"> ❌
^^^^^^^ ^^^^^^
<input v-model="unit[product['name']]" #change="calculateCost(product['name'])"> ✅
<input v-model="price[product['name']]" #change="calculateCost(product['name'])"> ✅
Setting the keys to product['name'] ensures the correct lookup for unit and price in calculateCost(). Since the user could enter invalid values (non-numeric strings) or omit a value, it's possible to get NaN, so it would be a good idea to add a NaN-check in calculateCost():
calculateCost(item) {
const unit = Number(this.unit[item]);
const price = Number(this.price[item]);
if (Number.isNaN(unit) || Number.isNaN(price)) return;
this.cost[item] = unit * price;
},
Also, you probably want the cost to react to user input, so switch from #change to #input:
<input v-model="unit[product['name']]" #input="calculateCost(product['name'])">
<input v-model="price[product['name']]" #input="calculateCost(product['name'])">
demo

How to redirect to a page with an object in vue

I have a couple of vue pages, one page is a list of users, the second is a list of permissions and the third is a vue component
that displays the list of users and another vue component that shows the details of a user.
What I'm trying to do is, if I'm on the permissions page and I would like to go to a specific user's details I would like to redirect
with that panel open.
I am able to redirect the the users page by doing window.location.href = '/admin/users'; but I'm not sure how I'm going to get
the user panel open.
I thought that if I could do $emit then that could pass the user object over to the users main page, but that won't work if I do
a window.location.href = '/admin/users';
Here is my code. This has 2 components displays a list of users and their details
<template>
<div class="row">
<div class="col-md-6">
<users :emit="emit" :users="users"></users>
</div>
<div class="col-md-6">
<detail v-if="userDetails" :emit="emit" :user="manageUser"></detail>
</div>
</div>
</template>
<script>
export default {
props: ['emit', 'users'],
data() {
return {
userDetails: false,
manageUser: null
}
},
mounted() {
this.emit.$on('user', payload => {
this.manageUser = payload.user;
this.userDetails = true;
});
}
}
</script>
This is my users lists
<template>
<div class="card">
<div class="card-header">
<h3 class="card-title text-success" style="cursor: pointer;" #click="createUser"> New User</h3>
</div>
<div class="card-body">
<table class="table table-sm table-striped">
<thead>
<tr>
<th>Name</th>
<th style="width:120px;"></th>
</tr>
</thead>
<tbody>
<tr v-for="user in users">
<td>{{user.name}}</td>
<td style="text-align: right;">
<button class="btn btn-sm btn-outline-info" #click="detail(user)">Details</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
export default {
props: ['emit', 'users'],
data() {
return {
}
},
methods: {
detail(user) {
this.emit.$emit('user', { user: user });
}
}
}
</script>
and this is my permissions pages
<template>
<div class="card">
<div class="card-header">
<h3 class="card-title">{{permission.name}}</strong></h3>
</div>
<div class="card-body p-0">
<table class="table table-sm table-striped">
<thead>
<tr>
<th>User Name</th>
<th style="width:120px;"></th>
</tr>
</thead>
<tbody>
<tr v-for="user in users">
<td>{{user.name}}</td>
<td style="text-align: right;">
<button class="btn btn-sm btn-outline-info" #click="detail(user)">Details</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
export default {
props: ['emit'],
data() {
return {
users: []
}
},
mounted() {
this.allUsers();
},
methods: {
allUsers() {
this.loading = true;
axios.get('/users').then(response => {
this.users = response.data.users;
});
},
detail(user) {
window.location.href = '/admin/users';
}
}
}
</script>

Hiding Rows After a Certain Limit of Rows Has Been Updated

I'm new to coding with a very basic understanding to Javascript. I created a table that will push an update to my tables upon clicking a button. However, i do want to limit the number of rows within my table. say for example upon pushing the sixth data to the table, i do want the first data to be removed from the row.
I tried searching everywhere with no luck. Maybe it's because my basic understand to javascript is pretty basic. Im a newbie haha. I am using vue.js for this code.
HTML
<form class="form-inline">
<div class="form-group mx-sm-3 mb-2">
<input
type="number"
v-model="newElement.buy"
class="form-control">
</div>
<button
v-on:click="buyStock"
type="button"
class="btn btn-success mb-2">BUY
</button>
</form>
<section class="stock_tables">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">{{codeBuy}}</th>
<th><a v-html="negative"></a> Buy</th>
</tr>
</thead>
<tbody>
<tr v-for="u in stocks">
<th></th>
<td>{{u.buy}}</td>
</tr>
</tbody>
</table>
</section>
Script
<script>
new Vue({
el: '#app',
data: {
codeBuy: "12345",
stocks: [],
newElement: {buy:"",sell:""}
},
methods: {
buyStock: function(){
this.stocks.push({buy:this.newElement.buy});
this.newElement = {buy:""};
}
}
});
</script>
So basically everytime i enter the amount of the stocks and press buy it will update.
You have some options to modify the buyStock method:
this.stocks.shift()
this.$delete(this.stocks, 0)
this.stocks = this.stocks.slice(1);
this.stocks = this.stocks.slice(-5);
this.stocks = this.stocks.splice(0, 1);
or, without the if use:
this.stocks = [...this.stocks, {
buy: this.newElement.buy
}].slice(-5);
Demo below:
new Vue({
el: '#app',
data: {
codeBuy: "12345",
stocks: [],
newElement: {
buy: "",
sell: ""
},
negative: ''
},
methods: {
buyStock: function() {
this.stocks.push({
buy: this.newElement.buy
});
if (this.stocks.length > 5) {
// this.$delete(this.stocks, 0);
// this.stocks = this.stocks.slice(1);
// this.stocks = this.stocks.slice(-5);
// this.stocks = this.stocks.splice(0, 1);
this.stocks.shift();
}
//this.stocks = [...this.stocks, {
// buy: this.newElement.buy
//}].slice(-5);
this.newElement = {
buy: ""
};
}
}
});
<script src="https://unpkg.com/vue"></script>
<div id="app">
<form class="form-inline">
<div class="form-group mx-sm-3 mb-2">
<input type="number" v-model="newElement.buy" class="form-control">
</div>
<button v-on:click="buyStock" type="button" class="btn btn-success mb-2">BUY</button>
</form>
<section class="stock_tables">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">{{codeBuy}}</th>
<th><a v-html="negative"></a> Buy</th>
</tr>
</thead>
<tbody>
<tr v-for="u in stocks">
<th></th>
<td>{{u.buy}}</td>
</tr>
</tbody>
</table>
</section>
</div>