Removing a row from a table with VueJS - vuejs2

In Vue how do you remove a row from a table when the item is deleted?
Below is how I am rendering the table
<tbody>
<tr v-for="item in items">
<td v-text="item.name"></td>
<td v-text="item.phone_number"></td>
<td v-text="item.email"></td>
<td><button #click="fireDelete(item.id)">Delete</button></td>
</tr>
</tbody>
Below is an excerpt from my Vue component.
data() {
return {
items: []
}
},
methods: {
fireDelete(id) {
axios.delete('/item/'+id).then();
}
},
mounted() {
axios.get('/item').then(response => this.items = response.data);
}
The axios.get work and so does the axios.delete, but the fronend doesn't react so the item is only removed from the table after a page refresh. How do I get it to remove the relevant <tr>?

I've managed to work out a nice way. I pass the index to the fireDelete method and use the splice function. Works exactly how I wanted.
<tbody>
<tr v-for="(item, index) in items" v-bind:index="index">
<td v-text="item.name"></td>
<td v-text="item.phone_number"></td>
<td v-text="item.email"></td>
<td><button #click="fireDelete(item.id, index)">Delete</button></td>
</tr>
</tbody>
fireDelete(id, index) {
axios.delete('/item/'+id).then(response => this.organisations.splice(index, 1));
}

I had the same trouble as this question. So maybe someone will find this usefull.
For the button use:
v-if="items.length > 1" v-on:click="fireDelete(index)"
And the fireDelete function:
fireDelete: function (index) {
this.photos.splice(index, 1);
}

You can try to modify your #click="fireDelete(item.id)" part to a custom method #click='deleteData(items, item.id)'
and do something like:
methods: {
deleteData (items, id) {
this.items = null // These parts may not
this.fireDelete(id) // match your exact code, but I hope
} // you got the idea.
}
and your template can do just:
<tbody>
<tr v-for="item in items" v-if='items'>
<td v-text="item.name"></td>
<td v-text="item.phone_number"></td>
<td v-text="item.email"></td>
<td><button #click="deleteData(item, item.id)">Delete</button></td>
</tr>
</tbody>

Related

nested v-for loop not rendering

I am trying to display a table full of Orders and the corresponding Order details in a row-like fashion. However, I've run into a bit of a conundrum. Any geniuses out there? I've tried all sorts of variations in approaching this problem but I'm stuck. Someone mentioned that perhaps using a 'computed' property would do the trick but I haven't been able to get it to work >=/. A bit of help, maybe a lot of help, would be greatly appreciated!
<div id="MyOrdersApp" class="table-responsive">
<table class="table table-striped table-bordered table-condensed">
<thead>
<tr style="color:black;font-size:2em;">
<th style="text-align:center;">Order Id</th>
<th style="text-align:center;">ProductIdsAndQuantity</th>
<th style="text-align:center;">Total Cost</th>
<th style="text-align:center;">Order Status</th>
<th style="text-align:center;">Shipment Tracking URL</th>
<th style="text-align:center;">Created Date</th>
</tr>
</thead>
<tbody>
<tr v-for="(order, i) in parsedOrders" style="background-color:snow;text-align:center;" class="align-content-center;">
<td>{{order.OrderId}}</td>
<td>
<ul>
<li v-if="order.CartLineItems != null" v-for="item in order.CartLineItems;" :key="uniqueKey">
<img :src="item.ProductImageUrl" />
<span>{{item.ProductName}} | </span>
<span>Quantity: {{item.Quantity}} | </span>
</li>
</ul>
</td>
<td style="color:black;font-size:1.5em;font-weight:bold;">{{order.TotalRevenue | currency }}</td>
<td>{{order.OrderStatus}}</td>
<td>{{order.ShipmentTrackingURL}}</td>
<td>{{order.CreatedDate | formatDate }}</td>
</tr>
</tbody>
</table>
</div>
<script>
var MyOrdersApp = new Vue({
el: '#MyOrdersApp',
data: {
parsedOrders: [],
theOrders: [],
uniqueKey: 0
},
computed: {
cartLineItems: function () {
return this.parsedOrders[0].CartLineItems;
}
},
methods: {
getMyOrders: function () {
var scope = this;
this.$http.get("https://" + location.host + "/Home/GetMyOrders").then(resp => {
if (resp.status == 200) {
this.theOrders = resp.body;
scope.setCartLineItems();
}
});
},
setCartLineItems: function () {
var scope = this;
var cartLineItems = [];
var cartDict = {};
for (var i = 0; i < this.theOrders.length; i++) {
cartDict = {};
cartLineItems = [];
cartDict = JSON.parse(this.theOrders[i].ProductIdsAndQuantity).CartLineItems;
for (var key in cartDict) {
var lineItem = JSON.parse(cartDict[key]);
cartLineItems.push(lineItem);
}
//this.allorders[i].CartLineItems = cartLineItems;
//scope.$set(this.allorders[i], 'CartLineItems', cartLineItems);
//this.theOrders[i].CartLineItems = cartLineItems;
scope.$set(this.theOrders[i], 'CartLineItems', cartLineItems);
}
this.parsedOrders = this.theOrders;
console.log("~~ Parsed Cart! ~~ ");
console.log(this.parsedOrders);
console.log(this.parsedOrders[1].CartLineItems);
}
},
mounted: function () {
var scope = this;
this.getMyOrders();
setTimeout(x => { scope.$forceUpdate(); scope.uniqueKey++; }, 1000);
}
});
</script>
How should I modify this to properly display the values in 'CartLineItems'??
Btw, parsedOrders looks like this:
If I comment out the inner v-for, the other columns display just fine and the table looks like this:
Some more background... when I fetch the orders via the $http call and the server returns the array of JSON, the ProductIdsAndQuantity property needs to be parsed as a JSON object and then set as it's own property on the order in question. The tricky part here is that the Vue component seems to not be reacting to the change in the order object array data. Hence, the need for a parsedOrders property and/or the use of scope.$forceUpdate(); or scope.uniqueKey++;. These were proposed solutions to the issue of the Vue component not re-rendering. However, these solutions are not working. So I'm stuck scratching my head...
You can't have both v-for and v-if on the same statement that's what is causing the issue. So instead try using computed in this case
<tbody>
<tr v-for="(order, i) in parsedOrders" style="background-color:snow;text-align:center;" class="align-content-center;">
<td>{{order.OrderId}}</td>
<td>
<ul>
<li v-for="(item, index) in getCartLineItems(order)" :key="index">
<img :src="item.ProductImageUrl" />
<span>{{item.ProductName}} | </span>
<span>Quantity: {{item.Quantity}} | </span>
</li>
</ul>
</td>
<td style="color:black;font-size:1.5em;font-weight:bold;">{{order.TotalRevenue | currency }}</td>
<td>{{order.OrderStatus}}</td>
<td>{{order.ShipmentTrackingURL}}</td>
<td>{{order.CreatedDate | formatDate }}</td>
</tr>
</tbody>
and in computed
computed: {
getCartLineItems() {
return order => {
if(order?.CartLineItems && order.CartLineItems.length) return order.CartLineItems;
return [];
}
}
}

Trying to not show v-model change until I want to show it

Okay so I have a table and the table has a number display and an Input for the user to increase the number up and down.
<tr v-for="(obj, index) in OBJECTARRAY" :key="obj.key">
<td>{{OBJOTHERNUMBER- OBJNUMBER}}</td>
<td>
<input v-model="OBJNUMBER" type="number">
</td>
</tr>
I don't want this change to show dynamically with a v-model but with a button click that updates the change?
What you could do is use 2 variables.
DiscountPriceTemp
DiscountPrice
And when you click on the button, you update DiscountPrice with DiscountPriceTemp.
<tbody>
<tr v-for="(obj, index) in OBJECTARRAY" :key="obj.key">
<td>{{ obj.SalePrice - obj.DiscountPrice }}</td>
<td>
<input v-model="obj.DiscountPriceTemp" type="number">
</td>
</tr>
</tbody>
export default {
methods: {
onButtonClick (obj) {
obj.DiscountPrice = obj.DiscountPriceTemp
}
}
}
You can use a ref to access the input value and set the discountPrice when the update button is clicked. In Vue it's best practice to not access the document directly. Not sure if you want a button for each row or a bulk update so I've included both. Here's the codepen if you want to see it in action https://codepen.io/madison_at_vrume/pen/mdWNrMy
<div>
<tbody>
<tr v-for="(obj, index) in prices" :key="obj.key">
<td>{{ obj.salePrice - obj.discountPrice }}</td>
<td>
<input
:ref="`discountInput-${index}"
:value="obj.discountPrice"
type="number"
>
</td>
<td>
<button #click="updatePrice(index)">Update Price</button>
</td>
</tr>
</tbody>
<button #click="updateAllPrices()">Update Prices</button>
</div>
methods: {
updatePrice(index) {
const newDiscountPrice = this.$refs[`discountInput-${index}`].value;
this.prices[index].discountPrice = newDiscountPrice;
},
updateAllPrices() {
this.prices.forEach((obj, index) => {
this.updatePrice(index);
})
},
}

Vue2: Can I pass an optional (global) filter into a reusable component?

I am quite new to Vue.
I am working on a table as component, which is supposed to be a lot of times. So far, so good, but now I want to use a filter, which can be optional passed into it.
That is how I "call" the table:
<table
:headers="headers"
:items="some.data"
></table>
data () {
return {
headers: [
{ title: 'date', value: ['date'], filter: 'truncate(0, 10, '...')' },
]
}
}
Here is my table component
<template>
<div>
<table class="table">
<thead>
<tr>
<!-- <th v-for="header in headers" :key="header.id" scope="col">{{ header.title }}</th> -->
<th v-for="header in headers" :key="header.id" scope="col">
{{ header.title }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items" :key="item.id" scope="col">
<td v-for="header in headers" :key="header.id" scope="row">
<!-- {{item}} -->
<span v-for="val in header.value" :key="val.id">
{{item[val] | truncate(0, 10, '...') }}
</span>
<!-- {{header.filter}} -->
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script >
export default {
name: 'Table',
props: {
headers: Array,
items: Array
}
}
</script>
My global filter:
Vue.filter('truncate', function (text, start, truncLength, clamp = '') {
return text.slice(start, truncLength) + clamp
// return text.slice(0, stop) + (stop < text.length ? clamp || ' ...' : '')
})
I was hopping, to add by that an optional filter (via v-if I would chech for it). So far I can render the filter as string ... but not execute it.
Even if I put the filter in the span, it does not work (it says then, "text.slice is not a function" in the console.
I was not successful with googling it, because with filters/filter it is mostly about how to use .filter(...) on data as JS method, but not as Vue filter.
Any advise?
A filter is a function that runs inside JSX template in html. Example of how to create custom Vue.js filter
Vue.filter('ssn', function (ssn) { // ssn filter
return ssn.replace(/(\d{3})(\d{2})(\d{4})/, '$1-$2-$3');
});
Using it
{{stringToTrans | ssn}}
To use a filter outside this you can use a computed property or standard function like so
convertedDateString() { // computed
return this.$moment(this.dateString).format('MM/DD/YYYY')
}
convertedDateString(dateString) { // method
return this.$moment(dateString).format('MM/DD/YYYY')
}

Vue Component data object property's behavior not as expected [Solved]

I have an app with a child component that makes a call to an api for a player's season stats. You click on the players name and I emit click event to child from Parent component. The problem is when you click on Players name from parent all instances of the child component are revealed. I just want the one player. I thought because I have a showComponent instance for each child by toggling this.showComponent in child would get my expected behavior but no.
Code:
Parent-
methods: {
emitPlayerSeasonStatsClicked: function(event) {
const target = event.target;
EventBus.$emit("showPlayerTemplateClicked", target);
}
},
template: `
<div v-for="playerStats in props_box_game_scores[index].data.gameboxscore.awayTeam.awayPlayers.playerEntry">
<tr v-if="playerStats.player.Position === 'P'" class="d-flex" v-bind:data-player-id="playerStats.player.ID">
<td class="col-4 justify-content-center" scope="row" title="Click for Season Stats">
{{playerStats.player.FirstName}} {{playerStats.player.LastName}}
<span v-if="playerStats.stats.Wins['#text'] === '1'">(W)</span>
<span v-else-if="playerStats.stats.Losses['#text'] === '1'">(L)</span>
<span v-else-if="playerStats.stats.Saves['#text'] === '1'">(S)</span>
</td>
<td class="col-2 justify-content-center" justify-content="center">
{{playerStats.stats.InningsPitched['#text']}}</td>
<td class="col-2 justify-content-center">{{playerStats.stats.RunsAllowed['#text']}}</td>
<td class="col-2 justify-content-center">{{playerStats.stats.PitcherStrikeouts['#text']}}</td>
<td class="col-2 justify-content-center">{{playerStats.stats.EarnedRunAvg['#text']}}
</td>
</tr>
<pitcher-season-stats v-bind:props_player_id="playerStats.player.ID"></pitcher-season-stats>
</div>
Child-
cumlativeStats: Vue.component("player-season-stats", {
props: ["props_player_id"],
data: function() {
return {
Hits: "",
HR: "",
RBI: "",
BattingAvg: "",
showComponent: false
};
},
mounted: function() {
EventBus.$on("showPlayerTemplateClicked", function(data) {
this.showComponent = !this.showComponent;
});
},
methods: {
retrievePlayerStats: function(playerId) {
const url = `https://api.mysportsfeeds.com/v1.2/pull/mlb/2019-regular/cumulative_player_stats.json?player=`;
const params = {
playerstats: "AB,H,HR,RBI,AVG",
force: true
};
...
template: `
<tr class="d-flex" v-if:showComponent>
<td #click="retrievePlayerStats(props_player_id)" class="col-4 justify-content-center" scope="row">
Season Stats</td>
</td>
<td class="col-2 justify-content-center" justify-content="center">
{{ Hits }}</td>
<td class="col-2 justify-content-center">{{ HR }}</td>
<td class="col-2 justify-content-center"> {{ BattingAvg }}</td>
<td class="col-2 justify-content-center">{{ RBI }}</td>
</tr>
` // End template
})
Any suggestions welcome. Sorry for the formatting.
**
Updated Working Solution:
**
Parent:
methods: {
emitPlayerSeasonStatsClicked: function($event) {
let playerId = $event.target.dataset.playerId;
EventBus.$emit("showPlayerTemplateClicked", playerId);
}
}
....
<table #click="emitPlayerSeasonStatsClicked($event)" class="table table-striped table-bordered table-hover table-sm collapse" v-bind:class="'multi-collapse-' + index">
<tr v-if="playerStats.stats.AtBats['#text'] > 0" class="d-flex">
<td class="col-4 justify-content-center" :data-player-id='playerStats.player.ID' scope="row" title="Click for Season Stats">
{{playerStats.player.FirstName}} {{playerStats.player.LastName}} ({{playerStats.player.Position}})</td>
Child:
mounted: function() {
EventBus.$on(
"showPlayerTemplateClicked",
this.onShowPlayerTemplateClicked
);
},
methods: {
onShowPlayerTemplateClicked: function(playerId) {
if (playerId === this.props_player_id) {
this.loading = true;
this.showComponent = !this.showComponent;
this.retrievePlayerStats(playerId);
}
},
template: `
<transition name="fade">
<tr class="d-flex" v-if="showComponent">
<td v-if="!loading" class="col-4 justify-content-center" scope="row">
Season Stats</td>
</td>
<td class="col-2 justify-content-center" justify-content="center">
{{ Hits }}</td>
<td class="col-2 justify-content-center">{{ HR }}</td>
<td class="col-2 justify-content-center"> {{ BattingAvg }}</td>
<td class="col-2 justify-content-center">{{ RBI }}</td>
</tr>
</transition>
` // End template
})
};
The code provided doesn't actually call emitPlayerSeasonStatsClicked but I assume that's supposed to go on the <td> that includes the name.
If you write the click listener like this:
<td
class="col-4 justify-content-center"
scope="row"
title="Click for Season Stats"
#click="emitPlayerSeasonStatsClicked(playerStats.player.ID)"
>
Then include the id as part of the event emitted by the event bus:
emitPlayerSeasonStatsClicked: function(playerId) {
EventBus.$emit("showPlayerTemplateClicked", playerId);
}
Listening for this in the mounted would be:
mounted: function() {
EventBus.$on("showPlayerTemplateClicked", this.onShowPlayerTemplateClicked);
},
with method:
methods: {
onShowPlayerTemplateClicked: function(playerId) {
if (playerId === this.props_player_id) {
this.showComponent = !this.showComponent;
}
}
}
Assuming the player ids are unique that should be enough to get it working.
However...
The choice of an event bus to pass data to a child seems a poor one. There are several ways this could be done. One way would be to only create it when it's showing (external v-if). Another would be to use props. Yet another would be to use refs to call a method on the child.
I don't understand how your code ever toggled anything. The this value for the listener in mounted will not be the component. I've fixed that by moving it to a method, which Vue will bind correctly. Another reason to move this to a method is that it allows you to remove the listener when the component is destroyed.
v-if:showComponent is not a thing. I assume that should be v-if="showComponent".
Your <tr> elements seem to be immediate children of <div> elements. That isn't correct HTML for tables.

Add active class a column to table (as in netflix)

I need help to select a table header and select your column using classes. As in Netflix. I'm noob in VueJS
Example GIF
My code is
<table class="table">
<thead class="text-center">
<tr>
<th scope="col"><button type="button" class="btn plan_columnA selected" #click="planSelect('plan_columnA')">Column A</button></th>
<th scope="col"><button type="button" class="btn plan_columnB" #click="planSelect('plan_columnB')">Column B</button></th>
<th scope="col"><button type="button" class="btn plan_columnC" #click="planSelect('plan_columnC')">Column C</button></th>
</tr>
</thead>
<tbody class="text-center">
<tr>
<td class="plan_columnA selected">Mark</td>
<td class="plan_columnB">Otto</td>
<td class="plan_columnC">#mdo</td>
</tr>
<tr>
<td class="plan_columnA selected">Jacob</td>
<td class="plan_columnB">Thornton</td>
<td class="plan_columnC">#fat</td>
</tr>
<tr>
<td class="plan_columnA selected">Larry</td>
<td class="plan_columnB">the Bird</td>
<td class="plan_columnC">#twitter</td>
</tr>
</tbody>
</table>
My style is
.btn {
background-color: darkgrey;
color: white;
}
button.selected {
background-color: red;
}
td.selected {
color: red;
}
I try to do this, but I do not know if it's right
export default {
data () {
return {
planSelected: '',
}
},
methods: {
planSelect (plan) {
this.planSelected = plan;
$('.selected').removeClass('selected');
$('.' + this.planSelected).addClass('selected');
},
},
}
I tried JQuery, but I want to do it in VueJS.
Thanks!
That's fairly easy, i've made an example for you in a fiddle, hope it helps you on the way. It should be made more dynamically, for better overview, but you can play around with the code i've made.
In the perfect scenario, you would generate all rows/columns from a data variable, instead of doing all this manually.
https://jsfiddle.net/6aojqm0k/
What i've made is just having 1 data variable, which you set and check for on the different tds and buttons.
data: () => ({
planSelected: 'plan_columnA'
})
Button to choose the plan:
<button type="button" class="btn plan_columnA" :class="{selected: planSelected === 'plan_columnA' }" #click="planSelected = 'plan_columnA'">Column A</button>
And the actual column to show selected
<td class="plan_columnA" :class="{selected: planSelected === 'plan_columnA' }">Mark</td>
Pro tip: Never combine jQuery and VueJS - Just use VueJS