I am fetching the JSON data(Orders) from REST API and displaying in a dynamic HTML table using Vue js. I have a "Print" button for each row in the table. The purpose of the button is printing the data of the row in a structure, basically a bill.
For that, I want to highlight the newly added row until the Print button is clicked by the user. How do I achieve this?
I'm refreshing the table every minute.
This is my code.
<tr v-for="orders, index in orders">
<th scope="row">{{ index + 1 }}</th>
<td>{{ orders.id }}</td>
<td>{{ orders.billing.first_name + " " +orders.billing.last_name }}</td>
<td>{{ orders.date_created }}</td>
<td>{{ orders.billing.phone}}</td>
<td>{{ orders.billing.address_1 + ", " + orders.billing.address_2 + ", " + orders.billing.city + orders.billing.postcode }}</td>
<td>{{ orders.line_items.name}}</td>
<td>{{ orders.total}}</td>
<td><button class="btn btn-primary" (click)="printBill(data)">Print</button></td>
</tr>
</tbody>
</table>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
orders: []
},
mounted: function() {
axios.get('https://localhost/Site/wp-json/wc/v3/orders?consumer_key=KEY&consumer_secret=KEY1')
.then(response => {
this.orders = response.data;
console.log(response);
})
.catch(error => {
console.log(error);
});
},
})
</script>
I wrote a small example, have a look:
<template>
<div id="app">*
<tr
v-for="(order, index) in orders"
:key="index"
:class="{highlight: orders[index].isPrinted === undefined}"
>
<th scope="row">{{ index + 1 }}</th>
<td>{{ order.name }}</td>
<td>{{ order.something}}</td>
<td>
<button class="btn btn-primary" #click="printBill(index)">Print</button>
</td>
</tr>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
orders: []
};
},
methods: {
printBill(index) {
//code to print the bill
//change flag
this.$set(this.orders[index], "isPrinted", true);
}
},
mounted() {
//axios request - data sample
this.orders = [
{
name: "first",
something: "whatever"
},
{
name: "second",
something: "whatever"
},
{
name: "third",
something: "whatever"
},
{
name: "fourth",
something: "whatever"
},
{
name: "fifth",
something: "whatever"
}
];
}
};
</script>
<style>
.highlight {
background-color: blue;
color: white;
}
th {
width: 20%;
}
td {
width: 20%;
}
</style>
You can run it here.
As you can see that I am adding a flag to elements in orders array whenever printBill method runs.
By tracking newly added property we can conditionally display highlight class.
Add an isPrinted flag to each row of data, making sure you retain this if rows had been previously flagged. Also, call the API every minute.
mounted: function() {
// Call the API the first time
this.refreshData()
// Then call the API every minute
this.setIntervalId = setInterval(this.refreshData, 60000)
},
beforeDestroy: function() {
// Stop refreshing data after the component is destroyed!
clearInterval(this.setIntervalId)
}
methods: {
// Extract refresh logic into a method
refreshData () {
axios.get('https://localhost/Site/wp-json/wc/v3/orders?consumer_key=KEY&consumer_secret=KEY1')
.then(response => {
// Note the orders we previously flagged as printed, so we can reapply the flag after refreshing
const previouslyFlaggedIds = this.orders.filter(x => x.is_printed).map(x => x.id);
this.orders = response.data.map(x => ({...x, is_printed: previouslyFlaggedIds.find(y => y === x.id) != null}));
})
.catch(error => {
console.log(error);
});
}
}
Use this to style the rows
<tr
v-for="(order, index) in orders"
:key="order.id"
:class="{highlight: !order.is_printed}"
>
Set is_printed when rows are printed.
<td><button class="btn btn-primary" #click="printBill(order)">Print</button></td>
methods: {
printBill(order) {
order.is_printed = true
}
}
Related
I'm working on a project which i need to fetch "clients" data on a table.
The data actually comes, but i can't manage to display it. Here's my template part, where i call for info with a v-for.
<tbody>
<tr v-for="item in clients" v-bind:key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.email }}</td>
<td>{{ item.documents.cpf || item.documents.cnpj }}</td>
<td>{{ item.documents.celular }}</td>
<td>{{ item.status }}</td>
<td v-if="item.address">
{{ `${item.address.localidade} / ${item.address.uf}` }}
</td>
<td v-else>-</td>
<td>
<a :href="`/ver-cliente/${item.id}`"
></a>
</td>
</tr>
</tbody>
On my DevTools, the data is shown:
Full DevTools with all errors (some of them are from other pages)
My script part:
export default {
data: () => ({
clients: [],
paginationData: {
lastPage: 0,
currentPage: 1,
},
loading: false,
}),
methods: {
getClients(page = 1) {
this.loading = true;
this.$api
.get("/api_v1/clientes", {
params: { page },
})
.then(({ data }) => {
console.log(data);
if (data.success) {
const {
data: result,
per_page,
total,
last_page,
current_page,
} = data.data;
this.clients = result;
this.paginationData = {
perPage: per_page,
total,
lastPage: last_page,
currentPage: current_page,
};
}
})
.finally(() => (this.loading = false));
},
},
mounted() {
this.getClients();
},
};
That' what i managed to do, but it isn't working.
Can anyone give me a hint of what am I doing wrong? Sorry for any rookie mistakes.
It can't find the property cnjp in the item.documents object, so it throws an error. To get rid of that error you can do
item.documents?.cnpj so if the property is not in the object, if will be null instead of throwing an error.
I'm building a component on my project, which is actually getting all the data and console logging it, but here's the problem: Inside my array of clients, i have some objects (address, documents, ...), and i can't manage to call them on my table.
My script:
<script>
export default {
data: () => ({
clients: [],
}),
methods: {
getClients() {
this.$api
.get("/api_v1/clientes", {
})
.then((response) => {
this.clients = response.data[0];
console.log(response.data);
})
.catch((e) => {
console.log(e);
});
},
},
mounted() {
this.getClients();
},
};
</script>
My table (inside ):
<tbody>
<tr v-for="client in clients" v-bind:key="client.id">
<td>{{ client.id }}</td>
<td>{{ client.name }}</td>
<td>{{ client.email }}</td>
<td>{{ client.documents.cpf || client.documents.cnpj }}</td>
<td>{{ client.documents.celular }}</td>
<td>{{ client.status }}</td>
<td v-if="client.address">
{{ `${client.address.localidade} / ${client.address.uf}` }}
</td>
<td v-else>-</td>
<td>
<a :href="`/see-client/${client.id}`"
><i class="icon-magnifier"></i
></a>
<i class="icon-check" style="color: green"></i>
<i class="icon-close" style="color: red"></i>
</td>
</tr>
</tbody>
My controller:
public function index(Request $request)
{
$data = [
'pag' => 'All clients',
'link' => '/'
];
return view('clients.index', $data);
}
The data:
Someone have a clue of a different approach i could have? I'm using Vue2. It's one of my first big projects, so previously sorry for any rookie mistake. Thanks for your time and help!
This line is only getting the first client:
this.clients = response.data[0];
response.data is your array of clients (from the looks of things). When you use .data[0], you're getting the first element of the array (i.e. the first client).
Then, this line is trying to loop over 1 client, not an array of clients.
<tr v-for="client in clients" v-bind:key="client.id">
Try changing
this.clients = response.data[0];
to
this.clients = response.data;
If that doesn't work (it looks like you've got a weird data structure), try this instead:
this.clients = response.data.data;
Or this (it's unclear to me how many nested data properties you have):
this.clients = response.data.data.data;
I just made a quick analysis about your code. I think you should polish it a little bit.
Let me start with a quick catch up:
Update yuor js section with:
<script>
export default {
// Please do use the function format instead of lambda expression, it's recommended in the vue2 docs.
data() {
return {
clients: [],
};
},
methods: {
// Change this to an async method, so you can have more control on your code.
async getClients() {
try {
/**
* Here, you should have to know, that your file `routes/api.php` hass all of the prefixed /api routes
* So you have a direct access to /api prefixed routes
* Additionally read a little bit about destructuring.
*/
const response = await this.$api.get("/api/clientes");
// Now, please notice that you have 2 data path names.
this.clients = response.data.data; // {or please follow the correct path to the array container of the clients}.
} catch (e) {
console.log("Check this error: ", e);
}
},
},
// Now, change your mounted to an async method
async mounted() {
// Trust me this is going to work perfectly.
await this.getClients();
},
};
</script>
Now, please, please change your api controller logic to a response()->json(...)
public function index(Request $request)
{
// Your retrieve logic...
return response()->json($data);
}
Finally if you have successfully configured everything abouve, your vue component should be able to retrieve the information correctly, and your tbody must work this way...
<tbody>
<tr v-for="client in clients" v-bind:key="client.id">
<td>{{ client.id }}</td>
<td>{{ client.name }}</td>
<td>{{ client.email }}</td>
<td>{{ client.documents.cpf || client.documents.cnpj }}</td>
<td>{{ client.documents.celular }}</td>
<td>{{ client.status }}</td>
<td v-if="client.address">
<!-- You can replace what you have with: -->
{{ client.address.localidade }} / {{ client.address.uf }}
</td>
<td v-else>
-
</td>
<td>
<a :href="`/see-client/${client.id}`">
<i class="icon-magnifier"></i>
</a>
<i class="icon-check" style="color: green"></i>
<i class="icon-close" style="color: red"></i>
</td>
</tr>
</tbody>
I am calling Vue.js using a shortcode on a Wordpress page.
pl2010_vue_init_directory = pl2010_vue_init_directory || (function(ctx) {
new Vue(
{
el: '#'+ctx.el,
data: {
errorMsg: "",
successMsg: "",
showAddModal: false,
showEditModal: false,
showDeleteModal: false,
listings: [],
newListing: {name: "",tel1: "",email1: ""},
currentListing: {}
},
mounted: function(){
console.log("Start Mounting...");
this.getAllListings();
console.log("End Mounting...");
},
methods: {
async getAllListings(){
this.listings = [];
axios.get('/scripts/prod/directory.php?action=read').then(response => {
this.listings = response.data.listings;
console.log(this.listings)
})
.catch(err => {
console.log(err);
});
}
}
});
});
NOTE: I have updated the "getAllListings()" function. The working code is above. The values now output ok.
<tr class="text-center" v-for="listing in listings">
<td>{{ listing.id }}</td>
<td>{{ listing.name }}</td>
<td>{{ listing.tel1 }}</td>
<td>{{ listing.email1 }}</td>
<td><i class="fas fa-edit"></i></td>
<td><i class="fas fa-trash-alt"></i></td>
</tr>
Thank you!
Try this
async getAllListings() {
try {
const res = await fetch("/scripts/prod/directory.php?action=read");
if (res.data.error) {
console.log("Error");
errorMsg = response.data.message;
} else {
const data = await res.json();
listings = data.listings;
console.log(listings);
}
} catch (err) {
console.log(err);
}
}
Amended the code above and it is now working ok. Array is assigned to "listings" and outputs on the page.
I have a probem to load data from database into my table created im vueJS. i have created my component table and my script in app.js, but in view i can see this error:
[Vue warn]: Property or method "datosUsuario" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
found in
---> <Formularioactualizacion> at resources/js/components/datosUsuarios.vue
is problem to v-for that it not detect my array from script vue. i have checked my array for is empty, but not, he have data. Also i have a new route for load de data user and other for load the view and all it´s ok, but i can´t load de data into de table. I attached my actual code.
app.js
require('./bootstrap');
window.Vue = require('vue');
Vue.component('usuarios-component', require('./components/usuariosComponent.vue').default);
Vue.component('formularioactualizacion', require('./components/datosUsuarios.vue').default);
// inicio de VUE
const app = new Vue({
el: '#contenedorVue',
created: function(){
this.cargar();
},
data: {
datosUsuario: [],
},
methods: {
cargar: function(){
let url = '/getDatosPersonales';
axios.get(url).then((response) => {
this.datosUsuario = response.data;
}).catch((error) => console.error(error));
},
enviar(){
let url = '/actualizarDatos';
axios.post(url, {
id: this.id,
nombreUsuario: this.nombreUsuario,
email: this.email,
password: this.password,
direccion: this.direccion
}).then(function(response){
this.arrayTasks = response.data;
}).catch(function(error){
console.log(error);
})
}
}
});
Component
<template>
<div class="tabla-usuarios">
<table class="table table-hover table-striped">
<thead>
<th>ID</th>
<th>NOMBRE</th>
<th>EMAIL</th>
<th>DIRECCIÓN</th>
<th>CONTRASEÑA</th>
</thead>
<tbody>
<tr v-for="usuario in datosUsuario" :key="usuario.id">
<td>{{ usuario.id }}</td>
<td>{{ usuario.nombre }}</td>
<td>{{ usuario.email }}</td>
<td>{{ usuario.direccion }}</td>
<td>{{ usuario.password }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
data() {
return {
datosUsuario: [],
};
},
created: function () {
this.cargar();
},
methods: {
cargar: function () {
let url = "/getDatosPersonales";
axios
.get(url)
.then((response) => {
this.datosUsuario = response.data;
console.log(this.datosUsuario);
})
.catch((error) => console.error(error));
},
},
};
</script>
my problem is in component in this v-for... i´m new in vueJS, i´m traying initiate in this frameworks.
Thanks so much for help
EDIT
[Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions.
warn # app.js:38441
./node_modules/vue/dist/vue.common.dev.js.strats.data # app.js:39068
mergeField # app.js:39372
mergeOptions # app.js:39367
Vue.extend # app.js:42959
Vue.<computed> # app.js:43037
./resources/js/app.js # app.js:49878
__webpack_require__ # app.js:20
0 # app.js:50103
__webpack_require__ # app.js:20
(anonymous) # app.js:84
(anonymous) # app.js:87
app.js:38441 [Vue warn]: Property or method "datosUsuario" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
found in
---> <Formularioactualizacion> at resources/js/components/datosUsuarios.vue
<Root>
here you Component is looking for datosUsuario variable inside that component that's why your getting that error to fix this
Component
<template>
<div class="tabla-usuarios">
<table class="table table-hover table-striped">
<thead>
<th>ID</th>
<th>NOMBRE</th>
<th>EMAIL</th>
<th>DIRECCIÓN</th>
<th>CONTRASEÑA</th>
</thead>
<tbody>
<tr v-for="usuario in datosUsuario" :key="usuario.id">
<td>{{ usuario.id }}</td>
<td>{{ usuario.nombre }}</td>
<td>{{ usuario.email }}</td>
<td>{{ usuario.direccion }}</td>
<td>{{ usuario.password }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
data() {
return {
datosUsuario: [],
};
},
created: function () {
this.cargar();
},
methods: {
cargar: function () {
let url = "/getDatosPersonales";
axios
.get(url)
.then((response) => {
this.datosUsuario = response.data;
})
.catch((error) => console.error(error));
},
enviar() {
let url = "/actualizarDatos";
axios
.post(url, {
id: this.id,
nombreUsuario: this.nombreUsuario,
email: this.email,
password: this.password,
direccion: this.direccion,
})
.then(function (response) {
this.arrayTasks = response.data;
})
.catch(function (error) {
console.log(error);
});
},
},
};
</script>
and remove function form app.js
I'm new to using VUE.Js, and i created a very simple app to try out how it works.
The problem happens immediately where when i run the app, the watch for a variable is triggered in an infinite loop. I cannot figure out why. There is a v-for loop but that is on an array that only has two elements.
Initially the SubTotal should be 0. But as soon as the app is run, it triggers the Buy method, even though i haven't clicked the buy button and the sub total ends up being 442.37999999999965.
Thanks for any help.
Here is the jsfiddle Beer shopping cart
HTML :
<div id = "growler">
<table>
<tr>
<th style='width:150px'>Beer</th>
<th style='width:50px'>Price</th>
<th style='width:30px'></th>
</tr>
<tr v-for = "beer in beers">
<td>{{ beer.name }}</td>
<td>{{ beer.price }}</td>
<td>
<button :click="buy(beer)">buy</button>
</td>
</tr>
<tr>
<td>SubTotal</td>
<td>{{subTotal}}</td>
<td></td>
</tr>
</table>
</div>
JS:
new Vue({
el: "#growler",
data: {
beers: [
{name: 'Ahool Ale', price: 2.00},
{name: 'Agogwe Ale', price: 2.38}
],
shoppingCart: [],
subTotal: 0.00
},
watch: {
shoppingCart: function() {
console.log('shopping cart watch triggered');
this.updateSubTotal();
}
},
methods: {
updateSubTotal: function () {
var s=this.shoppingCart.length;
var t=0;
for (var i=0;i<s; i++){
t += this.shoppingCart[i].price;
}
this.subTotal = t;
},
buy: function (beer) {
console.log('beer pushed on array');
this.shoppingCart.push(beer);
}
},
beforeCreate: function() {
console.log('beforeCreate');
},
created: function() {
console.log('created');
},
beforeMount: function() {
console.log('beforeMount');
},
mounted: function() {
console.log('mounted');
},
beforeUpdate: function() {
console.log('beforeUpdate');
},
updated: function() {
console.log('updated');
},
beforeDestroy: function() {
console.log('beforeDestroy');
},
destroyed: function() {
console.log('afterDestroy');
}
});
I found your mistake:
<button :click="buy(beer)">buy</button>
You used :(v-bind) instead of #(v-on:) on the click handler.
When you first bind it, the function is called once and updates the shoppingCart. This will update the subTotal data, which will force a re-render of the DOM, which will trigger the buy function again because of the :bind.
Fix:
<button #click="buy(beer)">buy</button>
<!-- or -->
<button v-on:click="buy(beer)">buy</button>
Suggested changes for your code:
Use computed properties instead of a method to update a property that represents a sum of other values:
new Vue({
el: "#growler",
data: {
beers: [{
name: 'Ahool Ale',
price: 2.00
},
{
name: 'Agogwe Ale',
price: 2.38
}
],
shoppingCart: []
},
watch: {
shoppingCart: function() {
console.log('shopping cart watch triggered');
}
},
computed: {
subTotal: function() {
return this.shoppingCart.reduce(function(total, beer) {
return total + beer.price;
}, 0);
}
}
},
methods: {
buy: function(beer) {
this.shoppingCart.push(beer);
}
},
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<div id="growler">
<button>buy</button>
<table>
<tr>
<th style='width:150px'>Beer</th>
<th style='width:50px'>Price</th>
<th style='width:30px'></th>
</tr>
<tr v-for="beer in beers">
<td>{{ beer.name }}</td>
<td>{{ beer.price }}</td>
<td>
<button #click="buy(beer)">buy</button>
</td>
</tr>
<tr>
<td>SubTotal</td>
<td>{{subTotal}}</td>
<td></td>
</tr>
</table>
</div>